@maci-protocol/core 0.0.0-ci.ba71b1e → 0.0.0-ci.bd42e06

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,7 +24,7 @@ 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 = [];
28
29
  this.messages = [];
29
30
  this.commands = [];
@@ -34,7 +35,7 @@ class Poll {
34
35
  this.numBatchesProcessed = 0;
35
36
  this.sbSalts = {};
36
37
  this.resultRootSalts = {};
37
- this.preVOSpentVoiceCreditsRootSalts = {};
38
+ this.perVoteOptionSpentVoiceCreditsRootSalts = {};
38
39
  this.spentVoiceCreditSubtotalSalts = {};
39
40
  // For vote tallying
40
41
  this.tallyResult = [];
@@ -86,7 +87,7 @@ class Poll {
86
87
  // not match. For this, we must only copy up to the number of signups
87
88
  // Copy the state tree, ballot tree, state leaves, and ballot leaves
88
89
  // start by setting the number of signups
89
- this.settotalSignups(totalSignups);
90
+ this.setTotalSignups(totalSignups);
90
91
  // copy up to totalSignups state leaves
91
92
  this.publicKeys = this.maciStateRef.publicKeys.slice(0, Number(this.totalSignups)).map((x) => x.copy());
92
93
  // ensure we have the correct actual state tree depth value
@@ -118,11 +119,11 @@ class Poll {
118
119
  * @param encryptionPublicKey - The public key associated with the encryption private key.
119
120
  * @returns A number of variables which will be used in the zk-SNARK circuit.
120
121
  */
121
- this.processMessage = (message, encryptionPublicKey, qv = true) => {
122
+ this.processMessage = (message, encryptionPublicKey) => {
122
123
  try {
123
124
  // Decrypt the message
124
125
  const sharedKey = domainobjs_1.Keypair.generateEcdhSharedKey(this.coordinatorKeypair.privateKey, encryptionPublicKey);
125
- const { command, signature } = domainobjs_1.PCommand.decrypt(message, sharedKey);
126
+ const { command, signature } = domainobjs_1.VoteCommand.decrypt(message, sharedKey);
126
127
  const stateLeafIndex = command.stateIndex;
127
128
  // If the state tree index in the command is invalid, do nothing
128
129
  if (stateLeafIndex >= BigInt(this.ballots.length) ||
@@ -148,24 +149,20 @@ class Poll {
148
149
  }
149
150
  const voteOptionIndex = Number(command.voteOptionIndex);
150
151
  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;
152
+ const voiceCreditsLeft = this.getVoiceCreditsLeft({
153
+ stateLeaf,
154
+ originalVoteWeight,
155
+ newVoteWeight: command.newVoteWeight,
156
+ mode: this.mode,
157
+ });
165
158
  // If the remaining voice credits is insufficient, do nothing
166
159
  if (voiceCreditsLeft < 0n) {
167
160
  throw new errors_1.ProcessMessageError(errors_1.ProcessMessageErrors.InsufficientVoiceCredits);
168
161
  }
162
+ // If there are some voice credits left for full spent mode, do nothing
163
+ if (this.mode === constants_1.EMode.FULL && voiceCreditsLeft > 0n) {
164
+ throw new errors_1.ProcessMessageError(errors_1.ProcessMessageErrors.InvalidVoiceCredits);
165
+ }
169
166
  // Deep-copy the state leaf and update its attributes
170
167
  const newStateLeaf = stateLeaf.copy();
171
168
  newStateLeaf.voiceCreditBalance = voiceCreditsLeft;
@@ -177,20 +174,23 @@ class Poll {
177
174
  newBallot.nonce += 1n;
178
175
  // we change the vote for this exact vote option
179
176
  newBallot.votes[voteOptionIndex] = command.newVoteWeight;
177
+ if (this.mode === constants_1.EMode.FULL) {
178
+ newBallot.votes = newBallot.votes.map((votes, index) => (voteOptionIndex === index ? votes : 0n));
179
+ }
180
180
  // calculate the path elements for the state tree given the original state tree (before any changes)
181
181
  // changes could effectively be made by this new vote - either a key change or vote change
182
182
  // would result in a different state leaf
183
- const originalStateLeafPathElements = this.pollStateTree?.genProof(Number(stateLeafIndex)).pathElements;
183
+ const { pathElements: originalStateLeafPathElements } = this.pollStateTree.genProof(Number(stateLeafIndex));
184
184
  // calculate the path elements for the ballot tree given the original ballot tree (before any changes)
185
185
  // changes could effectively be made by this new ballot
186
- const originalBallotPathElements = this.ballotTree?.genProof(Number(stateLeafIndex)).pathElements;
186
+ const { pathElements: originalBallotPathElements } = this.ballotTree.genProof(Number(stateLeafIndex));
187
187
  // create a new quinary tree where we insert the votes of the origin (up until this message is processed) ballot
188
- const vt = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
188
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
189
189
  for (let i = 0; i < this.ballots[0].votes.length; i += 1) {
190
- vt.insert(ballot.votes[i]);
190
+ voteTree.insert(ballot.votes[i]);
191
191
  }
192
192
  // calculate the path elements for the vote option tree given the original vote option tree (before any changes)
193
- const originalVoteWeightsPathElements = vt.genProof(voteOptionIndex).pathElements;
193
+ const { pathElements: originalVoteWeightsPathElements } = voteTree.genProof(voteOptionIndex);
194
194
  // we return the data which is then to be used in the processMessage circuit
195
195
  // to generate a proof of processing
196
196
  return {
@@ -239,14 +239,14 @@ class Poll {
239
239
  const sharedKey = domainobjs_1.Keypair.generateEcdhSharedKey(this.coordinatorKeypair.privateKey, encryptionPublicKey);
240
240
  try {
241
241
  // step 2. we decrypt it
242
- const { command } = domainobjs_1.PCommand.decrypt(message, sharedKey);
242
+ const { command } = domainobjs_1.VoteCommand.decrypt(message, sharedKey);
243
243
  // step 3. we store it in the commands array
244
244
  this.commands.push(command);
245
245
  }
246
246
  catch (e) {
247
247
  // if there is an error we store an empty command
248
- const keyPair = new domainobjs_1.Keypair();
249
- const command = new domainobjs_1.PCommand(0n, keyPair.publicKey, 0n, 0n, 0n, 0n, 0n);
248
+ const keypair = new domainobjs_1.Keypair();
249
+ const command = new domainobjs_1.VoteCommand(0n, keypair.publicKey, 0n, 0n, 0n, 0n, 0n);
250
250
  this.commands.push(command);
251
251
  }
252
252
  };
@@ -362,7 +362,7 @@ class Poll {
362
362
  * @param quiet - Whether to log errors or not
363
363
  * @returns stringified circuit inputs
364
364
  */
365
- this.processMessages = (pollId, qv = true, quiet = true) => {
365
+ this.processMessages = (pollId, quiet = true) => {
366
366
  (0, assert_1.default)(this.hasUnprocessedMessages(), "No more messages to process");
367
367
  const batchSize = this.batchSizes.messageBatchSize;
368
368
  if (this.numBatchesProcessed === 0) {
@@ -411,23 +411,23 @@ class Poll {
411
411
  encryptionPublicKey = this.encryptionPublicKeys[idx];
412
412
  try {
413
413
  // check if the command is valid
414
- const r = this.processMessage(message, encryptionPublicKey, qv);
415
- const index = r.stateLeafIndex;
414
+ const { stateLeafIndex, originalStateLeaf, originalBallot, originalVoteWeight, originalVoteWeightsPathElements, originalStateLeafPathElements, originalBallotPathElements, newStateLeaf, newBallot, } = this.processMessage(message, encryptionPublicKey);
415
+ const index = stateLeafIndex;
416
416
  // we add at position 0 the original data
417
- currentStateLeaves.unshift(r.originalStateLeaf);
418
- currentBallots.unshift(r.originalBallot);
419
- currentVoteWeights.unshift(r.originalVoteWeight);
420
- currentVoteWeightsPathElements.unshift(r.originalVoteWeightsPathElements);
421
- currentStateLeavesPathElements.unshift(r.originalStateLeafPathElements);
422
- currentBallotsPathElements.unshift(r.originalBallotPathElements);
417
+ currentStateLeaves.unshift(originalStateLeaf);
418
+ currentBallots.unshift(originalBallot);
419
+ currentVoteWeights.unshift(originalVoteWeight);
420
+ currentVoteWeightsPathElements.unshift(originalVoteWeightsPathElements);
421
+ currentStateLeavesPathElements.unshift(originalStateLeafPathElements);
422
+ currentBallotsPathElements.unshift(originalBallotPathElements);
423
423
  // update the state leaves with the new state leaf (result of processing the message)
424
- this.pollStateLeaves[index] = r.newStateLeaf.copy();
424
+ this.pollStateLeaves[index] = newStateLeaf.copy();
425
425
  // we also update the state tree with the hash of the new state leaf
426
- this.pollStateTree?.update(index, r.newStateLeaf.hash());
426
+ this.pollStateTree?.update(index, newStateLeaf.hash());
427
427
  // store the new ballot
428
- this.ballots[index] = r.newBallot;
428
+ this.ballots[index] = newBallot;
429
429
  // update the ballot tree
430
- this.ballotTree?.update(index, r.newBallot.hash());
430
+ this.ballotTree?.update(index, newBallot.hash());
431
431
  }
432
432
  catch (e) {
433
433
  // if the error is not a ProcessMessageError we throw it and exit here
@@ -449,7 +449,7 @@ class Poll {
449
449
  // generate shared key
450
450
  const sharedKey = domainobjs_1.Keypair.generateEcdhSharedKey(this.coordinatorKeypair.privateKey, encryptionPublicKey);
451
451
  // force decrypt it
452
- const { command } = domainobjs_1.PCommand.decrypt(message, sharedKey, true);
452
+ const { command } = domainobjs_1.VoteCommand.decrypt(message, sharedKey, true);
453
453
  // cache state leaf index
454
454
  const stateLeafIndex = command.stateIndex;
455
455
  // if the state leaf index is valid then use it
@@ -467,24 +467,24 @@ class Poll {
467
467
  if (command.voteOptionIndex < this.voteOptions) {
468
468
  currentVoteWeights.unshift(ballot.votes[Number(command.voteOptionIndex)]);
469
469
  // create a new quinary tree and add all votes we have so far
470
- const vt = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
470
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
471
471
  // fill the vote option tree with the votes we have so far
472
472
  for (let j = 0; j < this.ballots[0].votes.length; j += 1) {
473
- vt.insert(ballot.votes[j]);
473
+ voteTree.insert(ballot.votes[j]);
474
474
  }
475
475
  // get the path elements for the first vote leaf
476
- currentVoteWeightsPathElements.unshift(vt.genProof(Number(command.voteOptionIndex)).pathElements);
476
+ currentVoteWeightsPathElements.unshift(voteTree.genProof(Number(command.voteOptionIndex)).pathElements);
477
477
  }
478
478
  else {
479
479
  currentVoteWeights.unshift(ballot.votes[0]);
480
480
  // create a new quinary tree and add all votes we have so far
481
- const vt = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
481
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
482
482
  // fill the vote option tree with the votes we have so far
483
483
  for (let j = 0; j < this.ballots[0].votes.length; j += 1) {
484
- vt.insert(ballot.votes[j]);
484
+ voteTree.insert(ballot.votes[j]);
485
485
  }
486
486
  // get the path elements for the first vote leaf
487
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
487
+ currentVoteWeightsPathElements.unshift(voteTree.genProof(0).pathElements);
488
488
  }
489
489
  }
490
490
  else {
@@ -496,10 +496,10 @@ class Poll {
496
496
  // Since the command is invalid, we use a zero vote weight
497
497
  currentVoteWeights.unshift(this.ballots[0].votes[0]);
498
498
  // create a new quinary tree and add an empty vote
499
- const vt = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
500
- vt.insert(this.ballots[0].votes[0]);
499
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
500
+ voteTree.insert(this.ballots[0].votes[0]);
501
501
  // get the path elements for this empty vote weight leaf
502
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
502
+ currentVoteWeightsPathElements.unshift(voteTree.genProof(0).pathElements);
503
503
  }
504
504
  }
505
505
  else {
@@ -517,10 +517,10 @@ class Poll {
517
517
  // Since the command is invalid, we use a zero vote weight
518
518
  currentVoteWeights.unshift(this.ballots[0].votes[0]);
519
519
  // create a new quinary tree and add an empty vote
520
- const vt = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
521
- vt.insert(this.ballots[0].votes[0]);
520
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
521
+ voteTree.insert(this.ballots[0].votes[0]);
522
522
  // get the path elements for this empty vote weight leaf
523
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
523
+ currentVoteWeightsPathElements.unshift(voteTree.genProof(0).pathElements);
524
524
  }
525
525
  }
526
526
  // store the data in the circuit inputs object
@@ -585,14 +585,14 @@ class Poll {
585
585
  // generate ecdh key
586
586
  const ecdh = domainobjs_1.Keypair.generateEcdhSharedKey(key.privateKey, this.coordinatorKeypair.publicKey);
587
587
  // create an empty command with state index 0n
588
- const emptyCommand = new domainobjs_1.PCommand(0n, key.publicKey, 0n, 0n, 0n, 0n, 0n);
588
+ const emptyCommand = new domainobjs_1.VoteCommand(0n, key.publicKey, 0n, 0n, 0n, 0n, 0n);
589
589
  // encrypt it
590
- const msg = emptyCommand.encrypt(emptyCommand.sign(key.privateKey), ecdh);
590
+ const emptyMessage = emptyCommand.encrypt(emptyCommand.sign(key.privateKey), ecdh);
591
591
  // copy the messages to a new array
592
592
  let messages = this.messages.map((x) => x.asCircuitInputs());
593
593
  // pad with our state index 0 message
594
594
  while (messages.length % messageBatchSize > 0) {
595
- messages.push(msg.asCircuitInputs());
595
+ messages.push(emptyMessage.asCircuitInputs());
596
596
  }
597
597
  // copy the public keys, pad the array with the last keys if needed
598
598
  let encryptionPublicKeys = this.encryptionPublicKeys.map((x) => x.copy());
@@ -661,6 +661,7 @@ class Poll {
661
661
  this.hasUntalliedBallots = () => this.numBatchesTallied * this.batchSizes.tallyBatchSize < this.ballots.length;
662
662
  /**
663
663
  * This method tallies a ballots and updates the tally results.
664
+ *
664
665
  * @returns the circuit inputs for the TallyVotes circuit.
665
666
  */
666
667
  this.tallyVotes = () => {
@@ -674,31 +675,40 @@ class Poll {
674
675
  const batchStartIndex = this.numBatchesTallied * batchSize;
675
676
  // get the salts needed for the commitments
676
677
  const currentResultsRootSalt = batchStartIndex === 0 ? 0n : this.resultRootSalts[batchStartIndex - batchSize];
677
- const currentPerVOSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? 0n : this.preVOSpentVoiceCreditsRootSalts[batchStartIndex - batchSize];
678
+ const currentPerVoteOptionSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? 0n : this.perVoteOptionSpentVoiceCreditsRootSalts[batchStartIndex - batchSize];
678
679
  const currentSpentVoiceCreditSubtotalSalt = batchStartIndex === 0 ? 0n : this.spentVoiceCreditSubtotalSalts[batchStartIndex - batchSize];
679
680
  // generate a commitment to the current results
680
681
  const currentResultsCommitment = (0, crypto_1.generateTreeCommitment)(this.tallyResult, currentResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
681
682
  // generate a commitment to the current per vote option spent voice credits
682
- const currentPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment(currentPerVOSpentVoiceCreditsRootSalt, batchStartIndex, true);
683
+ const currentPerVoteOptionSpentVoiceCreditsCommitment = this.generatePerVoteOptionSpentVoiceCreditsCommitment(currentPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex, this.mode);
683
684
  // generate a commitment to the current spent voice credits
684
- const currentSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, true);
685
+ const currentSpentVoiceCreditsCommitment = this.generateSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, this.mode);
685
686
  // the current commitment for the first batch will be 0
686
687
  // otherwise calculate as
687
688
  // hash([
688
689
  // currentResultsCommitment,
689
690
  // currentSpentVoiceCreditsCommitment,
690
- // currentPerVOSpentVoiceCreditsCommitment
691
691
  // ])
692
- const currentTallyCommitment = batchStartIndex === 0
692
+ // or for QV
693
+ // hash([
694
+ // currentResultsCommitment,
695
+ // currentSpentVoiceCreditsCommitment,
696
+ // currentPerVoteOptionSpentVoiceCreditsCommitment
697
+ // ])
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;
699
709
  const ballots = [];
700
710
  const currentResults = this.tallyResult.map((x) => BigInt(x.toString()));
701
- const currentPerVOSpentVoiceCredits = this.perVoteOptionSpentVoiceCredits.map((x) => BigInt(x.toString()));
711
+ const currentPerVoteOptionSpentVoiceCredits = this.perVoteOptionSpentVoiceCredits.map((x) => BigInt(x.toString()));
702
712
  const currentSpentVoiceCreditSubtotal = BigInt(this.totalSpentVoiceCredits.toString());
703
713
  // loop in normal order to tally the ballots one by one
704
714
  for (let i = this.numBatchesTallied * batchSize; i < this.numBatchesTallied * batchSize + batchSize; i += 1) {
@@ -711,12 +721,17 @@ class Poll {
711
721
  // for each possible vote option we loop and calculate
712
722
  for (let j = 0; j < this.maxVoteOptions; j += 1) {
713
723
  const v = this.ballots[i].votes[j];
714
- // the vote itself will be a quadratic vote (sqrt(voiceCredits))
715
724
  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;
725
+ if (this.mode === constants_1.EMode.QV) {
726
+ // the per vote option spent voice credits will be the sum of the squares of the votes
727
+ this.perVoteOptionSpentVoiceCredits[j] += v * v;
728
+ // the total spent voice credits will be the sum of the squares of the votes
729
+ this.totalSpentVoiceCredits += v * v;
730
+ }
731
+ else {
732
+ // the total spent voice credits will be the sum of the votes
733
+ this.totalSpentVoiceCredits += v;
734
+ }
720
735
  }
721
736
  }
722
737
  const emptyBallot = new domainobjs_1.Ballot(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
@@ -726,24 +741,22 @@ class Poll {
726
741
  }
727
742
  // generate the new salts
728
743
  const newResultsRootSalt = (0, crypto_1.generateRandomSalt)();
729
- const newPerVOSpentVoiceCreditsRootSalt = (0, crypto_1.generateRandomSalt)();
744
+ const newPerVoteOptionSpentVoiceCreditsRootSalt = (0, crypto_1.generateRandomSalt)();
730
745
  const newSpentVoiceCreditSubtotalSalt = (0, crypto_1.generateRandomSalt)();
731
746
  // and save them to be used in the next batch
732
747
  this.resultRootSalts[batchStartIndex] = newResultsRootSalt;
733
- this.preVOSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVOSpentVoiceCreditsRootSalt;
748
+ this.perVoteOptionSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVoteOptionSpentVoiceCreditsRootSalt;
734
749
  this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt;
735
750
  // generate the new results commitment with the new salts and data
736
751
  const newResultsCommitment = (0, crypto_1.generateTreeCommitment)(this.tallyResult, newResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
737
752
  // generate the new spent voice credits commitment with the new salts and data
738
- const newSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, true);
753
+ const newSpentVoiceCreditsCommitment = this.generateSpentVoiceCreditSubtotalCommitment(newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, this.mode);
739
754
  // generate the new per vote option spent voice credits commitment with the new salts and data
740
- const newPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment(newPerVOSpentVoiceCreditsRootSalt, batchStartIndex + batchSize, true);
755
+ const newPerVoteOptionSpentVoiceCreditsCommitment = this.generatePerVoteOptionSpentVoiceCreditsCommitment(newPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex + batchSize, this.mode);
741
756
  // generate the new tally commitment
742
- const newTallyCommitment = (0, crypto_1.hash3)([
743
- newResultsCommitment,
744
- newSpentVoiceCreditsCommitment,
745
- newPerVOSpentVoiceCreditsCommitment,
746
- ]);
757
+ const newTallyCommitment = this.mode === constants_1.EMode.QV
758
+ ? (0, crypto_1.hash3)([newResultsCommitment, newSpentVoiceCreditsCommitment, newPerVoteOptionSpentVoiceCreditsCommitment])
759
+ : (0, crypto_1.hashLeftRight)(newResultsCommitment, newSpentVoiceCreditsCommitment);
747
760
  // cache vars
748
761
  const stateRoot = this.pollStateTree.root;
749
762
  const ballotRoot = this.ballotTree.root;
@@ -751,7 +764,7 @@ class Poll {
751
764
  const sbCommitment = (0, crypto_1.hash3)([stateRoot, ballotRoot, sbSalt]);
752
765
  const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize);
753
766
  const votes = ballots.map((x) => x.votes);
754
- const circuitInputs = (0, crypto_1.stringifyBigInts)({
767
+ const circuitInputs = (0, crypto_1.stringifyBigInts)((0, omit_1.default)({
755
768
  stateRoot,
756
769
  ballotRoot,
757
770
  sbSalt,
@@ -767,100 +780,18 @@ class Poll {
767
780
  currentResultsRootSalt,
768
781
  currentSpentVoiceCreditSubtotal,
769
782
  currentSpentVoiceCreditSubtotalSalt,
770
- currentPerVOSpentVoiceCredits,
771
- currentPerVOSpentVoiceCreditsRootSalt,
783
+ currentPerVoteOptionSpentVoiceCredits,
784
+ currentPerVoteOptionSpentVoiceCreditsRootSalt,
785
+ newPerVoteOptionSpentVoiceCreditsRootSalt,
772
786
  newResultsRootSalt,
773
- newPerVOSpentVoiceCreditsRootSalt,
774
787
  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
- newResultsRootSalt,
862
- newSpentVoiceCreditSubtotalSalt,
863
- });
788
+ }, this.mode !== constants_1.EMode.QV
789
+ ? [
790
+ "currentPerVoteOptionSpentVoiceCredits",
791
+ "currentPerVoteOptionSpentVoiceCreditsRootSalt",
792
+ "newPerVoteOptionSpentVoiceCreditsRootSalt",
793
+ ]
794
+ : []));
864
795
  this.numBatchesTallied += 1;
865
796
  return circuitInputs;
866
797
  };
@@ -869,19 +800,19 @@ class Poll {
869
800
  *
870
801
  * This is the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]).
871
802
  * @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.
803
+ * @param ballotsToCount - The number of ballots to count for the calculation.
804
+ * @param mode - Voting mode, default QV.
874
805
  * @returns Returns the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]).
875
806
  */
876
- this.genSpentVoiceCreditSubtotalCommitment = (salt, numBallotsToCount, useQuadraticVoting = true) => {
807
+ this.generateSpentVoiceCreditSubtotalCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
877
808
  let subtotal = 0n;
878
- for (let i = 0; i < numBallotsToCount; i += 1) {
809
+ for (let i = 0; i < ballotsToCount; i += 1) {
879
810
  if (this.ballots.length <= i) {
880
811
  break;
881
812
  }
882
813
  for (let j = 0; j < this.tallyResult.length; j += 1) {
883
814
  const v = BigInt(`${this.ballots[i].votes[j]}`);
884
- subtotal += useQuadraticVoting ? v * v : v;
815
+ subtotal += mode === constants_1.EMode.QV ? v * v : v;
885
816
  }
886
817
  }
887
818
  return (0, crypto_1.hashLeftRight)(subtotal, salt);
@@ -891,20 +822,20 @@ class Poll {
891
822
  *
892
823
  * 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
824
  * @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.
825
+ * @param ballotsToCount - The number of ballots to count for the calculation.
826
+ * @param mode - Voting mode, default is QV.
896
827
  * @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
828
  */
898
- this.genPerVOSpentVoiceCreditsCommitment = (salt, numBallotsToCount, useQuadraticVoting = true) => {
829
+ this.generatePerVoteOptionSpentVoiceCreditsCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
899
830
  const leaves = Array(this.tallyResult.length).fill(0n);
900
- for (let i = 0; i < numBallotsToCount; i += 1) {
831
+ for (let i = 0; i < ballotsToCount; i += 1) {
901
832
  // check that is a valid index
902
833
  if (i >= this.ballots.length) {
903
834
  break;
904
835
  }
905
836
  for (let j = 0; j < this.tallyResult.length; j += 1) {
906
837
  const v = this.ballots[i].votes[j];
907
- leaves[j] += useQuadraticVoting ? v * v : v;
838
+ leaves[j] += mode === constants_1.EMode.QV ? v * v : v;
908
839
  }
909
840
  }
910
841
  return (0, crypto_1.generateTreeCommitment)(leaves, salt, this.treeDepths.voteOptionTreeDepth);
@@ -915,13 +846,13 @@ class Poll {
915
846
  */
916
847
  this.copy = () => {
917
848
  const copied = new Poll(BigInt(this.pollEndTimestamp.toString()), this.coordinatorKeypair.copy(), {
918
- intStateTreeDepth: Number(this.treeDepths.intStateTreeDepth),
849
+ tallyProcessingStateTreeDepth: Number(this.treeDepths.tallyProcessingStateTreeDepth),
919
850
  voteOptionTreeDepth: Number(this.treeDepths.voteOptionTreeDepth),
920
851
  stateTreeDepth: Number(this.treeDepths.stateTreeDepth),
921
852
  }, {
922
853
  tallyBatchSize: Number(this.batchSizes.tallyBatchSize.toString()),
923
854
  messageBatchSize: Number(this.batchSizes.messageBatchSize.toString()),
924
- }, this.maciStateRef, this.voteOptions);
855
+ }, this.maciStateRef, this.voteOptions, this.mode);
925
856
  copied.publicKeys = this.publicKeys.map((x) => x.copy());
926
857
  copied.pollStateLeaves = this.pollStateLeaves.map((x) => x.copy());
927
858
  copied.messages = this.messages.map((x) => x.copy());
@@ -941,7 +872,7 @@ class Poll {
941
872
  copied.totalSpentVoiceCredits = BigInt(this.totalSpentVoiceCredits.toString());
942
873
  copied.sbSalts = {};
943
874
  copied.resultRootSalts = {};
944
- copied.preVOSpentVoiceCreditsRootSalts = {};
875
+ copied.perVoteOptionSpentVoiceCreditsRootSalts = {};
945
876
  copied.spentVoiceCreditSubtotalSalts = {};
946
877
  Object.keys(this.sbSalts).forEach((k) => {
947
878
  copied.sbSalts[k] = BigInt(this.sbSalts[k].toString());
@@ -949,14 +880,14 @@ class Poll {
949
880
  Object.keys(this.resultRootSalts).forEach((k) => {
950
881
  copied.resultRootSalts[k] = BigInt(this.resultRootSalts[k].toString());
951
882
  });
952
- Object.keys(this.preVOSpentVoiceCreditsRootSalts).forEach((k) => {
953
- copied.preVOSpentVoiceCreditsRootSalts[k] = BigInt(this.preVOSpentVoiceCreditsRootSalts[k].toString());
883
+ Object.keys(this.perVoteOptionSpentVoiceCreditsRootSalts).forEach((k) => {
884
+ copied.perVoteOptionSpentVoiceCreditsRootSalts[k] = BigInt(this.perVoteOptionSpentVoiceCreditsRootSalts[k].toString());
954
885
  });
955
886
  Object.keys(this.spentVoiceCreditSubtotalSalts).forEach((k) => {
956
887
  copied.spentVoiceCreditSubtotalSalts[k] = BigInt(this.spentVoiceCreditSubtotalSalts[k].toString());
957
888
  });
958
889
  // update the number of signups
959
- copied.settotalSignups(this.totalSignups);
890
+ copied.setTotalSignups(this.totalSignups);
960
891
  return copied;
961
892
  };
962
893
  /**
@@ -966,7 +897,7 @@ class Poll {
966
897
  */
967
898
  this.equals = (poll) => {
968
899
  const result = this.coordinatorKeypair.equals(poll.coordinatorKeypair) &&
969
- this.treeDepths.intStateTreeDepth === poll.treeDepths.intStateTreeDepth &&
900
+ this.treeDepths.tallyProcessingStateTreeDepth === poll.treeDepths.tallyProcessingStateTreeDepth &&
970
901
  this.treeDepths.voteOptionTreeDepth === poll.treeDepths.voteOptionTreeDepth &&
971
902
  this.batchSizes.tallyBatchSize === poll.batchSizes.tallyBatchSize &&
972
903
  this.batchSizes.messageBatchSize === poll.batchSizes.messageBatchSize &&
@@ -1000,7 +931,7 @@ class Poll {
1000
931
  * Set the number of signups to match the ones from the contract
1001
932
  * @param totalSignups - the number of signups
1002
933
  */
1003
- this.settotalSignups = (totalSignups) => {
934
+ this.setTotalSignups = (totalSignups) => {
1004
935
  this.totalSignups = totalSignups;
1005
936
  };
1006
937
  /**
@@ -1021,6 +952,7 @@ class Poll {
1021
952
  this.pollId = BigInt(maciStateRef.polls.size);
1022
953
  this.actualStateTreeDepth = treeDepths.stateTreeDepth;
1023
954
  this.currentMessageBatchIndex = 0;
955
+ this.mode = mode;
1024
956
  this.pollNullifiers = new Map();
1025
957
  this.tallyResult = new Array(this.maxVoteOptions).fill(0n);
1026
958
  this.perVoteOptionSpentVoiceCredits = new Array(this.maxVoteOptions).fill(0n);
@@ -1028,6 +960,35 @@ class Poll {
1028
960
  this.emptyBallot = domainobjs_1.Ballot.genBlankBallot(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
1029
961
  this.ballots.push(this.emptyBallot);
1030
962
  }
963
+ /**
964
+ * Get voice credits left for the voting command.
965
+ *
966
+ * @param args - arguments for getting voice credits
967
+ * @returns voice credits left
968
+ */
969
+ getVoiceCreditsLeft({ stateLeaf, originalVoteWeight, newVoteWeight, mode }) {
970
+ switch (mode) {
971
+ case constants_1.EMode.QV: {
972
+ // the voice credits left are:
973
+ // voiceCreditsBalance (how many the user has) +
974
+ // voiceCreditsPreviouslySpent (the original vote weight for this option) ** 2 -
975
+ // command.newVoteWeight ** 2 (the new vote weight squared)
976
+ // basically we are replacing the previous vote weight for this
977
+ // particular vote option with the new one
978
+ // but we need to ensure that we are not going >= balance
979
+ return stateLeaf.voiceCreditBalance + originalVoteWeight * originalVoteWeight - newVoteWeight * newVoteWeight;
980
+ }
981
+ case constants_1.EMode.NON_QV:
982
+ case constants_1.EMode.FULL: {
983
+ // for non quadratic voting, we simply remove the exponentiation
984
+ // for full spent voting, it will be zero
985
+ return stateLeaf.voiceCreditBalance + originalVoteWeight - newVoteWeight;
986
+ }
987
+ default: {
988
+ throw new Error("Voting mode is not supported");
989
+ }
990
+ }
991
+ }
1031
992
  /**
1032
993
  * Serialize the Poll object to a JSON object
1033
994
  * @returns a JSON object
@@ -1053,6 +1014,7 @@ class Poll {
1053
1014
  chainHash: this.chainHash.toString(),
1054
1015
  pollNullifiers: [...this.pollNullifiers.keys()].map((nullifier) => nullifier.toString()),
1055
1016
  batchHashes: this.batchHashes.map((batchHash) => batchHash.toString()),
1017
+ mode: this.mode,
1056
1018
  };
1057
1019
  }
1058
1020
  /**
@@ -1062,13 +1024,13 @@ class Poll {
1062
1024
  * @returns a new Poll instance
1063
1025
  */
1064
1026
  static fromJSON(json, maciState) {
1065
- const poll = new Poll(BigInt(json.pollEndTimestamp), new domainobjs_1.Keypair(), json.treeDepths, json.batchSizes, maciState, BigInt(json.voteOptions));
1027
+ const poll = new Poll(BigInt(json.pollEndTimestamp), new domainobjs_1.Keypair(), json.treeDepths, json.batchSizes, maciState, BigInt(json.voteOptions), json.mode);
1066
1028
  // set all properties
1067
1029
  poll.pollStateLeaves = json.pollStateLeaves.map((leaf) => domainobjs_1.StateLeaf.fromJSON(leaf));
1068
1030
  poll.ballots = json.ballots.map((ballot) => domainobjs_1.Ballot.fromJSON(ballot));
1069
1031
  poll.encryptionPublicKeys = json.encryptionPublicKeys.map((key) => domainobjs_1.PublicKey.deserialize(key));
1070
1032
  poll.messages = json.messages.map((message) => domainobjs_1.Message.fromJSON(message));
1071
- poll.commands = json.commands.map((command) => domainobjs_1.PCommand.fromJSON(command));
1033
+ poll.commands = json.commands.map((command) => domainobjs_1.VoteCommand.fromJSON(command));
1072
1034
  poll.tallyResult = json.results.map((result) => BigInt(result));
1073
1035
  poll.currentMessageBatchIndex = json.currentMessageBatchIndex;
1074
1036
  poll.numBatchesProcessed = json.numBatchesProcessed;