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.