@maci-protocol/circuits 0.0.0-ci.85bba2d → 0.0.0-ci.86ec64f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +2 -2
  2. package/build/ts/types.d.ts +7 -7
  3. package/build/ts/types.d.ts.map +1 -1
  4. package/build/tsconfig.build.tsbuildinfo +1 -1
  5. package/circom/circuits.json +28 -12
  6. package/circom/coordinator/full/MessageProcessor.circom +253 -0
  7. package/circom/coordinator/full/SingleMessageProcessor.circom +203 -0
  8. package/circom/coordinator/non-qv/{processMessages.circom → MessageProcessor.circom} +12 -206
  9. package/circom/coordinator/non-qv/SingleMessageProcessor.circom +199 -0
  10. package/circom/coordinator/non-qv/{tallyVotes.circom → VoteTally.circom} +22 -99
  11. package/circom/coordinator/qv/{processMessages.circom → MessageProcessor.circom} +13 -214
  12. package/circom/coordinator/qv/SingleMessageProcessor.circom +207 -0
  13. package/circom/coordinator/qv/VoteTally.circom +179 -0
  14. package/circom/coordinator/qv/VoteTallyWithIndividualCounts.circom +226 -0
  15. package/circom/utils/CalculateTotal.circom +6 -6
  16. package/circom/utils/PrivateToPublicKey.circom +3 -3
  17. package/circom/utils/full/MessageValidator.circom +91 -0
  18. package/circom/utils/full/StateLeafAndBallotTransformer.circom +122 -0
  19. package/circom/utils/non-qv/MessageValidator.circom +4 -4
  20. package/circom/utils/non-qv/ResultCommitmentVerifier.circom +84 -0
  21. package/circom/utils/non-qv/StateLeafAndBallotTransformer.circom +7 -7
  22. package/circom/utils/qv/MessageValidator.circom +4 -4
  23. package/circom/utils/qv/ResultCommitmentVerifier.circom +107 -0
  24. package/circom/utils/qv/StateLeafAndBallotTransformer.circom +7 -7
  25. package/circom/utils/trees/BinaryMerkleRoot.circom +6 -3
  26. package/circom/utils/trees/LeafExists.circom +2 -2
  27. package/circom/utils/trees/MerkleTreeInclusionProof.circom +4 -4
  28. package/circom/utils/trees/QuinaryCheckRoot.circom +54 -0
  29. package/circom/utils/trees/{MerklePathIndicesGenerator.circom → QuinaryGeneratePathIndices.circom} +18 -18
  30. package/circom/utils/trees/QuinaryLeafExists.circom +30 -0
  31. package/circom/utils/trees/QuinarySelector.circom +42 -0
  32. package/circom/utils/trees/QuinaryTreeInclusionProof.circom +55 -0
  33. package/circom/utils/trees/Splicer.circom +76 -0
  34. package/circom/voter/PollJoined.circom +5 -5
  35. package/circom/voter/PollJoining.circom +3 -3
  36. package/package.json +15 -13
  37. package/circom/coordinator/qv/tallyVotes.circom +0 -279
  38. package/circom/utils/trees/incrementalQuinaryTree.circom +0 -287
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maci-protocol/circuits",
3
- "version": "0.0.0-ci.85bba2d",
3
+ "version": "0.0.0-ci.86ec64f",
4
4
  "private": false,
5
5
  "description": "zk-SNARK circuits for MACI",
6
6
  "main": "build/ts/index.js",
@@ -31,42 +31,44 @@
31
31
  "test:poseidonHasher": "pnpm run mocha-test ts/__tests__/PoseidonHasher.test.ts",
32
32
  "test:messageHasher": "pnpm run mocha-test ts/__tests__/MessageHasher.test.ts",
33
33
  "test:slAndBallotTransformer": "pnpm run mocha-test ts/__tests__/StateLeafAndBallotTransformer.test.ts",
34
+ "test:slAndBallotTransformerFull": "pnpm run mocha-test ts/__tests__/StateLeafAndBallotTransformerFull.test.ts",
34
35
  "test:messageToCommand": "pnpm run mocha-test ts/__tests__/MessageToCommand.test.ts",
35
36
  "test:messageValidator": "pnpm run mocha-test ts/__tests__/MessageValidator.test.ts",
37
+ "test:messageValidatorFull": "pnpm run mocha-test ts/__tests__/MessageValidatorFull.test.ts",
36
38
  "test:verifySignature": "pnpm run mocha-test ts/__tests__/VerifySignature.test.ts",
