Reward.circom 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. include "../node_modules/circomlib/circuits/poseidon.circom";
  2. include "../node_modules/circomlib/circuits/bitify.circom";
  3. include "../node_modules/circomlib/circuits/comparators.circom";
  4. include "./Utils.circom";
  5. include "./MerkleTree.circom";
  6. include "./MerkleTreeUpdater.circom";
  7. template Reward(levels, zeroLeaf) {
  8. signal input rate;
  9. signal input fee;
  10. signal input instance;
  11. signal input rewardNullifier;
  12. signal input extDataHash;
  13. signal private input noteSecret;
  14. signal private input noteNullifier;
  15. signal private input inputAmount;
  16. signal private input inputSecret;
  17. signal private input inputNullifier;
  18. signal input inputRoot;
  19. signal private input inputPathElements[levels];
  20. signal private input inputPathIndices;
  21. signal input inputNullifierHash;
  22. signal private input outputAmount;
  23. signal private input outputSecret;
  24. signal private input outputNullifier;
  25. signal input outputRoot;
  26. signal input outputPathIndices;
  27. signal private input outputPathElements[levels];
  28. signal input outputCommitment;
  29. signal private input depositBlock;
  30. signal input depositRoot;
  31. signal private input depositPathIndices;
  32. signal private input depositPathElements[levels];
  33. signal private input withdrawalBlock;
  34. signal input withdrawalRoot;
  35. signal private input withdrawalPathIndices;
  36. signal private input withdrawalPathElements[levels];
  37. // Check amount invariant
  38. inputAmount + rate * (withdrawalBlock - depositBlock) === outputAmount + fee;
  39. // === check input and output accounts and block range ===
  40. // Check that amounts fit into 248 bits to prevent overflow
  41. // Fee range is checked by the smart contract
  42. // Technically block range check could be skipped because it can't be large enough
  43. // negative number that `outputAmount` fits into 248 bits
  44. component inputAmountCheck = Num2Bits(248);
  45. component outputAmountCheck = Num2Bits(248);
  46. component blockRangeCheck = Num2Bits(32);
  47. inputAmountCheck.in <== inputAmount;
  48. outputAmountCheck.in <== outputAmount;
  49. blockRangeCheck.in <== withdrawalBlock - depositBlock;
  50. // Compute input commitment
  51. component inputHasher = Poseidon(3);
  52. inputHasher.inputs[0] <== inputAmount;
  53. inputHasher.inputs[1] <== inputSecret;
  54. inputHasher.inputs[2] <== inputNullifier;
  55. // Verify that input commitment exists in the tree
  56. component inputTree = MerkleTree(levels);
  57. inputTree.leaf <== inputHasher.out;
  58. inputTree.pathIndices <== inputPathIndices;
  59. for (var i = 0; i < levels; i++) {
  60. inputTree.pathElements[i] <== inputPathElements[i];
  61. }
  62. // Check merkle proof only if amount is non-zero
  63. component checkRoot = ForceEqualIfEnabled();
  64. checkRoot.in[0] <== inputRoot;
  65. checkRoot.in[1] <== inputTree.root;
  66. checkRoot.enabled <== inputAmount;
  67. // Verify input nullifier hash
  68. component inputNullifierHasher = Poseidon(1);
  69. inputNullifierHasher.inputs[0] <== inputNullifier;
  70. inputNullifierHasher.out === inputNullifierHash;
  71. // Compute and verify output commitment
  72. component outputHasher = Poseidon(3);
  73. outputHasher.inputs[0] <== outputAmount;
  74. outputHasher.inputs[1] <== outputSecret;
  75. outputHasher.inputs[2] <== outputNullifier;
  76. outputHasher.out === outputCommitment;
  77. // Update accounts tree with output account commitment
  78. component accountTreeUpdater = MerkleTreeUpdater(levels, zeroLeaf);
  79. accountTreeUpdater.oldRoot <== inputRoot;
  80. accountTreeUpdater.newRoot <== outputRoot;
  81. accountTreeUpdater.leaf <== outputCommitment;
  82. accountTreeUpdater.pathIndices <== outputPathIndices;
  83. for (var i = 0; i < levels; i++) {
  84. accountTreeUpdater.pathElements[i] <== outputPathElements[i];
  85. }
  86. // === check deposit and withdrawal ===
  87. // Compute tornado.cash commitment and nullifier
  88. component noteHasher = TornadoCommitmentHasher();
  89. noteHasher.nullifier <== noteNullifier;
  90. noteHasher.secret <== noteSecret;
  91. // Compute deposit commitment
  92. component depositHasher = Poseidon(3);
  93. depositHasher.inputs[0] <== instance;
  94. depositHasher.inputs[1] <== noteHasher.commitment;
  95. depositHasher.inputs[2] <== depositBlock;
  96. // Verify that deposit commitment exists in the tree
  97. component depositTree = MerkleTree(levels);
  98. depositTree.leaf <== depositHasher.out;
  99. depositTree.pathIndices <== depositPathIndices;
  100. for (var i = 0; i < levels; i++) {
  101. depositTree.pathElements[i] <== depositPathElements[i];
  102. }
  103. depositTree.root === depositRoot;
  104. // Compute withdrawal commitment
  105. component withdrawalHasher = Poseidon(3);
  106. withdrawalHasher.inputs[0] <== instance;
  107. withdrawalHasher.inputs[1] <== noteHasher.nullifierHash;
  108. withdrawalHasher.inputs[2] <== withdrawalBlock;
  109. // Verify that withdrawal commitment exists in the tree
  110. component withdrawalTree = MerkleTree(levels);
  111. withdrawalTree.leaf <== withdrawalHasher.out;
  112. withdrawalTree.pathIndices <== withdrawalPathIndices;
  113. for (var i = 0; i < levels; i++) {
  114. withdrawalTree.pathElements[i] <== withdrawalPathElements[i];
  115. }
  116. withdrawalTree.root === withdrawalRoot;
  117. // Compute reward nullifier
  118. component rewardNullifierHasher = Poseidon(1);
  119. rewardNullifierHasher.inputs[0] <== noteNullifier;
  120. rewardNullifierHasher.out === rewardNullifier;
  121. // Add hidden signals to make sure that tampering with recipient or fee will invalidate the snark proof
  122. // Most likely it is not required, but it's better to stay on the safe side and it only takes 2 constraints
  123. // Squares are used to prevent optimizer from removing those constraints
  124. signal extDataHashSquare;
  125. extDataHashSquare <== extDataHash * extDataHash;
  126. }
  127. // zeroLeaf = keccak256("tornado") % FIELD_SIZE
  128. component main = Reward(20, 21663839004416932945382355908790599225266501822907911457504978515578255421292);