Skip to content

Commit 38c76f2

Browse files
authored
feat(core): support individual vote counts circuit inputs (#2575)
- [x] Update core logic for tallying with individual vote counts - [x] Exclude individual vote counts inputs (enable when feature is fully ready) - [x] Add VoteTallyWithIndividualCountsQv circuit with vote counts tree constraint checks - [x] Add VoteCounts domain object - [x] Minor refactoring
1 parent 7ae9e91 commit 38c76f2

File tree

8 files changed

+493
-37
lines changed

8 files changed

+493
-37
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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/QuinaryCheckRoot.circom";
12+
include "../../utils/qv/ResultCommitmentVerifier.circom";
13+
include "../../utils/CalculateTotal.circom";
14+
include "../../utils/PoseidonHasher.circom";
15+
16+
/**
17+
* Processes batches of votes and verifies their validity in a Merkle tree structure.
18+
* This template supports Quadratic Voting (QV) with individual vote counting.
19+
* Note: this circuit is not using right now, this is a part of individual vote counts functionality.
20+
* Not finished yet, don't use it in production. It is kept here for future use.
21+
*/
22+
template VoteTallyWithIndividualCountsQv(
23+
stateTreeDepth,
24+
tallyProcessingStateTreeDepth,
25+
voteOptionTreeDepth
26+
) {
27+
// Ensure there's at least one level in the vote option tree.
28+
assert(voteOptionTreeDepth > 0);
29+
// Ensure the intermediate state tree has at least one level.
30+
assert(tallyProcessingStateTreeDepth > 0);
31+
// The intermediate state tree must be smaller than the full state tree.
32+
assert(tallyProcessingStateTreeDepth < stateTreeDepth);
33+
34+
// Number of children per node in the tree, defining the tree's branching factor.
35+
var TREE_ARITY = 5;
36+
var BALLOT_TREE_ARITY = 2;
37+
var VOTE_COUNTS_TREE_ARITY = 2;
38+
39+
// The number of ballots processed at once, determined by the depth of the intermediate state tree.
40+
var ballotBatchSize = BALLOT_TREE_ARITY ** tallyProcessingStateTreeDepth;
41+
var voteCountsBatchSize = VOTE_COUNTS_TREE_ARITY ** tallyProcessingStateTreeDepth;
42+
// Number of voting options available, determined by the depth of the vote option tree.
43+
var totalVoteOptions = TREE_ARITY ** voteOptionTreeDepth;
44+
45+
// Number of elements in each ballot.
46+
var BALLOT_LENGTH = 2;
47+
// Index for the nonce in the ballot array.
48+
var BALLOT_NONCE_INDEX = 0;
49+
// Index for the voting option root in the ballot array.
50+
var BALLOT_VOTE_OPTION_ROOT_INDEX = 1;
51+
// Difference in tree depths, used in path calculations.
52+
var STATE_TREE_DEPTH_DIFFERENCE = stateTreeDepth - tallyProcessingStateTreeDepth;
53+
// Number of elements in each vote count leaf.
54+
var VOTE_COUNTS_LENGTH = 2;
55+
// Index for the voting option index.
56+
var VOTE_COUNTS_NONCE_INDEX = 0;
57+
// Index for root of the vote count per option.
58+
var VOTE_COUNTS_ROOT_INDEX = 1;
59+
60+
// Root of the state Merkle tree, representing the overall state before voting.
61+
signal input stateRoot;
62+
// Root of the ballot Merkle tree, representing the submitted ballots.
63+
signal input ballotRoot;
64+
// Root of the vote counts Merkle tree, representing the counts of votes for each option.
65+
signal input voteCountsRoot;
66+
// Salt used in commitment to secure the ballot data.
67+
signal input sbSalt;
68+
// Commitment to the state and ballots.
69+
signal input sbCommitment;
70+
// Commitment to the current tally before this batch.
71+
signal input currentTallyCommitment;
72+
// Commitment to the new tally after processing this batch.
73+
signal input newTallyCommitment;
74+
// Start index of given batch
75+
signal input index;
76+
// Number of users that signup
77+
signal input totalSignups;
78+
// Ballots and their corresponding path elements for verification in the tree.
79+
signal input ballots[ballotBatchSize][BALLOT_LENGTH];
80+
signal input ballotPathElements[STATE_TREE_DEPTH_DIFFERENCE][BALLOT_TREE_ARITY - 1];
81+
signal input votes[ballotBatchSize][totalVoteOptions];
82+
// Individual vote count tree and their corresponding path elements for verification in the tree.
83+
signal input voteCounts[voteCountsBatchSize][VOTE_COUNTS_LENGTH];
84+
signal input voteCountsPathElements[STATE_TREE_DEPTH_DIFFERENCE][VOTE_COUNTS_TREE_ARITY - 1];
85+
signal input voteCountsData[voteCountsBatchSize][totalVoteOptions];
86+
// Current results for each vote option.
87+
signal input currentResults[totalVoteOptions];
88+
// Salt for the root of the current results.
89+
signal input currentResultsRootSalt;
90+
// Total voice credits spent so far.
91+
signal input currentSpentVoiceCreditSubtotal;
92+
// Salt for the total spent voice credits.
93+
signal input currentSpentVoiceCreditSubtotalSalt;
94+
// Spent voice credits per vote option.
95+
signal input currentPerVoteOptionSpentVoiceCredits[totalVoteOptions];
96+
// Salt for the root of spent credits per option.
97+
signal input currentPerVoteOptionSpentVoiceCreditsRootSalt;
98+
// Salt for the root of the new results.
99+
signal input newResultsRootSalt;
100+
// Salt for the new spent credits per vote option root.
101+
signal input newPerVoteOptionSpentVoiceCreditsRootSalt;
102+
// Salt for the new total spent voice credits root.
103+
signal input newSpentVoiceCreditSubtotalSalt;
104+
105+
// Verify sbCommitment.
106+
var computedSbCommitment = PoseidonHasher(3)([stateRoot, ballotRoot, sbSalt]);
107+
computedSbCommitment === sbCommitment;
108+
109+
// Validates that the index is within the valid range of sign-ups.
110+
var totalSignupsValid = LessEqThan(50)([index, totalSignups]);
111+
totalSignupsValid === 1;
112+
113+
// Hashes each ballot for subroot generation, and checks the existence of the leaf in the Merkle tree.
114+
var computedBallotHashers[ballotBatchSize];
115+
var computedVoteCountsHashers[voteCountsBatchSize];
116+
117+
for (var i = 0; i < ballotBatchSize; i++) {
118+
computedBallotHashers[i] = PoseidonHasher(2)([
119+
ballots[i][BALLOT_NONCE_INDEX],
120+
ballots[i][BALLOT_VOTE_OPTION_ROOT_INDEX]
121+
]);
122+
}
123+
124+
for (var i = 0; i < voteCountsBatchSize; i++) {
125+
computedVoteCountsHashers[i] = PoseidonHasher(2)([
126+
voteCounts[i][VOTE_COUNTS_NONCE_INDEX],
127+
voteCounts[i][VOTE_COUNTS_ROOT_INDEX]
128+
]);
129+
}
130+
131+
var computedBallotSubroot = CheckRoot(tallyProcessingStateTreeDepth)(computedBallotHashers);
132+
var computedBallotPathIndices[STATE_TREE_DEPTH_DIFFERENCE] = MerklePathIndicesGenerator(STATE_TREE_DEPTH_DIFFERENCE)(index / ballotBatchSize);
133+
134+
var computedVoteCountsSubroot = CheckRoot(tallyProcessingStateTreeDepth)(computedVoteCountsHashers);
135+
var computedVoteCountsPathIndices[STATE_TREE_DEPTH_DIFFERENCE] = MerklePathIndicesGenerator(STATE_TREE_DEPTH_DIFFERENCE)(index / voteCountsBatchSize);
136+
137+
// Verifies each ballot's existence within the ballot tree.
138+
LeafExists(STATE_TREE_DEPTH_DIFFERENCE)(
139+
computedBallotSubroot,
140+
ballotPathElements,
141+
computedBallotPathIndices,
142+
ballotRoot
143+
);
144+
145+
// Verifies each vote count's existence within the vote count tree.
146+
LeafExists(STATE_TREE_DEPTH_DIFFERENCE)(
147+
computedVoteCountsSubroot,
148+
voteCountsPathElements,
149+
computedVoteCountsPathIndices,
150+
voteCountsRoot
151+
);
152+
153+
// Processes vote options, verifying each against its declared root.
154+
var computedVoteTree[ballotBatchSize];
155+
156+
for (var i = 0; i < ballotBatchSize; i++) {
157+
computedVoteTree[i] = QuinaryCheckRoot(voteOptionTreeDepth)(votes[i]);
158+
computedVoteTree[i] === ballots[i][BALLOT_VOTE_OPTION_ROOT_INDEX];
159+
}
160+
161+
// Processes vote counts, verifying each against its declared root.
162+
var computedVoteCountsTree[voteCountsBatchSize];
163+
164+
for (var i = 0; i < voteCountsBatchSize; i++) {
165+
computedVoteCountsTree[i] = QuinaryCheckRoot(voteOptionTreeDepth)(voteCountsData[i]);
166+
computedVoteCountsTree[i] === voteCounts[i][VOTE_COUNTS_ROOT_INDEX];
167+
}
168+
169+
// Calculates new results and spent voice credits based on the current and incoming votes.
170+
var computedIsFirstBatch = IsZero()(index);
171+
var computedIsZero = IsZero()(computedIsFirstBatch);
172+
173+
// Tally the new results.
174+
var computedCalculateTotalResult[totalVoteOptions];
175+
for (var i = 0; i < totalVoteOptions; i++) {
176+
var numsRC[ballotBatchSize + 1];
177+
numsRC[ballotBatchSize] = currentResults[i] * computedIsZero;
178+
for (var j = 0; j < ballotBatchSize; j++) {
179+
numsRC[j] = votes[j][i];
180+
}
181+
182+
computedCalculateTotalResult[i] = CalculateTotal(ballotBatchSize + 1)(numsRC);
183+
}
184+
185+
// Tally the new spent voice credit total.
186+
var numsSVC[ballotBatchSize * totalVoteOptions + 1];
187+
numsSVC[ballotBatchSize * totalVoteOptions] = currentSpentVoiceCreditSubtotal * computedIsZero;
188+
for (var i = 0; i < ballotBatchSize; i++) {
189+
for (var j = 0; j < totalVoteOptions; j++) {
190+
numsSVC[i * totalVoteOptions + j] = votes[i][j] * votes[i][j];
191+
}
192+
}
193+
194+
var computedNewSpentVoiceCreditSubtotal = CalculateTotal(ballotBatchSize * totalVoteOptions + 1)(numsSVC);
195+
196+
// Tally the spent voice credits per vote option.
197+
var computedNewPerVOSpentVoiceCredits[totalVoteOptions];
198+
199+
for (var i = 0; i < totalVoteOptions; i++) {
200+
var computedTotalVoiceCreditSpent[ballotBatchSize + 1];
201+
computedTotalVoiceCreditSpent[ballotBatchSize] = currentPerVoteOptionSpentVoiceCredits[i] * computedIsZero;
202+
for (var j = 0; j < ballotBatchSize; j++) {
203+
computedTotalVoiceCreditSpent[j] = votes[j][i] * votes[j][i];
204+
}
205+
206+
computedNewPerVOSpentVoiceCredits[i] = CalculateTotal(ballotBatchSize + 1)(computedTotalVoiceCreditSpent);
207+
}
208+
209+
// Verifies the updated results and spent credits, ensuring consistency and correctness of tally updates.
210+
ResultCommitmentVerifierQv(voteOptionTreeDepth)(
211+
computedIsFirstBatch,
212+
currentTallyCommitment,
213+
newTallyCommitment,
214+
currentResults,
215+
currentResultsRootSalt,
216+
computedCalculateTotalResult,
217+
newResultsRootSalt,
218+
currentSpentVoiceCreditSubtotal,
219+
currentSpentVoiceCreditSubtotalSalt,
220+
computedNewSpentVoiceCreditSubtotal,
221+
newSpentVoiceCreditSubtotalSalt,
222+
currentPerVoteOptionSpentVoiceCredits,
223+
currentPerVoteOptionSpentVoiceCreditsRootSalt,
224+
computedNewPerVOSpentVoiceCredits,
225+
newPerVoteOptionSpentVoiceCreditsRootSalt
226+
);
227+
}

0 commit comments

Comments
 (0)