/ doc / src / arch / tx_lifetime.md
tx_lifetime.md
  1  # Transactions
  2  
  3  _(Temporary document, to be integrated into other docs)_
  4  
  5  ## Transaction behaviour
  6  
  7  In our network context, we have two types of nodes.
  8  
  9  1. Miner (`M`)
 10  2. Spectator (`S`)
 11  
 12  `S` acts as a relayer for transactions in order to help out
 13  that transactions reach `M`.
 14  
 15  To avoid spam attacks, `S` should keep $tx$ in their mempool for some
 16  period of time, and then prune it.
 17  
 18  ## Ideal simulation with instant confirmation
 19  
 20  The lifetime of a transaction $tx$ that passes verification and whose
 21  state transition can be applied on top of the canonical (confirmed)
 22  chain:
 23  
 24  1. User creates a transaction $tx$
 25  2. User broadcasts $tx$ to `S`
 26  3. `S` validates $tx$ state transition
 27  4. $tx$ enters `S` `mempool`
 28  5. `S` broadcasts $tx$ to `M`
 29  6. `M` validates $tx$ state transition
 30  7. $tx$ enters `M` `mempool`
 31  8. `M` validates all transactions in its `mempool` in sequence
 32  9. `M` proposes a block confirmation containing $tx$
 33  10. `M` writes the state transition update of $tx$ to their chain
 34  11. `M` removes $tx$ from their `mempool`
 35  12. `M` broadcasts the confirmed proposal
 36  13. `S` receives the proposal and validates transactions
 37  14. `S` writes the state updates to their chain
 38  15. `S` removes $tx$ from their `mempool`
 39  
 40  ## Real-world simulation with non-instant confirmation
 41  
 42  The lifetime of a transaction $tx$ that passes verification and whose
 43  state transition is pending to be applied on top of the canonical
 44  (confirmed) chain:
 45  
 46  1. User creates a transaction $tx$
 47  2. User broadcasts $tx$ to `S`
 48  3. `S` validates $tx$ state transition
 49  4. $tx$ enters `S` `mempool`
 50  5. `S` broadcasts $tx$ to `M`
 51  6. `M` validates $tx$ state transition
 52  7. $tx$ enters `M` `mempool`
 53  8. `M` proposes a block proposal containing $tx$
 54  9. `M` proposes more block proposals
 55  10. When proposals can be confirmed, `M` validates all their transactions
 56  in sequence
 57  11. `M` writes the state transition update of $tx$ to their chain
 58  12. `M` removes $tx$ from their `mempool`
 59  13. `M` broadcasts the confirmed proposals sequence
 60  14. `S` receives the proposals sequence and validates transactions
 61  15. `S` writes the state updates to their chain
 62  16. `S` removes $tx$ from their `mempool`
 63  
 64  ## Real-world simulation with non-instant confirmation, forks and multiple `CP` nodes
 65  
 66  The lifetime of a transaction $tx$ that passes verifications and whose
 67  state transition is pending to be applied on top of the canonical
 68  (confirmed) chain:
 69  
 70  1. User creates a transaction $tx$
 71  2. User broadcasts $tx$ to `S`
 72  3. `S` validates $tx$ state transition against canonical chain state
 73  4. $tx$ enters `S` `mempool`
 74  5. `S` broadcasts $tx$ to `P`
 75  6. `M` validates $tx$ state transition against all known fork states
 76  7. $tx$ enters `M` `mempool`
 77  8. `M` broadcasts $tx$ to rest `M` nodes
 78  9. Block producer `SM` finds which fork to extend
 79  10. `SM` validates all unproposed transactions in its `mempool` in
 80    sequence, extended fork state, discarding invalid
 81  11. `SM` creates a block proposal containing $tx$ extending the fork
 82  12. `M` receives block proposal and validates its transactions against
 83  the extended fork state
 84  13. `SM` proposes more block proposals extending a fork state
 85  14. When a fork can be confirmed, `M` validates all its proposals
 86  transactions in sequence, against canonical state
 87  15. `M` writes the state transition update of $tx$ to their chain
 88  16. `M` removes $tx$ from their `mempool`
 89  17. `M` drop rest forks and keeps only the confirmed one
 90  18. `M` broadcasts the confirmed proposals sequence
 91  19. `S` receives the proposals sequence and validates transactions
 92  20. `S` writes the state updates to their chain
 93  21. `S` removes $tx$ from their `mempool`
 94  
 95  `M` will keep $tx$ in its `mempool` as long as it is a valid state
 96  transition for any fork(including canonical) or it get confirmed.
 97  
 98  Unproposed transactions refers to all $tx$ not included in a proposal
 99  of any fork.