37
39
  "test:privateToPublicKey": "pnpm run mocha-test ts/__tests__/PrivateToPublicKey.test.ts",
38
40
  "test:calculateTotal": "pnpm run mocha-test ts/__tests__/CalculateTotal.test.ts",
39
- "test:processMessages": "pnpm run mocha-test ts/__tests__/ProcessMessages.test.ts",
40
- "test:tallyVotes": "pnpm run mocha-test ts/__tests__/TallyVotes.test.ts",
41
+ "test:messageProcessor": "pnpm run mocha-test ts/__tests__/MessageProcessor.test.ts",
42
+ "test:voteTally": "pnpm run mocha-test ts/__tests__/VoteTally.test.ts",
41
43
  "test:ceremonyParams": "pnpm run mocha-test ts/__tests__/CeremonyParams.test.ts",
42
44
  "test:incrementalQuinaryTree": "pnpm run mocha-test ts/__tests__/IncrementalQuinaryTree.test.ts",
43
45
  "test:pollJoining": "pnpm run mocha-test ts/__tests__/PollJoining.test.ts",
44
46
  "test:pollJoined": "pnpm run mocha-test ts/__tests__/PollJoined.test.ts"
45
47
  },
46
48
  "dependencies": {
47
- "@maci-protocol/core": "0.0.0-ci.85bba2d",
48
- "@maci-protocol/crypto": "0.0.0-ci.85bba2d",
49
- "@maci-protocol/domainobjs": "0.0.0-ci.85bba2d",
50
- "@maci-protocol/sdk": "0.0.0-ci.85bba2d",
49
+ "@maci-protocol/core": "0.0.0-ci.86ec64f",
50
+ "@maci-protocol/crypto": "0.0.0-ci.86ec64f",
51
+ "@maci-protocol/domainobjs": "0.0.0-ci.86ec64f",
52
+ "@maci-protocol/sdk": "0.0.0-ci.86ec64f",
51
53
  "@zk-kit/circuits": "^0.4.0",
52
- "circomkit": "^0.3.2",
54
+ "circomkit": "^0.3.4",
53
55
  "circomlib": "^2.0.5"
54
56
  },
55
57
  "devDependencies": {
56
58
  "@types/chai": "^4.3.11",
57
59
  "@types/chai-as-promised": "^7.1.8",
58
60
  "@types/mocha": "^10.0.10",
59
- "@types/node": "^22.14.1",
61
+ "@types/node": "^24.2.0",
60
62
  "@types/snarkjs": "^0.7.9",
61
63
  "@zk-kit/baby-jubjub": "^1.0.3",
62
64
  "chai": "^4.3.10",
63
65
  "chai-as-promised": "^7.1.2",
64
- "fast-check": "^4.1.1",
65
- "glob": "^11.0.1",
66
- "mocha": "^11.1.0",
66
+ "fast-check": "^4.2.0",
67
+ "glob": "^11.0.3",
68
+ "mocha": "^11.7.1",
67
69
  "ts-mocha": "^11.1.0",
68
70
  "ts-node": "^10.9.1",
69
71
  "typescript": "^5.8.3"
70
72
  },
71
- "gitHead": "929b02ec5a5ec745a0e2b592b4d8e867bad7ac1a"
73
+ "gitHead": "c60edfc68d77f84d132590938967d6ed651a591a"
72
74
  }
