151 lines
5.6 KiB
Plaintext
151 lines
5.6 KiB
Plaintext
include "../node_modules/circomlib/circuits/poseidon.circom";
|
|
include "../node_modules/circomlib/circuits/bitify.circom";
|
|
include "../node_modules/circomlib/circuits/comparators.circom";
|
|
include "./Utils.circom";
|
|
include "./MerkleTree.circom";
|
|
include "./MerkleTreeUpdater.circom";
|
|
|
|
template Reward(levels, zeroLeaf) {
|
|
signal input rate;
|
|
signal input fee;
|
|
signal input instance;
|
|
signal input rewardNullifier;
|
|
signal input extDataHash;
|
|
|
|
signal private input noteSecret;
|
|
signal private input noteNullifier;
|
|
|
|
signal private input inputAmount;
|
|
signal private input inputSecret;
|
|
signal private input inputNullifier;
|
|
signal input inputRoot;
|
|
signal private input inputPathElements[levels];
|
|
signal private input inputPathIndices;
|
|
signal input inputNullifierHash;
|
|
|
|
signal private input outputAmount;
|
|
signal private input outputSecret;
|
|
signal private input outputNullifier;
|
|
signal input outputRoot;
|
|
signal input outputPathIndices;
|
|
signal private input outputPathElements[levels];
|
|
signal input outputCommitment;
|
|
|
|
signal private input depositBlock;
|
|
signal input depositRoot;
|
|
signal private input depositPathIndices;
|
|
signal private input depositPathElements[levels];
|
|
|
|
signal private input withdrawalBlock;
|
|
signal input withdrawalRoot;
|
|
signal private input withdrawalPathIndices;
|
|
signal private input withdrawalPathElements[levels];
|
|
|
|
// Check amount invariant
|
|
inputAmount + rate * (withdrawalBlock - depositBlock) === outputAmount + fee;
|
|
|
|
// === check input and output accounts and block range ===
|
|
// Check that amounts fit into 248 bits to prevent overflow
|
|
// Fee range is checked by the smart contract
|
|
// Technically block range check could be skipped because it can't be large enough
|
|
// negative number that `outputAmount` fits into 248 bits
|
|
component inputAmountCheck = Num2Bits(248);
|
|
component outputAmountCheck = Num2Bits(248);
|
|
component blockRangeCheck = Num2Bits(32);
|
|
inputAmountCheck.in <== inputAmount;
|
|
outputAmountCheck.in <== outputAmount;
|
|
blockRangeCheck.in <== withdrawalBlock - depositBlock;
|
|
|
|
// Compute input commitment
|
|
component inputHasher = Poseidon(3);
|
|
inputHasher.inputs[0] <== inputAmount;
|
|
inputHasher.inputs[1] <== inputSecret;
|
|
inputHasher.inputs[2] <== inputNullifier;
|
|
|
|
// Verify that input commitment exists in the tree
|
|
component inputTree = MerkleTree(levels);
|
|
inputTree.leaf <== inputHasher.out;
|
|
inputTree.pathIndices <== inputPathIndices;
|
|
for (var i = 0; i < levels; i++) {
|
|
inputTree.pathElements[i] <== inputPathElements[i];
|
|
}
|
|
|
|
// Check merkle proof only if amount is non-zero
|
|
component checkRoot = ForceEqualIfEnabled();
|
|
checkRoot.in[0] <== inputRoot;
|
|
checkRoot.in[1] <== inputTree.root;
|
|
checkRoot.enabled <== inputAmount;
|
|
|
|
// Verify input nullifier hash
|
|
component inputNullifierHasher = Poseidon(1);
|
|
inputNullifierHasher.inputs[0] <== inputNullifier;
|
|
inputNullifierHasher.out === inputNullifierHash;
|
|
|
|
// Compute and verify output commitment
|
|
component outputHasher = Poseidon(3);
|
|
outputHasher.inputs[0] <== outputAmount;
|
|
outputHasher.inputs[1] <== outputSecret;
|
|
outputHasher.inputs[2] <== outputNullifier;
|
|
outputHasher.out === outputCommitment;
|
|
|
|
// Update accounts tree with output account commitment
|
|
component accountTreeUpdater = MerkleTreeUpdater(levels, zeroLeaf);
|
|
accountTreeUpdater.oldRoot <== inputRoot;
|
|
accountTreeUpdater.newRoot <== outputRoot;
|
|
accountTreeUpdater.leaf <== outputCommitment;
|
|
accountTreeUpdater.pathIndices <== outputPathIndices;
|
|
for (var i = 0; i < levels; i++) {
|
|
accountTreeUpdater.pathElements[i] <== outputPathElements[i];
|
|
}
|
|
|
|
// === check deposit and withdrawal ===
|
|
// Compute tornado.cash commitment and nullifier
|
|
component noteHasher = TornadoCommitmentHasher();
|
|
noteHasher.nullifier <== noteNullifier;
|
|
noteHasher.secret <== noteSecret;
|
|
|
|
// Compute deposit commitment
|
|
component depositHasher = Poseidon(3);
|
|
depositHasher.inputs[0] <== instance;
|
|
depositHasher.inputs[1] <== noteHasher.commitment;
|
|
depositHasher.inputs[2] <== depositBlock;
|
|
|
|
// Verify that deposit commitment exists in the tree
|
|
component depositTree = MerkleTree(levels);
|
|
depositTree.leaf <== depositHasher.out;
|
|
depositTree.pathIndices <== depositPathIndices;
|
|
for (var i = 0; i < levels; i++) {
|
|
depositTree.pathElements[i] <== depositPathElements[i];
|
|
}
|
|
depositTree.root === depositRoot;
|
|
|
|
// Compute withdrawal commitment
|
|
component withdrawalHasher = Poseidon(3);
|
|
withdrawalHasher.inputs[0] <== instance;
|
|
withdrawalHasher.inputs[1] <== noteHasher.nullifierHash;
|
|
withdrawalHasher.inputs[2] <== withdrawalBlock;
|
|
|
|
// Verify that withdrawal commitment exists in the tree
|
|
component withdrawalTree = MerkleTree(levels);
|
|
withdrawalTree.leaf <== withdrawalHasher.out;
|
|
withdrawalTree.pathIndices <== withdrawalPathIndices;
|
|
for (var i = 0; i < levels; i++) {
|
|
withdrawalTree.pathElements[i] <== withdrawalPathElements[i];
|
|
}
|
|
withdrawalTree.root === withdrawalRoot;
|
|
|
|
// Compute reward nullifier
|
|
component rewardNullifierHasher = Poseidon(1);
|
|
rewardNullifierHasher.inputs[0] <== noteNullifier;
|
|
rewardNullifierHasher.out === rewardNullifier;
|
|
|
|
// Add hidden signals to make sure that tampering with recipient or fee will invalidate the snark proof
|
|
// Most likely it is not required, but it's better to stay on the safe side and it only takes 2 constraints
|
|
// Squares are used to prevent optimizer from removing those constraints
|
|
signal extDataHashSquare;
|
|
extDataHashSquare <== extDataHash * extDataHash;
|
|
}
|
|
|
|
// zeroLeaf = keccak256("tornado") % FIELD_SIZE
|
|
component main = Reward(20, 21663839004416932945382355908790599225266501822907911457504978515578255421292);
|