100  
101  If a fork that can be confirmed fails to validate all its
102  transactions(14), it should be dropped.
103  
104  ## The `Transaction` object
105  
106  ```rust
107  pub struct ContractCall {
108      /// The contract ID to which the payload is fed to
109      pub contract_id: ContractId,
110      /// Arbitrary payload for the contract call
111      pub payload: Vec<u8>,
112  }
113  
114  pub struct Transaction {
115      /// Calls executed in this transaction
116      pub calls: Vec<ContractCall>,
117      /// Attached ZK proofs
118      pub proofs: Vec<Vec<Proof>>,
119      /// Attached Schnorr signatures
120      pub signatures: Vec<Vec<Signature>>,
121  }
122  ```
123  
124  A generic DarkFi transaction object is simply an array of smart
125  contract calls, along with attached ZK proofs and signatures needed
126  to properly verify the contracts' execution. A transaction can have
127  any number of calls, and proofs, provided it does not exhaust a set
128  gas limit.
129  
130  In DarkFi, every operation is a smart contract. This includes payments,
131  which we'll explain in the following section.
132  
133  ## Payments
134  
135  For A -> B payments in DarkFi we use the Sapling scheme that originates
136  from zcash. A payment transaction has a number of _inputs_ (which are
137  coins being burned/spent), and a number of _outputs_ (which are coins
138  being minted/created). An explanation for the ZK proofs for this scheme
139  can be found [here](../zkas/examples/sapling.md) under the Zkas section
140  of this book.
141  
142  In code, the structs we use are the following:
143  
144  ```rust
145  pub struct MoneyTransferParams {
146      pub inputs: Vec<Input>,
147      pub outputs: Vec<Output>,
148  }
149  
150  pub struct Input {
151      /// Pedersen commitment for the input's value
152      pub value_commit: ValueCommit,
153      /// Pedersen commitment for the input's token ID
154      pub token_commit: ValueCommit,
155      /// Revealed nullifier
156      pub nullifier: Nullifier,
157      /// Revealed Merkle root
158      pub merkle_root: MerkleNode,
159      /// Public key for the Schnorr signature
160      pub signature_public: PublicKey,
161  }
162  
163  pub struct Output {
164      /// Pedersen commitment for the output's value
165      pub value_commit: ValueCommit,
166      /// Pedersen commitment for the output's token ID
167      pub token_commit: ValueCommit,
168      /// Minted coin: poseidon_hash(pubkey, value, token, serial, blind)
169      pub coin: Coin,
170      /// The encrypted note ciphertext
171      pub encrypted_note: EncryptedNote,
172  }
173  
174  pub struct EncryptedNote {
175      pub ciphertext: Vec<u8>,
176      pub ephemeral_key: PublicKey,
177  }
178  
179  pub struct Note {
180      /// Serial number of the coin, used to derive the nullifier
181      pub serial: pallas::Base,
182      /// Value of the coin
183      pub value: u64,
184      /// Token ID of the coin
185      pub token_id: TokenId,
186      /// Blinding factor for the value Pedersen commitment
187      pub value_blind: ValueBlind,
188      /// Blinding factor for the token ID Pedersen commitment
189      pub token_blind: ValueBlind,
190      /// Attached memo (arbitrary data)
191      pub memo: Vec<u8>,
192  }
193  ```
194  
195  In the blockchain state, every minted coin must be added into a Merkle
196  tree of all existing coins. Once added, the new tree root is used to
197  prove existence of this coin when it's being spent.
198  
199  Let's imagine a scenario where Alice has 100 ALICE tokens and wants to
200  send them to Bob. Alice would create an `Input` object using the info
201  she has of her coin. She has to derive a `nullifier` given her secret
202  key and the serial number of the coin, hash the coin bulla so she can
203  create a merkle path proof, and derive the value and token commitments
204  using the blinds.
205  
206  ```rust
207  let nullifier = poseidon_hash([alice_secret_key, serial]);
208  let signature_public = alice_secret_key * Generator;
209  let coin = poseidon_hash([signature_public, value, token_id, blind]);
210  let merkle_root = calculate_merkle_root(coin);
211  let value_commit = pedersen_commitment(value, value_blind);
212  let token_commit = pedersen_commitment(token_id, token_blind);
213  ```
214  
215  The values above, except `coin` become the public inputs for the `Burn`
216  ZK proof. If everything is correct, this allows Alice to spend her coin.
217  In DarkFi, the changes have to be atomic, so any payment transaction
218  that is burning some coins, has to mint new coins at the same time, and
219  no value must be lost, nor can the token ID change. We enforce this by
220  using Pedersen commitments.
221  
222  Now that Alice has a valid `Burn` proof and can spend her coin, she can
223  mint a new coin for Bob.
224  
225  ```rust
226  let blind = pallas::Base::random();
227  let value_blind = ValueBlind::random();
228  let token_blind = ValueBlind::random();
229  let coin = poseidon_hash([bob_public, value, token_id, blind]);
230  let value_commit = pedersen_commitment(value, value_blind);
231  let token_commit = pedersen_commitment(token, token_blind);
232  ```
233  
234  `coin`, `value_commit`, and `token_commit` become the public inputs
235  for the `Mint` ZK proof. If this proof is valid, it creates a new coin
236  for Bob with the given parameters. Additionally, Alice would put the
237  values and blinds in a `Note` which is encrypted with Bob's public key
238  so only Bob is able to decrypt it. This `Note` has the necessary info
239  for him to further spend the coin he received.
240  
241  At this point Alice should have 1 input and 1 output. The input is the
242  coin she burned, and the output is the coin she minted for Bob. Along
243  with this, she has two ZK proofs that prove creation of the input and
244  output. Now she can build a transaction object, and then use her secret
245  key she derived in the `Burn` proof to sign the transaction and publish
246  it to the blockchain.
247  
248  The blockchain will execute the smart contract with the given payload
249  and verify that the Pedersen commitments match, that the nullifier has
250  not been published before, and also that the merkle authentication path
251  is valid and therefore the coin existed in a previous state. Outside of
252  the VM, the validator will also verify the signature(s) and the ZK
253  proofs. If this is valid, then Alice's coin is now burned and cannot be
254  used anymore. And since Alice also created an output for Bob, this new
255  coin is now added to the Merkle tree and is able to be spent by him.
256  Effectively this means that Alice has sent her tokens to Bob.