@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/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.numBatchesProcessed = 0;
36
+ this.totalBatchesProcessed = 0;
35
37
  this.sbSalts = {};
36
38
  this.resultRootSalts = {};
37
- this.preVOSpentVoiceCreditsRootSalts = {};
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, qv = true) => {
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
- // the voice credits left are:
152
- // voiceCreditsBalance (how many the user has) +
153
- // voiceCreditsPreviouslySpent (the original vote weight for this option) ** 2 -
154
- // command.newVoteWeight ** 2 (the new vote weight squared)
155
- // basically we are replacing the previous vote weight for this
156
- // particular vote option with the new one
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.genProof(Number(stateLeafIndex));
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.genProof(Number(stateLeafIndex));
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.genProof(voteOptionIndex);
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
- indices,
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.genProof(Number(stateLeafIndex));
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
- pathIndices: pathIndices.map((item) => item.toString()),
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.numBatchesProcessed < totalBatches;
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, qv = true, quiet = true) => {
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.numBatchesProcessed === 0) {
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, qv);
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.genProof(Number(stateLeafIndex)).pathElements);
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.genProof(Number(stateLeafIndex)).pathElements);
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.genProof(Number(command.voteOptionIndex)).pathElements);
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.genProof(0).pathElements);
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.genProof(0).pathElements);
492
+ currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(0).pathElements);
494
493
  currentBallots.unshift(this.ballots[0].copy());
495
- currentBallotsPathElements.unshift(this.ballotTree.genProof(0).pathElements);
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.genProof(0).pathElements);
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.genProof(0).pathElements);
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.genProof(0).pathElements);
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.genProof(0).pathElements);
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.numBatchesProcessed += 1;
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.numBatchesProcessed * batchSize >= this.messages.length) {
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
- * @returns the circuit inputs for the TallyVotes circuit.
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.preVOSpentVoiceCreditsRootSalts[batchStartIndex - batchSize];
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 currentPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment(currentPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex, true);
682
+ const currentPerVoteOptionSpentVoiceCreditsCommitment = this.generatePerVoteOptionSpentVoiceCreditsCommitment(currentPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex, this.mode);
683
683
  // generate a commitment to the current spent voice credits
684
- const currentSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, true);
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
- const currentTallyCommitment = batchStartIndex === 0
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
- currentPerVOSpentVoiceCreditsCommitment,
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 = this.numBatchesTallied * batchSize; i < this.numBatchesTallied * batchSize + batchSize; i += 1) {
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 v = this.ballots[i].votes[j];
714
- // the vote itself will be a quadratic vote (sqrt(voiceCredits))
715
- this.tallyResult[j] += v;
716
- // the per vote option spent voice credits will be the sum of the squares of the votes
717
- this.perVoteOptionSpentVoiceCredits[j] += v * v;
718
- // the total spent voice credits will be the sum of the squares of the votes
719
- this.totalSpentVoiceCredits += v * v;
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.preVOSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVoteOptionSpentVoiceCreditsRootSalt;
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.genSpentVoiceCreditSubtotalCommitment(newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, true);
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 newPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment(newPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex + batchSize, true);
772
+ const newPerVoteOptionSpentVoiceCreditsCommitment = this.generatePerVoteOptionSpentVoiceCreditsCommitment(newPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex + batchSize, this.mode);
741
773
  // generate the new tally commitment
742
- const newTallyCommitment = (0, crypto_1.hash3)([
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?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize);
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
- const circuitInputs = (0, crypto_1.stringifyBigInts)({
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 numBallotsToCount - The number of ballots to count for the calculation.
873
- * @param useQuadraticVoting - Whether to use quadratic voting or not. Default is true.
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.genSpentVoiceCreditSubtotalCommitment = (salt, numBallotsToCount, useQuadraticVoting = true) => {
833
+ this.generateSpentVoiceCreditSubtotalCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
877
834
  let subtotal = 0n;
878
- for (let i = 0; i < numBallotsToCount; i += 1) {
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 v = BigInt(`${this.ballots[i].votes[j]}`);
884
- subtotal += useQuadraticVoting ? v * v : v;
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 numBallotsToCount - The number of ballots to count for the calculation.
895
- * @param useQuadraticVoting - Whether to use quadratic voting or not. Default is true.
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.genPerVOSpentVoiceCreditsCommitment = (salt, numBallotsToCount, useQuadraticVoting = true) => {
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 < numBallotsToCount; i += 1) {
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 v = this.ballots[i].votes[j];
907
- leaves[j] += useQuadraticVoting ? v * v : v;
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
- intStateTreeDepth: Number(this.treeDepths.intStateTreeDepth),
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.numBatchesProcessed = Number(this.numBatchesProcessed.toString());
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.preVOSpentVoiceCreditsRootSalts = {};
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.preVOSpentVoiceCreditsRootSalts).forEach((k) => {
953
- copied.preVOSpentVoiceCreditsRootSalts[k] = BigInt(this.preVOSpentVoiceCreditsRootSalts[k].toString());
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.intStateTreeDepth === poll.treeDepths.intStateTreeDepth &&
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.gettotalSignups = () => this.totalSignups;
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.genBlankBallot(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
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
- numBatchesProcessed: this.numBatchesProcessed,
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.numBatchesProcessed = json.numBatchesProcessed;
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]));