@maci-protocol/core 0.0.0-ci.a584b1a → 0.0.0-ci.a73cfa9
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.
- package/README.md +3 -3
- package/build/ts/MaciState.d.ts +4 -2
- package/build/ts/MaciState.d.ts.map +1 -1
- package/build/ts/MaciState.js +4 -3
- package/build/ts/MaciState.js.map +1 -1
- package/build/ts/Poll.d.ts +31 -19
- package/build/ts/Poll.d.ts.map +1 -1
- package/build/ts/Poll.js +188 -190
- package/build/ts/Poll.js.map +1 -1
- package/build/ts/index.d.ts +2 -1
- package/build/ts/index.d.ts.map +1 -1
- package/build/ts/index.js +7 -5
- package/build/ts/index.js.map +1 -1
- package/build/ts/utils/constants.d.ts +8 -0
- package/build/ts/utils/constants.d.ts.map +1 -1
- package/build/ts/utils/constants.js +10 -1
- package/build/ts/utils/constants.js.map +1 -1
- package/build/ts/utils/errors.d.ts +2 -1
- package/build/ts/utils/errors.d.ts.map +1 -1
- package/build/ts/utils/errors.js +1 -0
- package/build/ts/utils/errors.js.map +1 -1
- package/build/ts/utils/types.d.ts +37 -9
- package/build/ts/utils/types.d.ts.map +1 -1
- package/build/ts/utils/utils.d.ts +1 -1
- package/build/ts/utils/utils.js +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -8
package/build/ts/Poll.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.Poll = void 0;
|
|
|
7
7
|
const crypto_1 = require("@maci-protocol/crypto");
|
|
8
8
|
const domainobjs_1 = require("@maci-protocol/domainobjs");
|
|
9
9
|
const lean_imt_1 = require("@zk-kit/lean-imt");
|
|
10
|
+
const omit_1 = __importDefault(require("lodash/omit"));
|
|
10
11
|
const assert_1 = __importDefault(require("assert"));
|
|
11
12
|
const constants_1 = require("./utils/constants");
|
|
12
13
|
const errors_1 = require("./utils/errors");
|
|
@@ -23,18 +24,19 @@ class Poll {
|
|
|
23
24
|
* @param maciStateRef - The reference to the MACI state.
|
|
24
25
|
* @param pollId - The poll id
|
|
25
26
|
*/
|
|
26
|
-
constructor(pollEndTimestamp, coordinatorKeypair, treeDepths, batchSizes, maciStateRef, voteOptions) {
|
|
27
|
+
constructor(pollEndTimestamp, coordinatorKeypair, treeDepths, batchSizes, maciStateRef, voteOptions, mode) {
|
|
27
28
|
this.ballots = [];
|
|
29
|
+
this.voteCounts = [];
|
|
28
30
|
this.messages = [];
|
|
29
31
|
this.commands = [];
|
|
30
32
|
this.encryptionPublicKeys = [];
|
|
31
33
|
this.stateCopied = false;
|
|
32
34
|
this.publicKeys = [domainobjs_1.padKey];
|
|
33
35
|
// For message processing
|
|
34
|
-
this.
|
|
36
|
+
this.totalBatchesProcessed = 0;
|
|
35
37
|
this.sbSalts = {};
|
|
36
38
|
this.resultRootSalts = {};
|
|
37
|
-
this.
|
|
39
|
+
this.perVoteOptionSpentVoiceCreditsRootSalts = {};
|
|
38
40
|
this.spentVoiceCreditSubtotalSalts = {};
|
|
39
41
|
// For vote tallying
|
|
40
42
|
this.tallyResult = [];
|
|
@@ -102,7 +104,6 @@ class Poll {
|
|
|
102
104
|
this.pollStateTree?.insert(stateLeaf.hash());
|
|
103
105
|
});
|
|
104
106
|
// Create as many ballots as state leaves
|
|
105
|
-
this.emptyBallotHash = this.emptyBallot.hash();
|
|
106
107
|
this.ballotTree = new crypto_1.IncrementalQuinTree(Number(this.treeDepths.stateTreeDepth), this.emptyBallotHash, constants_1.STATE_TREE_ARITY, crypto_1.hash2);
|
|
107
108
|
this.ballotTree.insert(this.emptyBallotHash);
|
|
108
109
|
// we fill the ballotTree with empty ballots hashes to match the number of signups in the tree
|
|
@@ -110,6 +111,13 @@ class Poll {
|
|
|
110
111
|
this.ballotTree.insert(this.emptyBallotHash);
|
|
111
112
|
this.ballots.push(this.emptyBallot);
|
|
112
113
|
}
|
|
114
|
+
this.voteCountsTree = new crypto_1.IncrementalQuinTree(Number(this.treeDepths.stateTreeDepth), this.emptyVoteCountsHash, constants_1.STATE_TREE_ARITY, crypto_1.hash2);
|
|
115
|
+
this.voteCountsTree.insert(this.emptyVoteCountsHash);
|
|
116
|
+
const emptyVoteCounts = domainobjs_1.VoteCounts.generateBlank(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
|
|
117
|
+
while (this.voteCounts.length < this.publicKeys.length) {
|
|
118
|
+
this.voteCountsTree.insert(this.emptyVoteCountsHash);
|
|
119
|
+
this.voteCounts.push(emptyVoteCounts);
|
|
120
|
+
}
|
|
113
121
|
this.stateCopied = true;
|
|
114
122
|
};
|
|
115
123
|
/**
|
|
@@ -118,7 +126,7 @@ class Poll {
|
|
|
118
126
|
* @param encryptionPublicKey - The public key associated with the encryption private key.
|
|
119
127
|
* @returns A number of variables which will be used in the zk-SNARK circuit.
|
|
120
128
|
*/
|
|
121
|
-
this.processMessage = (message, encryptionPublicKey
|
|
129
|
+
this.processMessage = (message, encryptionPublicKey) => {
|
|
122
130
|
try {
|
|
123
131
|
// Decrypt the message
|
|
124
132
|
const sharedKey = domainobjs_1.Keypair.generateEcdhSharedKey(this.coordinatorKeypair.privateKey, encryptionPublicKey);
|
|
@@ -148,24 +156,20 @@ class Poll {
|
|
|
148
156
|
}
|
|
149
157
|
const voteOptionIndex = Number(command.voteOptionIndex);
|
|
150
158
|
const originalVoteWeight = ballot.votes[voteOptionIndex];
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// but we need to ensure that we are not going >= balance
|
|
158
|
-
// @note that above comment is valid for quadratic voting
|
|
159
|
-
// for non quadratic voting, we simply remove the exponentiation
|
|
160
|
-
const voiceCreditsLeft = qv
|
|
161
|
-
? stateLeaf.voiceCreditBalance +
|
|
162
|
-
originalVoteWeight * originalVoteWeight -
|
|
163
|
-
command.newVoteWeight * command.newVoteWeight
|
|
164
|
-
: stateLeaf.voiceCreditBalance + originalVoteWeight - command.newVoteWeight;
|
|
159
|
+
const voiceCreditsLeft = this.getVoiceCreditsLeft({
|
|
160
|
+
stateLeaf,
|
|
161
|
+
originalVoteWeight,
|
|
162
|
+
newVoteWeight: command.newVoteWeight,
|
|
163
|
+
mode: this.mode,
|
|
164
|
+
});
|
|
165
165
|
// If the remaining voice credits is insufficient, do nothing
|
|
166
166
|
if (voiceCreditsLeft < 0n) {
|
|
167
167
|
throw new errors_1.ProcessMessageError(errors_1.ProcessMessageErrors.InsufficientVoiceCredits);
|
|
168
168
|
}
|
|
169
|
+
// If there are some voice credits left for full credits mode, do nothing
|
|
170
|
+
if (this.mode === constants_1.EMode.FULL && voiceCreditsLeft > 0n) {
|
|
171
|
+
throw new errors_1.ProcessMessageError(errors_1.ProcessMessageErrors.InvalidVoiceCredits);
|
|
172
|
+
}
|
|
169
173
|
// Deep-copy the state leaf and update its attributes
|
|
170
174
|
const newStateLeaf = stateLeaf.copy();
|
|
171
175
|
newStateLeaf.voiceCreditBalance = voiceCreditsLeft;
|
|
@@ -177,20 +181,23 @@ class Poll {
|
|
|
177
181
|
newBallot.nonce += 1n;
|
|
178
182
|
// we change the vote for this exact vote option
|
|
179
183
|
newBallot.votes[voteOptionIndex] = command.newVoteWeight;
|
|
184
|
+
if (this.mode === constants_1.EMode.FULL) {
|
|
185
|
+
newBallot.votes = newBallot.votes.map((votes, index) => (voteOptionIndex === index ? votes : 0n));
|
|
186
|
+
}
|
|
180
187
|
// calculate the path elements for the state tree given the original state tree (before any changes)
|
|
181
188
|
// changes could effectively be made by this new vote - either a key change or vote change
|
|
182
189
|
// would result in a different state leaf
|
|
183
|
-
const { pathElements: originalStateLeafPathElements } = this.pollStateTree.
|
|
190
|
+
const { pathElements: originalStateLeafPathElements } = this.pollStateTree.generateProof(Number(stateLeafIndex));
|
|
184
191
|
// calculate the path elements for the ballot tree given the original ballot tree (before any changes)
|
|
185
192
|
// changes could effectively be made by this new ballot
|
|
186
|
-
const { pathElements: originalBallotPathElements } = this.ballotTree.
|
|
193
|
+
const { pathElements: originalBallotPathElements } = this.ballotTree.generateProof(Number(stateLeafIndex));
|
|
187
194
|
// create a new quinary tree where we insert the votes of the origin (up until this message is processed) ballot
|
|
188
195
|
const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
|
|
189
196
|
for (let i = 0; i < this.ballots[0].votes.length; i += 1) {
|
|
190
197
|
voteTree.insert(ballot.votes[i]);
|
|
191
198
|
}
|
|
192
199
|
// calculate the path elements for the vote option tree given the original vote option tree (before any changes)
|
|
193
|
-
const { pathElements: originalVoteWeightsPathElements } = voteTree.
|
|
200
|
+
const { pathElements: originalVoteWeightsPathElements } = voteTree.generateProof(voteOptionIndex);
|
|
194
201
|
// we return the data which is then to be used in the processMessage circuit
|
|
195
202
|
// to generate a proof of processing
|
|
196
203
|
return {
|
|
@@ -270,14 +277,7 @@ class Poll {
|
|
|
270
277
|
// calculate the path elements for the state tree given the original state tree
|
|
271
278
|
const { siblings, index } = this.stateTree.generateProof(Number(stateLeafIndex));
|
|
272
279
|
const siblingsLength = siblings.length;
|
|
273
|
-
// The index must be converted to a list of indices, 1 for each tree level.
|
|
274
|
-
// The circuit tree depth is this.treeDepths.stateTreeDepth, so the number of siblings must be this.treeDepths.stateTreeDepth,
|
|
275
|
-
// even if the tree depth is actually 3. The missing siblings can be set to 0, as they
|
|
276
|
-
// won't be used to calculate the root in the circuit.
|
|
277
|
-
const indices = [];
|
|
278
280
|
for (let i = 0; i < this.treeDepths.stateTreeDepth; i += 1) {
|
|
279
|
-
// eslint-disable-next-line no-bitwise
|
|
280
|
-
indices.push(BigInt((index >> i) & 1));
|
|
281
281
|
if (i >= siblingsLength) {
|
|
282
282
|
siblings[i] = BigInt(0);
|
|
283
283
|
}
|
|
@@ -294,7 +294,7 @@ class Poll {
|
|
|
294
294
|
privateKey: maciPrivateKey.asCircuitInputs(),
|
|
295
295
|
pollPublicKey: pollPublicKey.asCircuitInputs(),
|
|
296
296
|
siblings: siblingsArray,
|
|
297
|
-
|
|
297
|
+
index: BigInt(index),
|
|
298
298
|
nullifier,
|
|
299
299
|
stateRoot,
|
|
300
300
|
actualStateTreeDepth,
|
|
@@ -309,19 +309,18 @@ class Poll {
|
|
|
309
309
|
*/
|
|
310
310
|
this.joinedCircuitInputs = ({ maciPrivateKey, stateLeafIndex, voiceCreditsBalance, }) => {
|
|
311
311
|
// calculate the path elements for the state tree given the original state tree
|
|
312
|
-
const { root: stateRoot, pathElements, pathIndices } = this.pollStateTree.
|
|
312
|
+
const { root: stateRoot, pathElements, pathIndices } = this.pollStateTree.generateProof(Number(stateLeafIndex));
|
|
313
313
|
const elementsLength = pathIndices.length;
|
|
314
314
|
for (let i = 0; i < this.treeDepths.stateTreeDepth; i += 1) {
|
|
315
315
|
if (i >= elementsLength) {
|
|
316
316
|
pathElements[i] = [0n];
|
|
317
|
-
pathIndices[i] = 0;
|
|
318
317
|
}
|
|
319
318
|
}
|
|
320
319
|
const circuitInputs = {
|
|
321
320
|
privateKey: maciPrivateKey.asCircuitInputs(),
|
|
322
321
|
pathElements: pathElements.map((item) => item.toString()),
|
|
323
322
|
voiceCreditsBalance: voiceCreditsBalance.toString(),
|
|
324
|
-
|
|
323
|
+
index: BigInt(stateLeafIndex),
|
|
325
324
|
actualStateTreeDepth: BigInt(this.actualStateTreeDepth),
|
|
326
325
|
stateRoot,
|
|
327
326
|
};
|
|
@@ -346,7 +345,7 @@ class Poll {
|
|
|
346
345
|
if (this.messages.length > batchSize && this.messages.length % batchSize > 0) {
|
|
347
346
|
totalBatches += 1;
|
|
348
347
|
}
|
|
349
|
-
return this.
|
|
348
|
+
return this.totalBatchesProcessed < totalBatches;
|
|
350
349
|
};
|
|
351
350
|
/**
|
|
352
351
|
* Process _batchSize messages starting from the saved index. This
|
|
@@ -362,10 +361,10 @@ class Poll {
|
|
|
362
361
|
* @param quiet - Whether to log errors or not
|
|
363
362
|
* @returns stringified circuit inputs
|
|
364
363
|
*/
|
|
365
|
-
this.processMessages = (pollId,
|
|
364
|
+
this.processMessages = (pollId, quiet = true) => {
|
|
366
365
|
(0, assert_1.default)(this.hasUnprocessedMessages(), "No more messages to process");
|
|
367
366
|
const batchSize = this.batchSizes.messageBatchSize;
|
|
368
|
-
if (this.
|
|
367
|
+
if (this.totalBatchesProcessed === 0) {
|
|
369
368
|
// Prevent other polls from being processed until this poll has
|
|
370
369
|
// been fully processed
|
|
371
370
|
this.maciStateRef.pollBeingProcessed = true;
|
|
@@ -411,7 +410,7 @@ class Poll {
|
|
|
411
410
|
encryptionPublicKey = this.encryptionPublicKeys[idx];
|
|
412
411
|
try {
|
|
413
412
|
// check if the command is valid
|
|
414
|
-
const { stateLeafIndex, originalStateLeaf, originalBallot, originalVoteWeight, originalVoteWeightsPathElements, originalStateLeafPathElements, originalBallotPathElements, newStateLeaf, newBallot, } = this.processMessage(message, encryptionPublicKey
|
|
413
|
+
const { stateLeafIndex, originalStateLeaf, originalBallot, originalVoteWeight, originalVoteWeightsPathElements, originalStateLeafPathElements, originalBallotPathElements, newStateLeaf, newBallot, } = this.processMessage(message, encryptionPublicKey);
|
|
415
414
|
const index = stateLeafIndex;
|
|
416
415
|
// we add at position 0 the original data
|
|
417
416
|
currentStateLeaves.unshift(originalStateLeaf);
|
|
@@ -455,11 +454,11 @@ class Poll {
|
|
|
455
454
|
// if the state leaf index is valid then use it
|
|
456
455
|
if (stateLeafIndex < this.pollStateLeaves.length) {
|
|
457
456
|
currentStateLeaves.unshift(this.pollStateLeaves[Number(stateLeafIndex)].copy());
|
|
458
|
-
currentStateLeavesPathElements.unshift(this.pollStateTree.
|
|
457
|
+
currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(Number(stateLeafIndex)).pathElements);
|
|
459
458
|
// copy the ballot
|
|
460
459
|
const ballot = this.ballots[Number(stateLeafIndex)].copy();
|
|
461
460
|
currentBallots.unshift(ballot);
|
|
462
|
-
currentBallotsPathElements.unshift(this.ballotTree.
|
|
461
|
+
currentBallotsPathElements.unshift(this.ballotTree.generateProof(Number(stateLeafIndex)).pathElements);
|
|
463
462
|
// @note we check that command.voteOptionIndex is valid so < voteOptions
|
|
464
463
|
// this might be unnecessary but we do it to prevent a possible DoS attack
|
|
465
464
|
// from voters who could potentially encrypt a message in such as way that
|
|
@@ -473,7 +472,7 @@ class Poll {
|
|
|
473
472
|
voteTree.insert(ballot.votes[j]);
|
|
474
473
|
}
|
|
475
474
|
// get the path elements for the first vote leaf
|
|
476
|
-
currentVoteWeightsPathElements.unshift(voteTree.
|
|
475
|
+
currentVoteWeightsPathElements.unshift(voteTree.generateProof(Number(command.voteOptionIndex)).pathElements);
|
|
477
476
|
}
|
|
478
477
|
else {
|
|
479
478
|
currentVoteWeights.unshift(ballot.votes[0]);
|
|
@@ -484,22 +483,22 @@ class Poll {
|
|
|
484
483
|
voteTree.insert(ballot.votes[j]);
|
|
485
484
|
}
|
|
486
485
|
// get the path elements for the first vote leaf
|
|
487
|
-
currentVoteWeightsPathElements.unshift(voteTree.
|
|
486
|
+
currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
|
|
488
487
|
}
|
|
489
488
|
}
|
|
490
489
|
else {
|
|
491
490
|
// just use state leaf index 0
|
|
492
491
|
currentStateLeaves.unshift(this.pollStateLeaves[0].copy());
|
|
493
|
-
currentStateLeavesPathElements.unshift(this.pollStateTree.
|
|
492
|
+
currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(0).pathElements);
|
|
494
493
|
currentBallots.unshift(this.ballots[0].copy());
|
|
495
|
-
currentBallotsPathElements.unshift(this.ballotTree.
|
|
494
|
+
currentBallotsPathElements.unshift(this.ballotTree.generateProof(0).pathElements);
|
|
496
495
|
// Since the command is invalid, we use a zero vote weight
|
|
497
496
|
currentVoteWeights.unshift(this.ballots[0].votes[0]);
|
|
498
497
|
// create a new quinary tree and add an empty vote
|
|
499
498
|
const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
|
|
500
499
|
voteTree.insert(this.ballots[0].votes[0]);
|
|
501
500
|
// get the path elements for this empty vote weight leaf
|
|
502
|
-
currentVoteWeightsPathElements.unshift(voteTree.
|
|
501
|
+
currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
|
|
503
502
|
}
|
|
504
503
|
}
|
|
505
504
|
else {
|
|
@@ -510,17 +509,17 @@ class Poll {
|
|
|
510
509
|
else {
|
|
511
510
|
// Since we don't have a command at that position, use a blank state leaf
|
|
512
511
|
currentStateLeaves.unshift(this.pollStateLeaves[0].copy());
|
|
513
|
-
currentStateLeavesPathElements.unshift(this.pollStateTree.
|
|
512
|
+
currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(0).pathElements);
|
|
514
513
|
// since the command is invliad we use the blank ballot
|
|
515
514
|
currentBallots.unshift(this.ballots[0].copy());
|
|
516
|
-
currentBallotsPathElements.unshift(this.ballotTree.
|
|
515
|
+
currentBallotsPathElements.unshift(this.ballotTree.generateProof(0).pathElements);
|
|
517
516
|
// Since the command is invalid, we use a zero vote weight
|
|
518
517
|
currentVoteWeights.unshift(this.ballots[0].votes[0]);
|
|
519
518
|
// create a new quinary tree and add an empty vote
|
|
520
519
|
const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
|
|
521
520
|
voteTree.insert(this.ballots[0].votes[0]);
|
|
522
521
|
// get the path elements for this empty vote weight leaf
|
|
523
|
-
currentVoteWeightsPathElements.unshift(voteTree.
|
|
522
|
+
currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
|
|
524
523
|
}
|
|
525
524
|
}
|
|
526
525
|
// store the data in the circuit inputs object
|
|
@@ -538,7 +537,7 @@ class Poll {
|
|
|
538
537
|
circuitInputs.currentVoteWeights = currentVoteWeights;
|
|
539
538
|
circuitInputs.currentVoteWeightsPathElements = currentVoteWeightsPathElements;
|
|
540
539
|
// record that we processed one batch
|
|
541
|
-
this.
|
|
540
|
+
this.totalBatchesProcessed += 1;
|
|
542
541
|
if (this.currentMessageBatchIndex > 0) {
|
|
543
542
|
this.currentMessageBatchIndex -= 1;
|
|
544
543
|
}
|
|
@@ -557,7 +556,7 @@ class Poll {
|
|
|
557
556
|
circuitInputs.newSbCommitment = (0, crypto_1.hash3)([newStateRoot, newBallotRoot, newSbSalt]);
|
|
558
557
|
const coordinatorPublicKeyHash = this.coordinatorKeypair.publicKey.hash();
|
|
559
558
|
// If this is the last batch, release the lock
|
|
560
|
-
if (this.
|
|
559
|
+
if (this.totalBatchesProcessed * batchSize >= this.messages.length) {
|
|
561
560
|
this.maciStateRef.pollBeingProcessed = false;
|
|
562
561
|
}
|
|
563
562
|
// ensure we pass the dynamic tree depth
|
|
@@ -661,7 +660,8 @@ class Poll {
|
|
|
661
660
|
this.hasUntalliedBallots = () => this.numBatchesTallied * this.batchSizes.tallyBatchSize < this.ballots.length;
|
|
662
661
|
/**
|
|
663
662
|
* This method tallies a ballots and updates the tally results.
|
|
664
|
-
*
|
|
663
|
+
*
|
|
664
|
+
* @returns the circuit inputs for the VoteTally circuit.
|
|
665
665
|
*/
|
|
666
666
|
this.tallyVotes = () => {
|
|
667
667
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -674,84 +674,118 @@ class Poll {
|
|
|
674
674
|
const batchStartIndex = this.numBatchesTallied * batchSize;
|
|
675
675
|
// get the salts needed for the commitments
|
|
676
676
|
const currentResultsRootSalt = batchStartIndex === 0 ? 0n : this.resultRootSalts[batchStartIndex - batchSize];
|
|
677
|
-
const currentPerVoteOptionSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? 0n : this.
|
|
677
|
+
const currentPerVoteOptionSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? 0n : this.perVoteOptionSpentVoiceCreditsRootSalts[batchStartIndex - batchSize];
|
|
678
678
|
const currentSpentVoiceCreditSubtotalSalt = batchStartIndex === 0 ? 0n : this.spentVoiceCreditSubtotalSalts[batchStartIndex - batchSize];
|
|
679
679
|
// generate a commitment to the current results
|
|
680
680
|
const currentResultsCommitment = (0, crypto_1.generateTreeCommitment)(this.tallyResult, currentResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
|
|
681
681
|
// generate a commitment to the current per vote option spent voice credits
|
|
682
|
-
const
|
|
682
|
+
const currentPerVoteOptionSpentVoiceCreditsCommitment = this.generatePerVoteOptionSpentVoiceCreditsCommitment(currentPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex, this.mode);
|
|
683
683
|
// generate a commitment to the current spent voice credits
|
|
684
|
-
const currentSpentVoiceCreditsCommitment = this.
|
|
684
|
+
const currentSpentVoiceCreditsCommitment = this.generateSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, this.mode);
|
|
685
685
|
// the current commitment for the first batch will be 0
|
|
686
686
|
// otherwise calculate as
|
|
687
687
|
// hash([
|
|
688
688
|
// currentResultsCommitment,
|
|
689
689
|
// currentSpentVoiceCreditsCommitment,
|
|
690
|
-
// currentPerVOSpentVoiceCreditsCommitment
|
|
691
690
|
// ])
|
|
692
|
-
|
|
691
|
+
// or for QV
|
|
692
|
+
// hash([
|
|
693
|
+
// currentResultsCommitment,
|
|
694
|
+
// currentSpentVoiceCreditsCommitment,
|
|
695
|
+
// currentPerVoteOptionSpentVoiceCreditsCommitment
|
|
696
|
+
// ])
|
|
697
|
+
// TODO: use commitment for vote counts
|
|
698
|
+
const currentTallyCommitmentQv = this.mode !== constants_1.EMode.QV || batchStartIndex === 0
|
|
693
699
|
? 0n
|
|
694
700
|
: (0, crypto_1.hash3)([
|
|
695
701
|
currentResultsCommitment,
|
|
696
702
|
currentSpentVoiceCreditsCommitment,
|
|
697
|
-
|
|
703
|
+
currentPerVoteOptionSpentVoiceCreditsCommitment,
|
|
698
704
|
]);
|
|
705
|
+
const currentTallyCommitmentNonQv = this.mode === constants_1.EMode.QV || batchStartIndex === 0
|
|
706
|
+
? 0n
|
|
707
|
+
: (0, crypto_1.hashLeftRight)(currentResultsCommitment, currentSpentVoiceCreditsCommitment);
|
|
708
|
+
const currentTallyCommitment = currentTallyCommitmentNonQv || currentTallyCommitmentQv;
|
|
709
|
+
const startIndex = this.numBatchesTallied * batchSize;
|
|
710
|
+
const endIndex = this.numBatchesTallied * batchSize + batchSize;
|
|
699
711
|
const ballots = [];
|
|
712
|
+
const voteCounts = [];
|
|
713
|
+
const voteCountsData = [];
|
|
700
714
|
const currentResults = this.tallyResult.map((x) => BigInt(x.toString()));
|
|
701
715
|
const currentPerVoteOptionSpentVoiceCredits = this.perVoteOptionSpentVoiceCredits.map((x) => BigInt(x.toString()));
|
|
702
716
|
const currentSpentVoiceCreditSubtotal = BigInt(this.totalSpentVoiceCredits.toString());
|
|
703
717
|
// loop in normal order to tally the ballots one by one
|
|
704
|
-
for (let i =
|
|
718
|
+
for (let i = startIndex; i < endIndex; i += 1) {
|
|
705
719
|
// we stop if we have no more ballots to tally
|
|
706
720
|
if (i >= this.ballots.length) {
|
|
707
721
|
break;
|
|
708
722
|
}
|
|
709
723
|
// save to the local ballot array
|
|
710
724
|
ballots.push(this.ballots[i]);
|
|
725
|
+
const ballot = this.ballots[i];
|
|
726
|
+
const newVoteCounts = this.voteCounts[i].copy();
|
|
727
|
+
newVoteCounts.counts = ballot.votes.map((votes, voteOptionIndex) => this.voteCounts[i].counts[voteOptionIndex] + BigInt(votes !== 0n));
|
|
728
|
+
// save to the local vote counts array
|
|
729
|
+
this.voteCountsTree?.update(i, newVoteCounts.hash());
|
|
730
|
+
this.voteCounts[i] = newVoteCounts;
|
|
731
|
+
voteCounts.push(newVoteCounts);
|
|
732
|
+
voteCountsData.push(newVoteCounts.counts);
|
|
711
733
|
// for each possible vote option we loop and calculate
|
|
712
734
|
for (let j = 0; j < this.maxVoteOptions; j += 1) {
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
this.
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
735
|
+
const votes = this.ballots[i].votes[j];
|
|
736
|
+
this.tallyResult[j] += votes;
|
|
737
|
+
if (this.mode === constants_1.EMode.QV) {
|
|
738
|
+
// the per vote option spent voice credits will be the sum of the squares of the votes
|
|
739
|
+
this.perVoteOptionSpentVoiceCredits[j] += votes * votes;
|
|
740
|
+
// the total spent voice credits will be the sum of the squares of the votes
|
|
741
|
+
this.totalSpentVoiceCredits += votes * votes;
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
// the total spent voice credits will be the sum of the votes
|
|
745
|
+
this.totalSpentVoiceCredits += votes;
|
|
746
|
+
}
|
|
720
747
|
}
|
|
721
748
|
}
|
|
722
749
|
const emptyBallot = new domainobjs_1.Ballot(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
|
|
750
|
+
const emptyVoteCounts = new domainobjs_1.VoteCounts(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
|
|
723
751
|
// pad the ballots array
|
|
724
752
|
while (ballots.length < batchSize) {
|
|
725
753
|
ballots.push(emptyBallot);
|
|
726
754
|
}
|
|
755
|
+
// pad the vote counts array
|
|
756
|
+
while (voteCounts.length < batchSize) {
|
|
757
|
+
voteCounts.push(emptyVoteCounts);
|
|
758
|
+
}
|
|
727
759
|
// generate the new salts
|
|
728
760
|
const newResultsRootSalt = (0, crypto_1.generateRandomSalt)();
|
|
729
761
|
const newPerVoteOptionSpentVoiceCreditsRootSalt = (0, crypto_1.generateRandomSalt)();
|
|
730
762
|
const newSpentVoiceCreditSubtotalSalt = (0, crypto_1.generateRandomSalt)();
|
|
731
763
|
// and save them to be used in the next batch
|
|
732
764
|
this.resultRootSalts[batchStartIndex] = newResultsRootSalt;
|
|
733
|
-
this.
|
|
765
|
+
this.perVoteOptionSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVoteOptionSpentVoiceCreditsRootSalt;
|
|
734
766
|
this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt;
|
|
735
767
|
// generate the new results commitment with the new salts and data
|
|
736
768
|
const newResultsCommitment = (0, crypto_1.generateTreeCommitment)(this.tallyResult, newResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
|
|
737
769
|
// generate the new spent voice credits commitment with the new salts and data
|
|
738
|
-
const newSpentVoiceCreditsCommitment = this.
|
|
770
|
+
const newSpentVoiceCreditsCommitment = this.generateSpentVoiceCreditSubtotalCommitment(newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, this.mode);
|
|
739
771
|
// generate the new per vote option spent voice credits commitment with the new salts and data
|
|
740
|
-
const
|
|
772
|
+
const newPerVoteOptionSpentVoiceCreditsCommitment = this.generatePerVoteOptionSpentVoiceCreditsCommitment(newPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex + batchSize, this.mode);
|
|
741
773
|
// generate the new tally commitment
|
|
742
|
-
const newTallyCommitment =
|
|
743
|
-
newResultsCommitment,
|
|
744
|
-
newSpentVoiceCreditsCommitment
|
|
745
|
-
newPerVOSpentVoiceCreditsCommitment,
|
|
746
|
-
]);
|
|
774
|
+
const newTallyCommitment = this.mode === constants_1.EMode.QV
|
|
775
|
+
? (0, crypto_1.hash3)([newResultsCommitment, newSpentVoiceCreditsCommitment, newPerVoteOptionSpentVoiceCreditsCommitment])
|
|
776
|
+
: (0, crypto_1.hashLeftRight)(newResultsCommitment, newSpentVoiceCreditsCommitment);
|
|
747
777
|
// cache vars
|
|
748
778
|
const stateRoot = this.pollStateTree.root;
|
|
749
779
|
const ballotRoot = this.ballotTree.root;
|
|
780
|
+
const voteCountsRoot = this.voteCountsTree.root;
|
|
750
781
|
const sbSalt = this.sbSalts[this.currentMessageBatchIndex];
|
|
751
782
|
const sbCommitment = (0, crypto_1.hash3)([stateRoot, ballotRoot, sbSalt]);
|
|
752
|
-
const ballotSubrootProof = this.ballotTree?.
|
|
783
|
+
const ballotSubrootProof = this.ballotTree?.generateSubrootProof(batchStartIndex, batchStartIndex + batchSize);
|
|
784
|
+
const voteCountsSubrootProof = this.voteCountsTree?.generateSubrootProof(batchStartIndex, batchStartIndex + batchSize);
|
|
753
785
|
const votes = ballots.map((x) => x.votes);
|
|
754
|
-
|
|
786
|
+
// Don't include these inputs in the circuit inputs until individual vote counts are implemented
|
|
787
|
+
const excludedCircuitInputs = ["voteCountsData", "voteCountsPathElements", "voteCounts", "voteCountsRoot"];
|
|
788
|
+
const circuitInputs = (0, crypto_1.stringifyBigInts)((0, omit_1.default)({
|
|
755
789
|
stateRoot,
|
|
756
790
|
ballotRoot,
|
|
757
791
|
sbSalt,
|
|
@@ -763,104 +797,27 @@ class Poll {
|
|
|
763
797
|
ballots: ballots.map((x) => x.asCircuitInputs()),
|
|
764
798
|
ballotPathElements: ballotSubrootProof.pathElements,
|
|
765
799
|
votes,
|
|
800
|
+
voteCountsRoot,
|
|
801
|
+
voteCounts: voteCounts.map((x) => x.asCircuitInputs()),
|
|
802
|
+
voteCountsPathElements: voteCountsSubrootProof.pathElements,
|
|
803
|
+
voteCountsData,
|
|
766
804
|
currentResults,
|
|
767
805
|
currentResultsRootSalt,
|
|
768
806
|
currentSpentVoiceCreditSubtotal,
|
|
769
807
|
currentSpentVoiceCreditSubtotalSalt,
|
|
770
808
|
currentPerVoteOptionSpentVoiceCredits,
|
|
771
809
|
currentPerVoteOptionSpentVoiceCreditsRootSalt,
|
|
772
|
-
newResultsRootSalt,
|
|
773
810
|
newPerVoteOptionSpentVoiceCreditsRootSalt,
|
|
774
|
-
newSpentVoiceCreditSubtotalSalt,
|
|
775
|
-
});
|
|
776
|
-
this.numBatchesTallied += 1;
|
|
777
|
-
return circuitInputs;
|
|
778
|
-
};
|
|
779
|
-
this.tallyVotesNonQv = () => {
|
|
780
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
781
|
-
if (this.sbSalts[this.currentMessageBatchIndex] === undefined) {
|
|
782
|
-
throw new Error("You must process the messages first");
|
|
783
|
-
}
|
|
784
|
-
const batchSize = this.batchSizes.tallyBatchSize;
|
|
785
|
-
(0, assert_1.default)(this.hasUntalliedBallots(), "No more ballots to tally");
|
|
786
|
-
// calculate where we start tallying next
|
|
787
|
-
const batchStartIndex = this.numBatchesTallied * batchSize;
|
|
788
|
-
// get the salts needed for the commitments
|
|
789
|
-
const currentResultsRootSalt = batchStartIndex === 0 ? 0n : this.resultRootSalts[batchStartIndex - batchSize];
|
|
790
|
-
const currentSpentVoiceCreditSubtotalSalt = batchStartIndex === 0 ? 0n : this.spentVoiceCreditSubtotalSalts[batchStartIndex - batchSize];
|
|
791
|
-
// generate a commitment to the current results
|
|
792
|
-
const currentResultsCommitment = (0, crypto_1.generateTreeCommitment)(this.tallyResult, currentResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
|
|
793
|
-
// generate a commitment to the current spent voice credits
|
|
794
|
-
const currentSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, false);
|
|
795
|
-
// the current commitment for the first batch will be 0
|
|
796
|
-
// otherwise calculate as
|
|
797
|
-
// hash([
|
|
798
|
-
// currentResultsCommitment,
|
|
799
|
-
// currentSpentVoiceCreditsCommitment,
|
|
800
|
-
// ])
|
|
801
|
-
const currentTallyCommitment = batchStartIndex === 0 ? 0n : (0, crypto_1.hashLeftRight)(currentResultsCommitment, currentSpentVoiceCreditsCommitment);
|
|
802
|
-
const ballots = [];
|
|
803
|
-
const currentResults = this.tallyResult.map((x) => BigInt(x.toString()));
|
|
804
|
-
const currentSpentVoiceCreditSubtotal = BigInt(this.totalSpentVoiceCredits.toString());
|
|
805
|
-
// loop in normal order to tally the ballots one by one
|
|
806
|
-
for (let i = this.numBatchesTallied * batchSize; i < this.numBatchesTallied * batchSize + batchSize; i += 1) {
|
|
807
|
-
// we stop if we have no more ballots to tally
|
|
808
|
-
if (i >= this.ballots.length) {
|
|
809
|
-
break;
|
|
810
|
-
}
|
|
811
|
-
// save to the local ballot array
|
|
812
|
-
ballots.push(this.ballots[i]);
|
|
813
|
-
// for each possible vote option we loop and calculate
|
|
814
|
-
for (let j = 0; j < this.maxVoteOptions; j += 1) {
|
|
815
|
-
const v = this.ballots[i].votes[j];
|
|
816
|
-
this.tallyResult[j] += v;
|
|
817
|
-
// the total spent voice credits will be the sum of the votes
|
|
818
|
-
this.totalSpentVoiceCredits += v;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
const emptyBallot = new domainobjs_1.Ballot(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
|
|
822
|
-
// pad the ballots array
|
|
823
|
-
while (ballots.length < batchSize) {
|
|
824
|
-
ballots.push(emptyBallot);
|
|
825
|
-
}
|
|
826
|
-
// generate the new salts
|
|
827
|
-
const newResultsRootSalt = (0, crypto_1.generateRandomSalt)();
|
|
828
|
-
const newSpentVoiceCreditSubtotalSalt = (0, crypto_1.generateRandomSalt)();
|
|
829
|
-
// and save them to be used in the next batch
|
|
830
|
-
this.resultRootSalts[batchStartIndex] = newResultsRootSalt;
|
|
831
|
-
this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt;
|
|
832
|
-
// generate the new results commitment with the new salts and data
|
|
833
|
-
const newResultsCommitment = (0, crypto_1.generateTreeCommitment)(this.tallyResult, newResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
|
|
834
|
-
// generate the new spent voice credits commitment with the new salts and data
|
|
835
|
-
const newSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, false);
|
|
836
|
-
// generate the new tally commitment
|
|
837
|
-
const newTallyCommitment = (0, crypto_1.hashLeftRight)(newResultsCommitment, newSpentVoiceCreditsCommitment);
|
|
838
|
-
// cache vars
|
|
839
|
-
const stateRoot = this.pollStateTree.root;
|
|
840
|
-
const ballotRoot = this.ballotTree.root;
|
|
841
|
-
const sbSalt = this.sbSalts[this.currentMessageBatchIndex];
|
|
842
|
-
const sbCommitment = (0, crypto_1.hash3)([stateRoot, ballotRoot, sbSalt]);
|
|
843
|
-
const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize);
|
|
844
|
-
const votes = ballots.map((x) => x.votes);
|
|
845
|
-
const circuitInputs = (0, crypto_1.stringifyBigInts)({
|
|
846
|
-
stateRoot,
|
|
847
|
-
ballotRoot,
|
|
848
|
-
sbSalt,
|
|
849
|
-
index: BigInt(batchStartIndex),
|
|
850
|
-
totalSignups: BigInt(this.totalSignups),
|
|
851
|
-
sbCommitment,
|
|
852
|
-
currentTallyCommitment,
|
|
853
|
-
newTallyCommitment,
|
|
854
|
-
ballots: ballots.map((x) => x.asCircuitInputs()),
|
|
855
|
-
ballotPathElements: ballotSubrootProof.pathElements,
|
|
856
|
-
votes,
|
|
857
|
-
currentResults,
|
|
858
|
-
currentResultsRootSalt,
|
|
859
|
-
currentSpentVoiceCreditSubtotal,
|
|
860
|
-
currentSpentVoiceCreditSubtotalSalt,
|
|
861
811
|
newResultsRootSalt,
|
|
862
812
|
newSpentVoiceCreditSubtotalSalt,
|
|
863
|
-
}
|
|
813
|
+
}, this.mode !== constants_1.EMode.QV
|
|
814
|
+
? [
|
|
815
|
+
...excludedCircuitInputs,
|
|
816
|
+
"currentPerVoteOptionSpentVoiceCredits",
|
|
817
|
+
"currentPerVoteOptionSpentVoiceCreditsRootSalt",
|
|
818
|
+
"newPerVoteOptionSpentVoiceCreditsRootSalt",
|
|
819
|
+
]
|
|
820
|
+
: [...excludedCircuitInputs]));
|
|
864
821
|
this.numBatchesTallied += 1;
|
|
865
822
|
return circuitInputs;
|
|
866
823
|
};
|
|
@@ -869,19 +826,19 @@ class Poll {
|
|
|
869
826
|
*
|
|
870
827
|
* This is the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]).
|
|
871
828
|
* @param salt - The salt used in the hash function.
|
|
872
|
-
* @param
|
|
873
|
-
* @param
|
|
829
|
+
* @param ballotsToCount - The number of ballots to count for the calculation.
|
|
830
|
+
* @param mode - Voting mode, default is QV.
|
|
874
831
|
* @returns Returns the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]).
|
|
875
832
|
*/
|
|
876
|
-
this.
|
|
833
|
+
this.generateSpentVoiceCreditSubtotalCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
|
|
877
834
|
let subtotal = 0n;
|
|
878
|
-
for (let i = 0; i <
|
|
835
|
+
for (let i = 0; i < ballotsToCount; i += 1) {
|
|
879
836
|
if (this.ballots.length <= i) {
|
|
880
837
|
break;
|
|
881
838
|
}
|
|
882
839
|
for (let j = 0; j < this.tallyResult.length; j += 1) {
|
|
883
|
-
const
|
|
884
|
-
subtotal +=
|
|
840
|
+
const vote = BigInt(`${this.ballots[i].votes[j]}`);
|
|
841
|
+
subtotal += mode === constants_1.EMode.QV ? vote * vote : vote;
|
|
885
842
|
}
|
|
886
843
|
}
|
|
887
844
|
return (0, crypto_1.hashLeftRight)(subtotal, salt);
|
|
@@ -891,20 +848,20 @@ class Poll {
|
|
|
891
848
|
*
|
|
892
849
|
* This is the hash of the Merkle root of the spent voice credits per vote option and a salt, computed as Poseidon([root, _salt]).
|
|
893
850
|
* @param salt - The salt used in the hash function.
|
|
894
|
-
* @param
|
|
895
|
-
* @param
|
|
851
|
+
* @param ballotsToCount - The number of ballots to count for the calculation.
|
|
852
|
+
* @param mode - Voting mode, default is QV.
|
|
896
853
|
* @returns Returns the hash of the Merkle root of the spent voice credits per vote option and a salt, computed as Poseidon([root, _salt]).
|
|
897
854
|
*/
|
|
898
|
-
this.
|
|
855
|
+
this.generatePerVoteOptionSpentVoiceCreditsCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
|
|
899
856
|
const leaves = Array(this.tallyResult.length).fill(0n);
|
|
900
|
-
for (let i = 0; i <
|
|
857
|
+
for (let i = 0; i < ballotsToCount; i += 1) {
|
|
901
858
|
// check that is a valid index
|
|
902
859
|
if (i >= this.ballots.length) {
|
|
903
860
|
break;
|
|
904
861
|
}
|
|
905
862
|
for (let j = 0; j < this.tallyResult.length; j += 1) {
|
|
906
|
-
const
|
|
907
|
-
leaves[j] +=
|
|
863
|
+
const vote = this.ballots[i].votes[j];
|
|
864
|
+
leaves[j] += mode === constants_1.EMode.QV ? vote * vote : vote;
|
|
908
865
|
}
|
|
909
866
|
}
|
|
910
867
|
return (0, crypto_1.generateTreeCommitment)(leaves, salt, this.treeDepths.voteOptionTreeDepth);
|
|
@@ -915,33 +872,37 @@ class Poll {
|
|
|
915
872
|
*/
|
|
916
873
|
this.copy = () => {
|
|
917
874
|
const copied = new Poll(BigInt(this.pollEndTimestamp.toString()), this.coordinatorKeypair.copy(), {
|
|
918
|
-
|
|
875
|
+
tallyProcessingStateTreeDepth: Number(this.treeDepths.tallyProcessingStateTreeDepth),
|
|
919
876
|
voteOptionTreeDepth: Number(this.treeDepths.voteOptionTreeDepth),
|
|
920
877
|
stateTreeDepth: Number(this.treeDepths.stateTreeDepth),
|
|
921
878
|
}, {
|
|
922
879
|
tallyBatchSize: Number(this.batchSizes.tallyBatchSize.toString()),
|
|
923
880
|
messageBatchSize: Number(this.batchSizes.messageBatchSize.toString()),
|
|
924
|
-
}, this.maciStateRef, this.voteOptions);
|
|
881
|
+
}, this.maciStateRef, this.voteOptions, this.mode);
|
|
925
882
|
copied.publicKeys = this.publicKeys.map((x) => x.copy());
|
|
926
883
|
copied.pollStateLeaves = this.pollStateLeaves.map((x) => x.copy());
|
|
927
884
|
copied.messages = this.messages.map((x) => x.copy());
|
|
928
885
|
copied.commands = this.commands.map((x) => x.copy());
|
|
929
886
|
copied.ballots = this.ballots.map((x) => x.copy());
|
|
930
887
|
copied.encryptionPublicKeys = this.encryptionPublicKeys.map((x) => x.copy());
|
|
888
|
+
copied.voteCounts = this.voteCounts.map((x) => x.copy());
|
|
931
889
|
if (this.ballotTree) {
|
|
932
890
|
copied.ballotTree = this.ballotTree.copy();
|
|
933
891
|
}
|
|
892
|
+
if (this.voteCountsTree) {
|
|
893
|
+
copied.voteCountsTree = this.voteCountsTree.copy();
|
|
894
|
+
}
|
|
934
895
|
copied.currentMessageBatchIndex = this.currentMessageBatchIndex;
|
|
935
896
|
copied.maciStateRef = this.maciStateRef;
|
|
936
897
|
copied.tallyResult = this.tallyResult.map((x) => BigInt(x.toString()));
|
|
937
898
|
copied.perVoteOptionSpentVoiceCredits = this.perVoteOptionSpentVoiceCredits.map((x) => BigInt(x.toString()));
|
|
938
|
-
copied.
|
|
899
|
+
copied.totalBatchesProcessed = Number(this.totalBatchesProcessed.toString());
|
|
939
900
|
copied.numBatchesTallied = Number(this.numBatchesTallied.toString());
|
|
940
901
|
copied.pollId = this.pollId;
|
|
941
902
|
copied.totalSpentVoiceCredits = BigInt(this.totalSpentVoiceCredits.toString());
|
|
942
903
|
copied.sbSalts = {};
|
|
943
904
|
copied.resultRootSalts = {};
|
|
944
|
-
copied.
|
|
905
|
+
copied.perVoteOptionSpentVoiceCreditsRootSalts = {};
|
|
945
906
|
copied.spentVoiceCreditSubtotalSalts = {};
|
|
946
907
|
Object.keys(this.sbSalts).forEach((k) => {
|
|
947
908
|
copied.sbSalts[k] = BigInt(this.sbSalts[k].toString());
|
|
@@ -949,8 +910,8 @@ class Poll {
|
|
|
949
910
|
Object.keys(this.resultRootSalts).forEach((k) => {
|
|
950
911
|
copied.resultRootSalts[k] = BigInt(this.resultRootSalts[k].toString());
|
|
951
912
|
});
|
|
952
|
-
Object.keys(this.
|
|
953
|
-
copied.
|
|
913
|
+
Object.keys(this.perVoteOptionSpentVoiceCreditsRootSalts).forEach((k) => {
|
|
914
|
+
copied.perVoteOptionSpentVoiceCreditsRootSalts[k] = BigInt(this.perVoteOptionSpentVoiceCreditsRootSalts[k].toString());
|
|
954
915
|
});
|
|
955
916
|
Object.keys(this.spentVoiceCreditSubtotalSalts).forEach((k) => {
|
|
956
917
|
copied.spentVoiceCreditSubtotalSalts[k] = BigInt(this.spentVoiceCreditSubtotalSalts[k].toString());
|
|
@@ -966,7 +927,7 @@ class Poll {
|
|
|
966
927
|
*/
|
|
967
928
|
this.equals = (poll) => {
|
|
968
929
|
const result = this.coordinatorKeypair.equals(poll.coordinatorKeypair) &&
|
|
969
|
-
this.treeDepths.
|
|
930
|
+
this.treeDepths.tallyProcessingStateTreeDepth === poll.treeDepths.tallyProcessingStateTreeDepth &&
|
|
970
931
|
this.treeDepths.voteOptionTreeDepth === poll.treeDepths.voteOptionTreeDepth &&
|
|
971
932
|
this.batchSizes.tallyBatchSize === poll.batchSizes.tallyBatchSize &&
|
|
972
933
|
this.batchSizes.messageBatchSize === poll.batchSizes.messageBatchSize &&
|
|
@@ -1007,26 +968,60 @@ class Poll {
|
|
|
1007
968
|
* Get the number of signups
|
|
1008
969
|
* @returns The number of signups
|
|
1009
970
|
*/
|
|
1010
|
-
this.
|
|
971
|
+
this.getTotalSignups = () => this.totalSignups;
|
|
972
|
+
if (voteOptions > constants_1.VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth) {
|
|
973
|
+
throw new Error("Vote options cannot be greater than the number of leaves in the vote option tree");
|
|
974
|
+
}
|
|
1011
975
|
this.pollEndTimestamp = pollEndTimestamp;
|
|
1012
976
|
this.coordinatorKeypair = coordinatorKeypair;
|
|
1013
977
|
this.treeDepths = treeDepths;
|
|
1014
978
|
this.batchSizes = batchSizes;
|
|
1015
|
-
if (voteOptions > constants_1.VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth) {
|
|
1016
|
-
throw new Error("Vote options cannot be greater than the number of leaves in the vote option tree");
|
|
1017
|
-
}
|
|
1018
979
|
this.voteOptions = voteOptions;
|
|
1019
980
|
this.maxVoteOptions = constants_1.VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth;
|
|
1020
981
|
this.maciStateRef = maciStateRef;
|
|
1021
982
|
this.pollId = BigInt(maciStateRef.polls.size);
|
|
1022
983
|
this.actualStateTreeDepth = treeDepths.stateTreeDepth;
|
|
1023
984
|
this.currentMessageBatchIndex = 0;
|
|
985
|
+
this.mode = mode;
|
|
1024
986
|
this.pollNullifiers = new Map();
|
|
1025
987
|
this.tallyResult = new Array(this.maxVoteOptions).fill(0n);
|
|
1026
988
|
this.perVoteOptionSpentVoiceCredits = new Array(this.maxVoteOptions).fill(0n);
|
|
1027
989
|
// we put a blank state leaf to prevent a DoS attack
|
|
1028
|
-
this.emptyBallot = domainobjs_1.Ballot.
|
|
990
|
+
this.emptyBallot = domainobjs_1.Ballot.generateBlank(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
|
|
1029
991
|
this.ballots.push(this.emptyBallot);
|
|
992
|
+
this.emptyBallotHash = this.emptyBallot.hash();
|
|
993
|
+
this.emptyVoteCounts = domainobjs_1.VoteCounts.generateBlank(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
|
|
994
|
+
this.voteCounts.push(this.emptyVoteCounts);
|
|
995
|
+
this.emptyVoteCountsHash = this.emptyVoteCounts.hash();
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Get voice credits left for the voting command.
|
|
999
|
+
*
|
|
1000
|
+
* @param args - arguments for getting voice credits
|
|
1001
|
+
* @returns voice credits left
|
|
1002
|
+
*/
|
|
1003
|
+
getVoiceCreditsLeft({ stateLeaf, originalVoteWeight, newVoteWeight, mode }) {
|
|
1004
|
+
switch (mode) {
|
|
1005
|
+
case constants_1.EMode.QV: {
|
|
1006
|
+
// the voice credits left are:
|
|
1007
|
+
// voiceCreditsBalance (how many the user has) +
|
|
1008
|
+
// voiceCreditsPreviouslySpent (the original vote weight for this option) ** 2 -
|
|
1009
|
+
// command.newVoteWeight ** 2 (the new vote weight squared)
|
|
1010
|
+
// basically we are replacing the previous vote weight for this
|
|
1011
|
+
// particular vote option with the new one
|
|
1012
|
+
// but we need to ensure that we are not going >= balance
|
|
1013
|
+
return stateLeaf.voiceCreditBalance + originalVoteWeight * originalVoteWeight - newVoteWeight * newVoteWeight;
|
|
1014
|
+
}
|
|
1015
|
+
case constants_1.EMode.NON_QV:
|
|
1016
|
+
case constants_1.EMode.FULL: {
|
|
1017
|
+
// for non quadratic voting, we simply remove the exponentiation
|
|
1018
|
+
// for full credits voting, it will be zero
|
|
1019
|
+
return stateLeaf.voiceCreditBalance + originalVoteWeight - newVoteWeight;
|
|
1020
|
+
}
|
|
1021
|
+
default: {
|
|
1022
|
+
throw new Error("Voting mode is not supported");
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1030
1025
|
}
|
|
1031
1026
|
/**
|
|
1032
1027
|
* Serialize the Poll object to a JSON object
|
|
@@ -1043,16 +1038,18 @@ class Poll {
|
|
|
1043
1038
|
messages: this.messages.map((message) => message.toJSON()),
|
|
1044
1039
|
commands: this.commands.map((command) => command.toJSON()),
|
|
1045
1040
|
ballots: this.ballots.map((ballot) => ballot.toJSON()),
|
|
1041
|
+
voteCounts: this.voteCounts.map((voteCounts) => voteCounts.toJSON()),
|
|
1046
1042
|
encryptionPublicKeys: this.encryptionPublicKeys.map((encryptionPublicKey) => encryptionPublicKey.serialize()),
|
|
1047
1043
|
currentMessageBatchIndex: this.currentMessageBatchIndex,
|
|
1048
1044
|
publicKeys: this.publicKeys.map((leaf) => leaf.toJSON()),
|
|
1049
1045
|
pollStateLeaves: this.pollStateLeaves.map((leaf) => leaf.toJSON()),
|
|
1050
1046
|
results: this.tallyResult.map((result) => result.toString()),
|
|
1051
|
-
|
|
1047
|
+
totalBatchesProcessed: this.totalBatchesProcessed,
|
|
1052
1048
|
totalSignups: this.totalSignups.toString(),
|
|
1053
1049
|
chainHash: this.chainHash.toString(),
|
|
1054
1050
|
pollNullifiers: [...this.pollNullifiers.keys()].map((nullifier) => nullifier.toString()),
|
|
1055
1051
|
batchHashes: this.batchHashes.map((batchHash) => batchHash.toString()),
|
|
1052
|
+
mode: this.mode,
|
|
1056
1053
|
};
|
|
1057
1054
|
}
|
|
1058
1055
|
/**
|
|
@@ -1062,16 +1059,17 @@ class Poll {
|
|
|
1062
1059
|
* @returns a new Poll instance
|
|
1063
1060
|
*/
|
|
1064
1061
|
static fromJSON(json, maciState) {
|
|
1065
|
-
const poll = new Poll(BigInt(json.pollEndTimestamp), new domainobjs_1.Keypair(), json.treeDepths, json.batchSizes, maciState, BigInt(json.voteOptions));
|
|
1062
|
+
const poll = new Poll(BigInt(json.pollEndTimestamp), new domainobjs_1.Keypair(), json.treeDepths, json.batchSizes, maciState, BigInt(json.voteOptions), json.mode);
|
|
1066
1063
|
// set all properties
|
|
1067
1064
|
poll.pollStateLeaves = json.pollStateLeaves.map((leaf) => domainobjs_1.StateLeaf.fromJSON(leaf));
|
|
1068
1065
|
poll.ballots = json.ballots.map((ballot) => domainobjs_1.Ballot.fromJSON(ballot));
|
|
1066
|
+
poll.voteCounts = json.voteCounts.map((voteCounts) => domainobjs_1.VoteCounts.fromJSON(voteCounts));
|
|
1069
1067
|
poll.encryptionPublicKeys = json.encryptionPublicKeys.map((key) => domainobjs_1.PublicKey.deserialize(key));
|
|
1070
1068
|
poll.messages = json.messages.map((message) => domainobjs_1.Message.fromJSON(message));
|
|
1071
1069
|
poll.commands = json.commands.map((command) => domainobjs_1.VoteCommand.fromJSON(command));
|
|
1072
1070
|
poll.tallyResult = json.results.map((result) => BigInt(result));
|
|
1073
1071
|
poll.currentMessageBatchIndex = json.currentMessageBatchIndex;
|
|
1074
|
-
poll.
|
|
1072
|
+
poll.totalBatchesProcessed = json.totalBatchesProcessed;
|
|
1075
1073
|
poll.chainHash = BigInt(json.chainHash);
|
|
1076
1074
|
poll.batchHashes = json.batchHashes.map((batchHash) => BigInt(batchHash));
|
|
1077
1075
|
poll.pollNullifiers = new Map(json.pollNullifiers.map((nullifier) => [BigInt(nullifier), true]));
|