@@ -1,279 +0,0 @@
1
- pragma circom 2.0.0;
2
-
3
- // circomlib import
4
- include "./comparators.circom";
5
- // zk-kit import
6
- include "./unpack-element.circom";
7
- // local imports
8
- include "../../utils/trees/CheckRoot.circom";
9
- include "../../utils/trees/MerklePathIndicesGenerator.circom";
10
- include "../../utils/trees/LeafExists.circom";
11
- include "../../utils/trees/incrementalQuinaryTree.circom";
12
- include "../../utils/CalculateTotal.circom";
13
- include "../../utils/PoseidonHasher.circom";
14
-
15
- /**
16
- * Processes batches of votes and verifies their validity in a Merkle tree structure.
17
- * This template supports Quadratic Voting (QV).
18
- */
19
- template TallyVotes(
20
- stateTreeDepth,
21
- intStateTreeDepth,
22
- voteOptionTreeDepth
23
- ) {
24
- // Ensure there's at least one level in the vote option tree.
25
- assert(voteOptionTreeDepth > 0);
26
- // Ensure the intermediate state tree has at least one level.
27
- assert(intStateTreeDepth > 0);
28
- // The intermediate state tree must be smaller than the full state tree.
29
- assert(intStateTreeDepth < stateTreeDepth);
30
-
31
- // Number of children per node in the tree, defining the tree's branching factor.
32
- var TREE_ARITY = 5;
33
- var BALLOT_TREE_ARITY = 2;
34
-
35
- // The number of ballots processed at once, determined by the depth of the intermediate state tree.
36
- var batchSize = BALLOT_TREE_ARITY ** intStateTreeDepth;
37
- // Number of voting options available, determined by the depth of the vote option tree.
38
- var totalVoteOptions = TREE_ARITY ** voteOptionTreeDepth;
39
-
40
- // Number of elements in each ballot.
41
- var BALLOT_LENGTH = 2;
42
- // Index for the nonce in the ballot array.
43
- var BALLOT_NONCE_INDEX = 0;
44
- // Index for the voting option root in the ballot array.
45
- var BALLOT_VOTE_OPTION_ROOT_INDEX = 1;
46
- // Difference in tree depths, used in path calculations.
47
- var STATE_INT_TREE_DEPTH_DIFFERENCE = stateTreeDepth - intStateTreeDepth;
48
-
49
- // Root of the state Merkle tree, representing the overall state before voting.
50
- signal input stateRoot;
51
- // Root of the ballot Merkle tree, representing the submitted ballots.
52
- signal input ballotRoot;
53
- // Salt used in commitment to secure the ballot data.
54
- signal input sbSalt;
55
- // Commitment to the state and ballots.
56
- signal input sbCommitment;
57
- // Commitment to the current tally before this batch.
58
- signal input currentTallyCommitment;
59
- // Commitment to the new tally after processing this batch.
60
- signal input newTallyCommitment;
61
- // Start index of given batch
62
- signal input index;
63
- // Number of users that signup
64
- signal input totalSignups;
65
- // Ballots and their corresponding path elements for verification in the tree.
66
- signal input ballots[batchSize][BALLOT_LENGTH];
67
- signal input ballotPathElements[STATE_INT_TREE_DEPTH_DIFFERENCE][BALLOT_TREE_ARITY - 1];
68
- signal input votes[batchSize][totalVoteOptions];
69
- // Current results for each vote option.
70
- signal input currentResults[totalVoteOptions];
71
- // Salt for the root of the current results.
72
- signal input currentResultsRootSalt;
73
- // Total voice credits spent so far.
74
- signal input currentSpentVoiceCreditSubtotal;
75
- // Salt for the total spent voice credits.
76
- signal input currentSpentVoiceCreditSubtotalSalt;
77
- // Spent voice credits per vote option.
78
- signal input currentPerVOSpentVoiceCredits[totalVoteOptions];
79
- // Salt for the root of spent credits per option.
80
- signal input currentPerVOSpentVoiceCreditsRootSalt;
81
- // Salt for the root of the new results.
82
- signal input newResultsRootSalt;
83
- // Salt for the new spent credits per vote option root.
84
- signal input newPerVOSpentVoiceCreditsRootSalt;
85
- // Salt for the new total spent voice credits root.
86
- signal input newSpentVoiceCreditSubtotalSalt;
87
-
88
- // Verify sbCommitment.
89
- var computedSbCommitment = PoseidonHasher(3)([stateRoot, ballotRoot, sbSalt]);
90
- computedSbCommitment === sbCommitment;
91
-
92
- // Validates that the index is within the valid range of sign-ups.
93
- var totalSignupsValid = LessEqThan(50)([index, totalSignups]);
94
- totalSignupsValid === 1;
95
-
96
- // Hashes each ballot for subroot generation, and checks the existence of the leaf in the Merkle tree.
97
- var computedBallotHashers[batchSize];
98
-
99
- for (var i = 0; i < batchSize; i++) {
100
- computedBallotHashers[i] = PoseidonHasher(2)([ballots[i][BALLOT_NONCE_INDEX], ballots[i][BALLOT_VOTE_OPTION_ROOT_INDEX]]);
101
- }
102
-
103
- var computedBallotSubroot = CheckRoot(intStateTreeDepth)(computedBallotHashers);
104
- var computedBallotPathIndices[STATE_INT_TREE_DEPTH_DIFFERENCE] = MerklePathIndicesGenerator(STATE_INT_TREE_DEPTH_DIFFERENCE)(index / batchSize);
105
-
106
- // Verifies each ballot's existence within the ballot tree.
107
- LeafExists(STATE_INT_TREE_DEPTH_DIFFERENCE)(
108
- computedBallotSubroot,
109
- ballotPathElements,
110
- computedBallotPathIndices,
111
- ballotRoot
112
- );
113
-
114
- // Processes vote options, verifying each against its declared root.
115
- var computedVoteTree[batchSize];
116
- for (var i = 0; i < batchSize; i++) {
117
- computedVoteTree[i] = QuinCheckRoot(voteOptionTreeDepth)(votes[i]);
118
- computedVoteTree[i] === ballots[i][BALLOT_VOTE_OPTION_ROOT_INDEX];
119
- }
120
-
121
- // Calculates new results and spent voice credits based on the current and incoming votes.
122
- var computedIsFirstBatch = IsZero()(index);
123
- var computedIsZero = IsZero()(computedIsFirstBatch);
124
-
125
- // Tally the new results.
126
- var computedCalculateTotalResult[totalVoteOptions];
127
- for (var i = 0; i < totalVoteOptions; i++) {
128
- var numsRC[batchSize + 1];
129
- numsRC[batchSize] = currentResults[i] * computedIsZero;
130
- for (var j = 0; j < batchSize; j++) {
131
- numsRC[j] = votes[j][i];
132
- }
133
-
134
- computedCalculateTotalResult[i] = CalculateTotal(batchSize + 1)(numsRC);
135
- }
136
-
137
- // Tally the new spent voice credit total.
138
- var numsSVC[batchSize * totalVoteOptions + 1];
139
- numsSVC[batchSize * totalVoteOptions] = currentSpentVoiceCreditSubtotal * computedIsZero;
140
- for (var i = 0; i < batchSize; i++) {
141
- for (var j = 0; j < totalVoteOptions; j++) {
142
- numsSVC[i * totalVoteOptions + j] = votes[i][j] * votes[i][j];
143
- }
144
- }
145
-
146
- var computedNewSpentVoiceCreditSubtotal = CalculateTotal(batchSize * totalVoteOptions + 1)(numsSVC);
147
-
148
- // Tally the spent voice credits per vote option.
149
- var computedNewPerVOSpentVoiceCredits[totalVoteOptions];
150
-
151
- for (var i = 0; i < totalVoteOptions; i++) {
152
- var computedNumsSVC[batchSize + 1];
153
- computedNumsSVC[batchSize] = currentPerVOSpentVoiceCredits[i] * computedIsZero;
154
- for (var j = 0; j < batchSize; j++) {
155
- computedNumsSVC[j] = votes[j][i] * votes[j][i];
156
- }
157
-
158
- computedNewPerVOSpentVoiceCredits[i] = CalculateTotal(batchSize + 1)(computedNumsSVC);
159
- }
160
-
161
- // Verifies the updated results and spent credits, ensuring consistency and correctness of tally updates.
162
- ResultCommitmentVerifier(voteOptionTreeDepth)(
163
- computedIsFirstBatch,
164
- currentTallyCommitment,
165
- newTallyCommitment,
166
- currentResults,
167
- currentResultsRootSalt,
168
- computedCalculateTotalResult,
169
- newResultsRootSalt,
170
- currentSpentVoiceCreditSubtotal,
171
- currentSpentVoiceCreditSubtotalSalt,
172
- computedNewSpentVoiceCreditSubtotal,
173
- newSpentVoiceCreditSubtotalSalt,
174
- currentPerVOSpentVoiceCredits,
175
- currentPerVOSpentVoiceCreditsRootSalt,
176
- computedNewPerVOSpentVoiceCredits,
177
- newPerVOSpentVoiceCreditsRootSalt
178
- );
179
- }
180
-
181
- /**
182
- * Performs verifications and computations related to current voting results.
183
- * Also, computes and outputs a commitment to the new results.
184
- * This template supports the Quadratic Voting (QV).
185
- */
186
- template ResultCommitmentVerifier(voteOptionTreeDepth) {
187
- // Number of children per node in the tree, defining the tree's branching factor.
188
- var TREE_ARITY = 5;
189
- // Number of voting options available, determined by the depth of the vote option tree.
190
- var totalVoteOptions = TREE_ARITY ** voteOptionTreeDepth;
191
-
192
- // Equal to 1 if this is the first batch, otherwise 0.
193
- signal input isFirstBatch;
194
- // Commitment to the current tally before this batch.
195
- signal input currentTallyCommitment;
196
- // Commitment to the new tally after processing this batch.
197
- signal input newTallyCommitment;
198
-
199
- // Current results for each vote option.
200
- signal input currentResults[totalVoteOptions];
201
- // Salt for the root of the current results.
202
- signal input currentResultsRootSalt;
203
-
204
- // New results for each vote option.
205
- signal input newResults[totalVoteOptions];
206
- // Salt for the root of the new results.
207
- signal input newResultsRootSalt;
208
-
209
- // Total voice credits spent so far.
210
- signal input currentSpentVoiceCreditSubtotal;
211
- // Salt for the total spent voice credits.
212
- signal input currentSpentVoiceCreditSubtotalSalt;
213
-
214
- // Total new voice credits spent.
215
- signal input newSpentVoiceCreditSubtotal;
216
- // Salt for the new total spent voice credits.
217
- signal input newSpentVoiceCreditSubtotalSalt;
218
-
219
- // Spent voice credits per vote option.
220
- signal input currentPerVOSpentVoiceCredits[totalVoteOptions];
221
- // Salt for the root of spent credits per option.
222
- signal input currentPerVOSpentVoiceCreditsRootSalt;
223
-
224
- // New spent voice credits per vote option.
225
- signal input newPerVOSpentVoiceCredits[totalVoteOptions];
226
- // Salt for the root of new spent credits per option.
227
- signal input newPerVOSpentVoiceCreditsRootSalt;
228
-
229
- // Compute the commitment to the current results.
230
- var computedCurrentResultsRoot = QuinCheckRoot(voteOptionTreeDepth)(currentResults);
231
-
232
- // Verify currentResultsCommitmentHash.
233
- var computedCurrentResultsCommitment = PoseidonHasher(2)([computedCurrentResultsRoot, currentResultsRootSalt]);
234
-
235
- // Compute the commitment to the current spent voice credits.
236
- var computedCurrentSpentVoiceCreditsCommitment = PoseidonHasher(2)([currentSpentVoiceCreditSubtotal, currentSpentVoiceCreditSubtotalSalt]);
237
-
238
- // Compute the root of the spent voice credits per vote option.
239
- var computedCurrentPerVOSpentVoiceCreditsRoot = QuinCheckRoot(voteOptionTreeDepth)(currentPerVOSpentVoiceCredits);
240
- var computedCurrentPerVOSpentVoiceCreditsCommitment = PoseidonHasher(2)([computedCurrentPerVOSpentVoiceCreditsRoot, currentPerVOSpentVoiceCreditsRootSalt]);
241
-
242
- // Commit to the current tally.
243
- var computedCurrentTallyCommitment = PoseidonHasher(3)([
244
- computedCurrentResultsCommitment,
245
- computedCurrentSpentVoiceCreditsCommitment,
246
- computedCurrentPerVOSpentVoiceCreditsCommitment
247
- ]);
248
-
249
- // Check if the current tally commitment is correct only if this is not the first batch.
250
- // computedIsZero.out is 1 if this is not the first batch.
251
- // computedIsZero.out is 0 if this is the first batch.
252
- var computedIsZero = IsZero()(isFirstBatch);
253
-
254
- // isFirstCommitment is 0 if this is the first batch, currentTallyCommitment should be 0 if this is the first batch.
255
- // isFirstCommitment is 1 if this is not the first batch, currentTallyCommitment should not be 0 if this is the first batch.
256
- signal isFirstCommitment;
257
- isFirstCommitment <== computedIsZero * computedCurrentTallyCommitment;
258
- isFirstCommitment === currentTallyCommitment;
259
-
260
- // Compute the root of the new results.
261
- var computedNewResultsRoot = QuinCheckRoot(voteOptionTreeDepth)(newResults);
262
- var computedNewResultsCommitment = PoseidonHasher(2)([computedNewResultsRoot, newResultsRootSalt]);
263
-
264
- // Compute the commitment to the new spent voice credits value.
265
- var computedNewSpentVoiceCreditsCommitment = PoseidonHasher(2)([newSpentVoiceCreditSubtotal, newSpentVoiceCreditSubtotalSalt]);
266
-
267
- // Compute the root of the spent voice credits per vote option.
268
- var computedNewPerVOSpentVoiceCreditsRoot = QuinCheckRoot(voteOptionTreeDepth)(newPerVOSpentVoiceCredits);
269
- var computedNewPerVOSpentVoiceCreditsCommitment = PoseidonHasher(2)([computedNewPerVOSpentVoiceCreditsRoot, newPerVOSpentVoiceCreditsRootSalt]);
270
-
271
- // Commit to the new tally.
272
- var computedNewTallyCommitment = PoseidonHasher(3)([
273
- computedNewResultsCommitment,
274
- computedNewSpentVoiceCreditsCommitment,
275
- computedNewPerVOSpentVoiceCreditsCommitment
276
- ]);
277
-
278
- computedNewTallyCommitment === newTallyCommitment;
279
- }
@@ -1,287 +0,0 @@
1
- pragma circom 2.0.0;
2
-
3
- // circomlib imports
4
- include "./bitify.circom";
5
- include "./mux1.circom";
6
- // zk-kit import
7
- include "./safe-comparators.circom";
8
- // local imports
9
- include "../CalculateTotal.circom";
10
- include "../PoseidonHasher.circom";
11
-
12
- // Incremental Quintary Merkle Tree (IQT) verification circuits.
13
- // Since each node contains 5 leaves, we are using PoseidonT6 for hashing them.
14
- //
15
- // nb. circom has some particularities which limit the code patterns we can use:
16
- // - You can only assign a value to a signal once.
17
- // - A component's input signal must only be wired to another component's output signal.
18
- // - Variables can store linear combinations, and can also be used for loops,
19
- // declaring sizes of things, and anything that is not related to inputs of a circuit.
20
- // - The compiler fails whenever you try to mix invalid elements.
21
- // - You can't use a signal as a list index.
22
-
23
- /**
24
- * Selects an item from a list based on the given index.
25
- * It verifies the index is within the valid range and then iterates over the inputs to find the match.
26
- * For each item, it checks if its position equals the given index and if so, multiplies the item
27
- * by the result of the equality check, effectively selecting it.
28
- * The sum of these results yields the selected item, ensuring only the item at the specified index be the output.
29
- *
30
- * nb. The number of items must be less than 8, and the index must be less than the number of items.
31
- */
32
- template QuinSelector(choices) {
33
- signal input in[choices];
34
- signal input index;
35
- signal output out;
36
-
37
- // Ensure that index < choices.
38
- var computedLtIndex = SafeLessThan(3)([index, choices]);
39
- computedLtIndex === 1;
40
-
41
- // Initialize an array to hold the results of equality checks.
42
- var computedResults[choices];
43
-
44
- // For each item, check whether its index equals the input index.
45
- // The result is multiplied by the corresponding input value.
46
- for (var i = 0; i < choices; i++) {
47
- var computedIsIndexEqual = IsEqual()([i, index]);
48
-
49
- computedResults[i] = computedIsIndexEqual * in[i];
50
- }
51
-
52
- // Calculate the total sum of the results array.
53
- out <== CalculateTotal(choices)(computedResults);
54
- }
55
-
56
- /**
57
- * The output array contains the input items, with the leaf inserted at the
58
- * specified index. For example, if input = [0, 20, 30, 40], index = 3, and
59
- * leaf = 10, the output will be [0, 20, 30, 10, 40].
60
- */
61
- template Splicer(numItems) {
62
- // The number of output items (because only one item is inserted).
63
- var NUM_OUTPUT_ITEMS = numItems + 1;
64
-
65
- signal input in[numItems];
66
- signal input leaf;
67
- signal input index;
68
- signal output out[NUM_OUTPUT_ITEMS];
69
-
70
- // There is a loop where the goal is to assign values to the output signal.
71
- //
72
- // | output[0] | output[1] | output[2] | ...
73
- //
74
- // We can either assign the leaf, or an item from the `items` signal, to the output, using Mux1().
75
- // The Mux1's selector is 0 or 1 depending on whether the index is equal to the loop counter.
76
- //
77
- // i --> [IsEqual] <-- index
78
- // |
79
- // v
80
- // leaf --> [Mux1] <-- <item from in>
81
- // |
82
- // v
83
- // output[m]
84
- //
85
- // To obtain the value from <item from in>, we need to compute an item
86
- // index (let it be `s`).
87
- // 1. if index = 2 and i = 0, then s = 0
88
- // 2. if index = 2 and i = 1, then s = 1
89
- // 3. if index = 2 and i = 2, then s = 2
90
- // 4. if index = 2 and i = 3, then s = 2
91
- // 5. if index = 2 and i = 4, then s = 3
92
- // We then wire `s`, as well as each item in `in` to a QuinSelector.
93
- // The output signal from the QuinSelector is <item from in> and gets
94
- // wired to Mux1 (as above).
95
-
96
- var inputs[NUM_OUTPUT_ITEMS];
97
-
98
- for (var i = 0; i < numItems; i++) {
99
- inputs[i] = in[i];
100
- }
101
- inputs[NUM_OUTPUT_ITEMS - 1] = 0;
102
-
103
- for (var i = 0; i < NUM_OUTPUT_ITEMS; i++) {
104
- // Determines if current index is greater than the insertion index.
105
- var computedIsIndexAfterInsertPoint = SafeGreaterThan(3)([i, index]);
106
-
107
- // Calculates correct index for original items, adjusting for leaf insertion.
108
- var computedAdjustedIndex = i - computedIsIndexAfterInsertPoint;
109
-
110
- // Selects item from the original array or the leaf for insertion.
111
- var computedQuinSelected = QuinSelector(NUM_OUTPUT_ITEMS)(inputs, computedAdjustedIndex);
112
- var computedIsIndexEqual = IsEqual()([index, i]);
113
- var mux = Mux1()([computedQuinSelected, leaf], computedIsIndexEqual);
114
-
115
- out[i] <== mux;
116
- }
117
- }
118
-
119
- /**
120
- * Computes the root of an IQT given a leaf, its path, and sibling nodes at each level of the tree.
121
- * It iteratively incorporates the leaf or the hash from the previous level with sibling nodes using
122
- * the Splicer to place the leaf or hash at the correct position based on path_index.
123
- * Then, it hashes these values together with PoseidonHasher to move up the tree.
124
- * This process repeats for each level (levels) of the tree, culminating in the computation of the tree's root.
125
- */
126
- template QuinTreeInclusionProof(levels) {
127
- var LEAVES_PER_NODE = 5;
128
- var LEAVES_PER_PATH_LEVEL = LEAVES_PER_NODE - 1;
129
-
130
- signal input leaf;
131
- signal input path_index[levels];
132
- signal input path_elements[levels][LEAVES_PER_PATH_LEVEL];
133
- signal output root;
134
-
135
- var currentLeaf = leaf;
136
-
137
- // Iteratively hash each level of path_elements with the leaf or previous hash
138
- for (var i = 0; i < levels; i++) {
139
- var elements[LEAVES_PER_PATH_LEVEL];
140
-
141
- for (var j = 0; j < LEAVES_PER_PATH_LEVEL; j++) {
142
- elements[j] = path_elements[i][j];
143
- }
144
-
145
- var computedSplicedLeaf[LEAVES_PER_NODE] = Splicer(LEAVES_PER_PATH_LEVEL)(
146
- elements,
147
- currentLeaf,
148
- path_index[i]
149
- );
150
-
151
- currentLeaf = PoseidonHasher(5)([
152
- computedSplicedLeaf[0],
153
- computedSplicedLeaf[1],
154
- computedSplicedLeaf[2],
155
- computedSplicedLeaf[3],
156
- computedSplicedLeaf[4]
157
- ]);
158
- }
159
-
160
- root <== currentLeaf;
161
- }
162
-
163
- /**
164
- * Verifies if a given leaf exists within an IQT.
165
- * Takes a leaf, its path to the root (specified by indices and path elements),
166
- * and the root itself, to verify the leaf's inclusion within the tree.
167
- */
168
- template QuinLeafExists(levels){
169
- var LEAVES_PER_NODE = 5;
170
- var LEAVES_PER_PATH_LEVEL = LEAVES_PER_NODE - 1;
171
-
172
- signal input leaf;
173
- signal input path_index[levels];
174
- signal input path_elements[levels][LEAVES_PER_PATH_LEVEL];
175
- signal input root;
176
-
177
- // Verify the Merkle path.
178
- var computedRoot = QuinTreeInclusionProof(levels)(leaf, path_index, path_elements);
179
-
180
- root === computedRoot;
181
- }
182
-
183
- /**
184
- * Checks if a list of leaves exists within an IQT, leveraging the PoseidonT6
185
- * circuit for hashing. This can be used to verify the presence of multiple leaves.
186
- */
187
- template QuinBatchLeavesExists(levels, batchLevels) {
188
- var LEAVES_PER_NODE = 5;
189
- var LEAVES_PER_PATH_LEVEL = LEAVES_PER_NODE - 1;
190
- var LEAVES_PER_BATCH = LEAVES_PER_NODE ** batchLevels;
191
-
192
- signal input root;
193
- signal input leaves[LEAVES_PER_BATCH];
194
- signal input path_index[levels - batchLevels];
195
- signal input path_elements[levels - batchLevels][LEAVES_PER_PATH_LEVEL];
196
-
197
- // Compute the subroot (= leaf).
198
- var computedQuinSubroot = QuinCheckRoot(batchLevels)(leaves);
199
-
200
- // Check if the Merkle path is valid
201
- QuinLeafExists(levels - batchLevels)(computedQuinSubroot, path_index, path_elements, root);
202
- }
203
-
204
- /**
205
- * Calculates the path indices required for Merkle proof verifications (e.g., QuinTreeInclusionProof, QuinLeafExists).
206
- * Given a node index within an IQT and the total tree levels, it outputs the path indices leading to that node.
207
- * The template handles the modulo and division operations to break down the tree index into its constituent path indices.
208
- * e.g., if the index is 30 and the number of levels is 4, the output should be [0, 1, 1, 0].
209
- */
210
- template QuinGeneratePathIndices(levels) {
211
- var BASE = 5;
212
-
213
- signal input in;
214
- signal output out[levels];
215
-
216
- var m = in;
217
- var computedResults[levels];
218
-
219
- for (var i = 0; i < levels; i++) {
220
- // circom's best practices suggests to avoid using <-- unless you
221
- // are aware of what's going on. This is the only way to do modulo operation.
222
- out[i] <-- m % BASE;
223
- m = m \ BASE;
224
-
225
- // Check that each output element is less than the base.
226
- var computedIsOutputElementLessThanBase = SafeLessThan(3)([out[i], BASE]);
227
- computedIsOutputElementLessThanBase === 1;
228
-
229
- // Re-compute the total sum.
230
- computedResults[i] = out[i] * (BASE ** i);
231
- }
232
-
233
- // Check that the total sum matches the index.
234
- var computedCalculateTotal = CalculateTotal(levels)(computedResults);
235
-
236
- computedCalculateTotal === in;
237
- }
238
-
239
- /**
240
- * Computes the root of a quintary Merkle tree given a list of leaves.
241
- * This template constructs a Merkle tree with each node having 5 children (quintary)
242
- * and computes the root by hashing with Poseidon the leaves and intermediate nodes in the given order.
243
- * The computation is performed by first hashing groups of 5 leaves to form the bottom layer of nodes,
244
- * then recursively hashing groups of these nodes to form the next layer, and so on, until the root is computed.
245
- */
246
- template QuinCheckRoot(levels) {
247
- var LEAVES_PER_NODE = 5;
248
- var totalLeaves = LEAVES_PER_NODE ** levels;
249
- var numLeafHashers = LEAVES_PER_NODE ** (levels - 1);
250
-
251
- signal input leaves[totalLeaves];
252
- signal output root;
253
-
254
- // Determine the total number of hashers.
255
- var numHashers = 0;
256
- for (var i = 0; i < levels; i++) {
257
- numHashers += LEAVES_PER_NODE ** i;
258
- }
259
-
260
- var computedHashers[numHashers];
261
-
262
- // Initialize hashers for the leaves.
263
- for (var i = 0; i < numLeafHashers; i++) {
264
- computedHashers[i] = PoseidonHasher(5)([
265
- leaves[i * LEAVES_PER_NODE + 0],
266
- leaves[i * LEAVES_PER_NODE + 1],
267
- leaves[i * LEAVES_PER_NODE + 2],
268
- leaves[i * LEAVES_PER_NODE + 3],
269
- leaves[i * LEAVES_PER_NODE + 4]
270
- ]);
271
- }
272
-
273
- // Initialize hashers for intermediate nodes and compute the root.
274
- var k = 0;
275
- for (var i = numLeafHashers; i < numHashers; i++) {
276
- computedHashers[i] = PoseidonHasher(5)([
277
- computedHashers[k * LEAVES_PER_NODE + 0],
278
- computedHashers[k * LEAVES_PER_NODE + 1],
279
- computedHashers[k * LEAVES_PER_NODE + 2],
280
- computedHashers[k * LEAVES_PER_NODE + 3],
281
- computedHashers[k * LEAVES_PER_NODE + 4]
282
- ]);
283
- k++;
284
- }
285
-
286
- root <== computedHashers[numHashers - 1];
287
- }