Skip to content

Commit ef60226

Browse files
authored
Adds VAnchorForest (#212)
1 parent 055a903 commit ef60226

32 files changed

+6162
-29
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
pragma circom 2.0.0;
2+
3+
include "../../node_modules/circomlib/circuits/bitify.circom";
4+
include "../../node_modules/circomlib/circuits/poseidon.circom";
5+
include "../../node_modules/circomlib/circuits/switcher.circom";
6+
include "../set/membership_if_enabled.circom";
7+
8+
9+
// Verifies that merkle proof is correct for given merkle root and a leaf
10+
// pathIndices bits is an array of 0/1 selectors telling whether given pathElement is on the left or right side of merkle path
11+
template GetMerkleRoot(levels) {
12+
signal input leaf;
13+
signal input pathElements[levels];
14+
signal input pathIndices;
15+
signal output root;
16+
17+
component switcher[levels];
18+
component hasher[levels];
19+
20+
component indexBits = Num2Bits(levels);
21+
indexBits.in <== pathIndices;
22+
23+
for (var i = 0; i < levels; i++) {
24+
switcher[i] = Switcher();
25+
switcher[i].L <== i == 0 ? leaf : hasher[i - 1].out;
26+
switcher[i].R <== pathElements[i];
27+
switcher[i].sel <== indexBits.out[i];
28+
29+
hasher[i] = Poseidon(2);
30+
hasher[i].inputs[0] <== switcher[i].outL;
31+
hasher[i].inputs[1] <== switcher[i].outR;
32+
}
33+
34+
// verify that the resultant hash (computed merkle root)
35+
// is in the set of roots
36+
root <== hasher[levels - 1].out;
37+
}
38+
39+
template MerkleForest(groupLevels, subtreeLevels, length) {
40+
signal input leaf;
41+
signal input pathElements[groupLevels];
42+
signal input pathIndices;
43+
signal input subtreePathElements[subtreeLevels];
44+
signal input subtreePathIndices;
45+
signal input roots[length];
46+
signal input isEnabled;
47+
48+
component getSubtreeMerkleRoot = GetMerkleRoot(subtreeLevels);
49+
getSubtreeMerkleRoot.leaf <== leaf;
50+
getSubtreeMerkleRoot.pathIndices <== subtreePathIndices;
51+
for (var i = 0; i < subtreeLevels; i++) {
52+
getSubtreeMerkleRoot.pathElements[i] <== subtreePathElements[i];
53+
}
54+
component getMerkleRoot = GetMerkleRoot(groupLevels);
55+
getMerkleRoot.leaf <== getSubtreeMerkleRoot.root;
56+
getMerkleRoot.pathIndices <== pathIndices;
57+
for (var i = 0; i < groupLevels; i++) {
58+
getMerkleRoot.pathElements[i] <== pathElements[i];
59+
}
60+
61+
component set = ForceSetMembershipIfEnabled(length);
62+
set.enabled <== isEnabled;
63+
for (var i = 0; i < length; i++) {
64+
set.set[i] <== roots[i];
65+
}
66+
set.element <== getMerkleRoot.root;
67+
}
68+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pragma circom 2.0.0;
2+
3+
include "../vanchor/transactionForest.circom";
4+
5+
// zeroLeaf = Poseidon(zero, zero)
6+
// default `zero` value is keccak256("tornado") % FIELD_SIZE = 21663839004416932945382355908790599225266501822907911457504978515578255421292
7+
component main {public [publicAmount, extDataHash, inputNullifier, outputCommitment, chainID, roots]} = TransactionForest(5, 30, 16, 2, 11850551329423159860688778991827824730037759162201783566284850822760196767874, 2);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pragma circom 2.0.0;
2+
3+
include "../vanchor/transactionForest.circom";
4+
5+
// zeroLeaf = Poseidon(zero, zero)
6+
// default `zero` value is keccak256("tornado") % FIELD_SIZE = 21663839004416932945382355908790599225266501822907911457504978515578255421292
7+
component main {public [publicAmount, extDataHash, inputNullifier, outputCommitment, chainID, roots]} = TransactionForest(5, 30, 16, 2, 11850551329423159860688778991827824730037759162201783566284850822760196767874, 8);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pragma circom 2.0.0;
2+
3+
include "../vanchor/transactionForest.circom";
4+
5+
// zeroLeaf = Poseidon(zero, zero)
6+
// default `zero` value is keccak256("tornado") % FIELD_SIZE = 21663839004416932945382355908790599225266501822907911457504978515578255421292
7+
component main {public [publicAmount, extDataHash, inputNullifier, outputCommitment, chainID, roots]} = TransactionForest(5, 30, 2, 2, 11850551329423159860688778991827824730037759162201783566284850822760196767874, 2);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pragma circom 2.0.0;
2+
3+
include "../vanchor/transactionForest.circom";
4+
5+
// zeroLeaf = Poseidon(zero, zero)
6+
// default `zero` value is keccak256("tornado") % FIELD_SIZE = 21663839004416932945382355908790599225266501822907911457504978515578255421292
7+
component main {public [publicAmount, extDataHash, inputNullifier, outputCommitment, chainID, roots]} = TransactionForest(5, 30, 2, 2, 11850551329423159860688778991827824730037759162201783566284850822760196767874, 8);
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
pragma circom 2.0.0;
2+
3+
include "../../node_modules/circomlib/circuits/poseidon.circom";
4+
include "../set/membership.circom";
5+
include "../merkle-tree/merkleForest.circom";
6+
include "./keypair.circom";
7+
8+
/*
9+
UTXO structure:
10+
{
11+
chainID, // destination chain identifier
12+
amount,
13+
pubkey,
14+
blinding, // random number
15+
}
16+
17+
commitment = hash(chainID, amount, pubKey, blinding)
18+
nullifier = hash(commitment, merklePath, sign(privKey, commitment, merklePath))
19+
*/
20+
21+
// Universal JoinSplit transaction with nIns inputs and 2 outputs (2-2 & 16-2)
22+
template TransactionForest(forestLevels, subtreeLevels, nIns, nOuts, zeroLeaf, length) {
23+
// extAmount = external amount used for deposits and withdrawals
24+
// correct extAmount range is enforced on the smart contract
25+
// publicAmount = extAmount - fee
26+
signal input publicAmount;
27+
signal input extDataHash; // arbitrary
28+
29+
// data for transaction inputs
30+
signal input inputNullifier[nIns];
31+
signal input inAmount[nIns];
32+
signal input inPrivateKey[nIns];
33+
signal input inBlinding[nIns];
34+
signal input subtreePathIndices[nIns];
35+
signal input subtreePathElements[nIns][subtreeLevels];
36+
signal input forestPathIndices[nIns];
37+
signal input forestPathElements[nIns][forestLevels];
38+
39+
// data for transaction outputs
40+
signal input outputCommitment[nOuts];
41+
signal input outChainID[nOuts];
42+
signal input outAmount[nOuts];
43+
signal input outPubkey[nOuts];
44+
signal input outBlinding[nOuts];
45+
46+
// roots for interoperability, one-of-many merkle membership proof
47+
signal input chainID;
48+
signal input roots[length];
49+
50+
component inKeypair[nIns];
51+
component inSignature[nIns];
52+
component inCommitmentHasher[nIns];
53+
component inNullifierHasher[nIns];
54+
component inTree[nIns];
55+
component inCheckRoot[nIns];
56+
var sumIns = 0;
57+
58+
// verify correctness of transaction inputs
59+
for (var tx = 0; tx < nIns; tx++) {
60+
inKeypair[tx] = Keypair();
61+
inKeypair[tx].privateKey <== inPrivateKey[tx];
62+
63+
inCommitmentHasher[tx] = Poseidon(4);
64+
inCommitmentHasher[tx].inputs[0] <== chainID;
65+
inCommitmentHasher[tx].inputs[1] <== inAmount[tx];
66+
inCommitmentHasher[tx].inputs[2] <== inKeypair[tx].publicKey;
67+
inCommitmentHasher[tx].inputs[3] <== inBlinding[tx];
68+
69+
inSignature[tx] = Signature();
70+
inSignature[tx].privateKey <== inPrivateKey[tx];
71+
inSignature[tx].commitment <== inCommitmentHasher[tx].out;
72+
inSignature[tx].merklePath <== subtreePathIndices[tx];
73+
74+
inNullifierHasher[tx] = Poseidon(3);
75+
inNullifierHasher[tx].inputs[0] <== inCommitmentHasher[tx].out;
76+
inNullifierHasher[tx].inputs[1] <== subtreePathIndices[tx];
77+
inNullifierHasher[tx].inputs[2] <== inSignature[tx].out;
78+
inNullifierHasher[tx].out === inputNullifier[tx];
79+
80+
inTree[tx] = MerkleForest(forestLevels, subtreeLevels, length);
81+
inTree[tx].leaf <== inCommitmentHasher[tx].out;
82+
inTree[tx].subtreePathIndices <== subtreePathIndices[tx];
83+
inTree[tx].pathIndices <== forestPathIndices[tx];
84+
85+
// add the roots and diffs signals to the bridge circuit
86+
for (var i = 0; i < length; i++) {
87+
inTree[tx].roots[i] <== roots[i];
88+
}
89+
90+
inTree[tx].isEnabled <== inAmount[tx];
91+
for (var i = 0; i < subtreeLevels; i++) {
92+
inTree[tx].subtreePathElements[i] <== subtreePathElements[tx][i];
93+
}
94+
for (var i = 0; i < forestLevels; i++) {
95+
inTree[tx].pathElements[i] <== forestPathElements[tx][i];
96+
}
97+
98+
// We don't need to range check input amounts, since all inputs are valid UTXOs that
99+
// were already checked as outputs in the previous transaction (or zero amount UTXOs that don't
100+
// need to be checked either).
101+
sumIns += inAmount[tx];
102+
}
103+
104+
component outCommitmentHasher[nOuts];
105+
component outAmountCheck[nOuts];
106+
var sumOuts = 0;
107+
108+
// verify correctness of transaction outputs
109+
for (var tx = 0; tx < nOuts; tx++) {
110+
outCommitmentHasher[tx] = Poseidon(4);
111+
outCommitmentHasher[tx].inputs[0] <== outChainID[tx];
112+
outCommitmentHasher[tx].inputs[1] <== outAmount[tx];
113+
outCommitmentHasher[tx].inputs[2] <== outPubkey[tx];
114+
outCommitmentHasher[tx].inputs[3] <== outBlinding[tx];
115+
outCommitmentHasher[tx].out === outputCommitment[tx];
116+
117+
// Check that amount fits into 248 bits to prevent overflow
118+
outAmountCheck[tx] = Num2Bits(248);
119+
outAmountCheck[tx].in <== outAmount[tx];
120+
121+
sumOuts += outAmount[tx];
122+
}
123+
124+
// check that there are no same nullifiers among all inputs
125+
component sameNullifiers[nIns * (nIns - 1) / 2];
126+
var index = 0;
127+
for (var i = 0; i < nIns - 1; i++) {
128+
for (var j = i + 1; j < nIns; j++) {
129+
sameNullifiers[index] = IsEqual();
130+
sameNullifiers[index].in[0] <== inputNullifier[i];
131+
sameNullifiers[index].in[1] <== inputNullifier[j];
132+
sameNullifiers[index].out === 0;
133+
index++;
134+
}
135+
}
136+
137+
// verify amount invariant
138+
sumIns + publicAmount === sumOuts;
139+
140+
// optional safety constraint to make sure extDataHash cannot be changed
141+
signal extDataSquare;
142+
extDataSquare <== extDataHash * extDataHash;
143+
}

packages/anchors/src/IdentityVAnchor.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,6 @@ export class IdentityVAnchor implements IAnchor {
502502
};
503503
}
504504

505-
// TODO parameterize this better
506505
public generatePublicInputs(
507506
proof: any,
508507
byte_calldata: any,

0 commit comments

Comments
 (0)