@maci-protocol/core 0.0.0-ci.7f7ef53 → 0.0.0-ci.7fb2610

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,22 +24,23 @@ 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
- this.encPubKeys = [];
32
+ this.encryptionPublicKeys = [];
31
33
  this.stateCopied = false;
32
- this.pubKeys = [domainobjs_1.padKey];
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 = [];
41
- this.perVOSpentVoiceCredits = [];
43
+ this.perVoteOptionSpentVoiceCredits = [];
42
44
  this.numBatchesTallied = 0;
43
45
  this.totalSpentVoiceCredits = 0n;
44
46
  // message chain hash
@@ -48,7 +50,7 @@ class Poll {
48
50
  // Poll state tree leaves
49
51
  this.pollStateLeaves = [domainobjs_1.blankStateLeaf];
50
52
  // how many users signed up
51
- this.numSignups = 0n;
53
+ this.totalSignups = 0n;
52
54
  /**
53
55
  * Check if user has already joined the poll by checking if the nullifier is registered
54
56
  */
@@ -56,12 +58,12 @@ class Poll {
56
58
  /**
57
59
  * Join the anonymous user to the Poll (to the tree)
58
60
  * @param nullifier - Hashed private key used as nullifier
59
- * @param pubKey - The poll public key.
61
+ * @param publicKey - The poll public key.
60
62
  * @param newVoiceCreditBalance - New voice credit balance of the user.
61
63
  * @returns The index of added state leaf
62
64
  */
63
- this.joinPoll = (nullifier, pubKey, newVoiceCreditBalance) => {
64
- const stateLeaf = new domainobjs_1.StateLeaf(pubKey, newVoiceCreditBalance);
65
+ this.joinPoll = (nullifier, publicKey, newVoiceCreditBalance) => {
66
+ const stateLeaf = new domainobjs_1.StateLeaf(publicKey, newVoiceCreditBalance);
65
67
  if (this.hasJoined(nullifier)) {
66
68
  throw new Error("UserAlreadyJoined");
67
69
  }
@@ -74,11 +76,11 @@ class Poll {
74
76
  * Update a Poll with data from MaciState.
75
77
  * This is the step where we copy the state from the MaciState instance,
76
78
  * and set the number of signups we have so far.
77
- * @note It should be called to generate the state for poll joining with numSignups set as
78
- * the number of signups in the MaciState. For message processing, you should set numSignups as
79
+ * @note It should be called to generate the state for poll joining with totalSignups set as
80
+ * the number of signups in the MaciState. For message processing, you should set totalSignups as
79
81
  * the number of users who joined the poll.
80
82
  */
81
- this.updatePoll = (numSignups) => {
83
+ this.updatePoll = (totalSignups) => {
82
84
  // there might be occasions where we fetch logs after new signups have been made
83
85
  // logs are fetched (and MaciState/Poll created locally).
84
86
  // If someone signs up after that and we fetch that record
@@ -86,15 +88,15 @@ class Poll {
86
88
  // not match. For this, we must only copy up to the number of signups
87
89
  // Copy the state tree, ballot tree, state leaves, and ballot leaves
88
90
  // start by setting the number of signups
89
- this.setNumSignups(numSignups);
90
- // copy up to numSignups state leaves
91
- this.pubKeys = this.maciStateRef.pubKeys.slice(0, Number(this.numSignups)).map((x) => x.copy());
91
+ this.setTotalSignups(totalSignups);
92
+ // copy up to totalSignups state leaves
93
+ this.publicKeys = this.maciStateRef.publicKeys.slice(0, Number(this.totalSignups)).map((x) => x.copy());
92
94
  // ensure we have the correct actual state tree depth value
93
- this.actualStateTreeDepth = Math.max(1, Math.ceil(Math.log2(Number(this.numSignups))));
95
+ this.actualStateTreeDepth = Math.max(1, Math.ceil(Math.log2(Number(this.totalSignups))));
94
96
  this.stateTree = new lean_imt_1.LeanIMT(crypto_1.hashLeanIMT);
95
97
  // add all leaves
96
- this.pubKeys.forEach((pubKey) => {
97
- this.stateTree?.insert(pubKey.hash());
98
+ this.publicKeys.forEach((publicKey) => {
99
+ this.stateTree?.insert(publicKey.hash());
98
100
  });
99
101
  // create a poll state tree
100
102
  this.pollStateTree = new crypto_1.IncrementalQuinTree(this.actualStateTreeDepth, domainobjs_1.blankStateLeafHash, constants_1.STATE_TREE_ARITY, crypto_1.hash2);
@@ -102,27 +104,33 @@ 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
109
- while (this.ballots.length < this.pubKeys.length) {
110
+ while (this.ballots.length < this.publicKeys.length) {
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
  /**
116
124
  * Process one message.
117
125
  * @param message - The message to process.
118
- * @param encPubKey - The public key associated with the encryption private key.
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, encPubKey, qv = true) => {
129
+ this.processMessage = (message, encryptionPublicKey) => {
122
130
  try {
123
131
  // Decrypt the message
124
- const sharedKey = domainobjs_1.Keypair.genEcdhSharedKey(this.coordinatorKeypair.privKey, encPubKey);
125
- const { command, signature } = domainobjs_1.PCommand.decrypt(message, sharedKey);
132
+ const sharedKey = domainobjs_1.Keypair.generateEcdhSharedKey(this.coordinatorKeypair.privateKey, encryptionPublicKey);
133
+ const { command, signature } = domainobjs_1.VoteCommand.decrypt(message, sharedKey);
126
134
  const stateLeafIndex = command.stateIndex;
127
135
  // If the state tree index in the command is invalid, do nothing
128
136
  if (stateLeafIndex >= BigInt(this.ballots.length) ||
@@ -135,7 +143,7 @@ class Poll {
135
143
  // The ballot to update (or not)
136
144
  const ballot = this.ballots[Number(stateLeafIndex)];
137
145
  // If the signature is invalid, do nothing
138
- if (!command.verifySignature(signature, stateLeaf.pubKey)) {
146
+ if (!command.verifySignature(signature, stateLeaf.publicKey)) {
139
147
  throw new errors_1.ProcessMessageError(errors_1.ProcessMessageErrors.InvalidSignature);
140
148
  }
141
149
  // If the nonce is invalid, do nothing
@@ -148,49 +156,48 @@ 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;
172
176
  // if the key changes, this is effectively a key-change message too
173
- newStateLeaf.pubKey = command.newPubKey.copy();
177
+ newStateLeaf.publicKey = command.newPublicKey.copy();
174
178
  // Deep-copy the ballot and update its attributes
175
179
  const newBallot = ballot.copy();
176
180
  // increase the nonce
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 originalStateLeafPathElements = this.pollStateTree?.genProof(Number(stateLeafIndex)).pathElements;
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 originalBallotPathElements = this.ballotTree?.genProof(Number(stateLeafIndex)).pathElements;
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
- const vt = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
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
- vt.insert(ballot.votes[i]);
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 originalVoteWeightsPathElements = vt.genProof(voteOptionIndex).pathElements;
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 {
@@ -219,40 +226,40 @@ class Poll {
219
226
  * Inserts a Message and the corresponding public key used to generate the
220
227
  * ECDH shared key which was used to encrypt said message.
221
228
  * @param message - The message to insert
222
- * @param encPubKey - The public key used to encrypt the message
229
+ * @param encryptionPublicKey - The public key used to encrypt the message
223
230
  */
224
- this.publishMessage = (message, encPubKey) => {
225
- (0, assert_1.default)(encPubKey.rawPubKey[0] < crypto_1.SNARK_FIELD_SIZE && encPubKey.rawPubKey[1] < crypto_1.SNARK_FIELD_SIZE, "The public key is not in the correct range");
231
+ this.publishMessage = (message, encryptionPublicKey) => {
232
+ (0, assert_1.default)(encryptionPublicKey.raw[0] < crypto_1.SNARK_FIELD_SIZE && encryptionPublicKey.raw[1] < crypto_1.SNARK_FIELD_SIZE, "The public key is not in the correct range");
226
233
  message.data.forEach((d) => {
227
234
  (0, assert_1.default)(d < crypto_1.SNARK_FIELD_SIZE, "The message data is not in the correct range");
228
235
  });
229
- // store the encryption pub key
230
- this.encPubKeys.push(encPubKey);
236
+ // store the encryption public key
237
+ this.encryptionPublicKeys.push(encryptionPublicKey);
231
238
  // store the message locally
232
239
  this.messages.push(message);
233
240
  // add the message hash to the message tree
234
- const messageHash = message.hash(encPubKey);
241
+ const messageHash = message.hash(encryptionPublicKey);
235
242
  // update chain hash
236
243
  this.updateChainHash(messageHash);
237
244
  // Decrypt the message and store the Command
238
245
  // step 1. we generate the shared key
239
- const sharedKey = domainobjs_1.Keypair.genEcdhSharedKey(this.coordinatorKeypair.privKey, encPubKey);
246
+ const sharedKey = domainobjs_1.Keypair.generateEcdhSharedKey(this.coordinatorKeypair.privateKey, encryptionPublicKey);
240
247
  try {
241
248
  // step 2. we decrypt it
242
- const { command } = domainobjs_1.PCommand.decrypt(message, sharedKey);
249
+ const { command } = domainobjs_1.VoteCommand.decrypt(message, sharedKey);
243
250
  // step 3. we store it in the commands array
244
251
  this.commands.push(command);
245
252
  }
246
253
  catch (e) {
247
254
  // 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.pubKey, 0n, 0n, 0n, 0n, 0n);
255
+ const keypair = new domainobjs_1.Keypair();
256
+ const command = new domainobjs_1.VoteCommand(0n, keypair.publicKey, 0n, 0n, 0n, 0n, 0n);
250
257
  this.commands.push(command);
251
258
  }
252
259
  };
253
260
  /**
254
261
  * Updates message chain hash
255
- * @param messageHash hash of message with encPubKey
262
+ * @param messageHash hash of message with encryptionPublicKey
256
263
  */
257
264
  this.updateChainHash = (messageHash) => {
258
265
  this.chainHash = (0, crypto_1.hash2)([this.chainHash, messageHash]);
@@ -266,7 +273,7 @@ class Poll {
266
273
  * @param args Poll joining circuit inputs
267
274
  * @returns stringified circuit inputs
268
275
  */
269
- this.joiningCircuitInputs = ({ maciPrivKey, stateLeafIndex, pollPubKey, }) => {
276
+ this.joiningCircuitInputs = ({ maciPrivateKey, stateLeafIndex, pollPublicKey, }) => {
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;
@@ -284,15 +291,15 @@ class Poll {
284
291
  }
285
292
  const siblingsArray = siblings.map((sibling) => [sibling]);
286
293
  // Create nullifier from private key
287
- const inputNullifier = BigInt(maciPrivKey.asCircuitInputs());
294
+ const inputNullifier = BigInt(maciPrivateKey.asCircuitInputs());
288
295
  const nullifier = (0, crypto_1.poseidon)([inputNullifier, this.pollId]);
289
296
  // Get state tree's root
290
297
  const stateRoot = this.stateTree.root;
291
298
  // Set actualStateTreeDepth as number of initial siblings length
292
299
  const actualStateTreeDepth = BigInt(siblingsLength);
293
300
  const circuitInputs = {
294
- privKey: maciPrivKey.asCircuitInputs(),
295
- pollPubKey: pollPubKey.asCircuitInputs(),
301
+ privateKey: maciPrivateKey.asCircuitInputs(),
302
+ pollPublicKey: pollPublicKey.asCircuitInputs(),
296
303
  siblings: siblingsArray,
297
304
  indices,
298
305
  nullifier,
@@ -307,9 +314,9 @@ class Poll {
307
314
  * @param args Poll joined circuit inputs
308
315
  * @returns stringified circuit inputs
309
316
  */
310
- this.joinedCircuitInputs = ({ maciPrivKey, stateLeafIndex, voiceCreditsBalance, }) => {
317
+ this.joinedCircuitInputs = ({ maciPrivateKey, stateLeafIndex, voiceCreditsBalance, }) => {
311
318
  // calculate the path elements for the state tree given the original state tree
312
- const { root: stateRoot, pathElements, pathIndices } = this.pollStateTree.genProof(Number(stateLeafIndex));
319
+ const { root: stateRoot, pathElements, pathIndices } = this.pollStateTree.generateProof(Number(stateLeafIndex));
313
320
  const elementsLength = pathIndices.length;
314
321
  for (let i = 0; i < this.treeDepths.stateTreeDepth; i += 1) {
315
322
  if (i >= elementsLength) {
@@ -318,7 +325,7 @@ class Poll {
318
325
  }
319
326
  }
320
327
  const circuitInputs = {
321
- privKey: maciPrivKey.asCircuitInputs(),
328
+ privateKey: maciPrivateKey.asCircuitInputs(),
322
329
  pathElements: pathElements.map((item) => item.toString()),
323
330
  voiceCreditsBalance: voiceCreditsBalance.toString(),
324
331
  pathIndices: pathIndices.map((item) => item.toString()),
@@ -346,7 +353,7 @@ class Poll {
346
353
  if (this.messages.length > batchSize && this.messages.length % batchSize > 0) {
347
354
  totalBatches += 1;
348
355
  }
349
- return this.numBatchesProcessed < totalBatches;
356
+ return this.totalBatchesProcessed < totalBatches;
350
357
  };
351
358
  /**
352
359
  * Process _batchSize messages starting from the saved index. This
@@ -362,10 +369,10 @@ class Poll {
362
369
  * @param quiet - Whether to log errors or not
363
370
  * @returns stringified circuit inputs
364
371
  */
365
- this.processMessages = (pollId, qv = true, quiet = true) => {
372
+ this.processMessages = (pollId, quiet = true) => {
366
373
  (0, assert_1.default)(this.hasUnprocessedMessages(), "No more messages to process");
367
374
  const batchSize = this.batchSizes.messageBatchSize;
368
- if (this.numBatchesProcessed === 0) {
375
+ if (this.totalBatchesProcessed === 0) {
369
376
  // Prevent other polls from being processed until this poll has
370
377
  // been fully processed
371
378
  this.maciStateRef.pollBeingProcessed = true;
@@ -386,7 +393,7 @@ class Poll {
386
393
  throw new Error("You must update the poll with the correct data first");
387
394
  }
388
395
  // Generate circuit inputs
389
- const circuitInputs = (0, crypto_1.stringifyBigInts)(this.genProcessMessagesCircuitInputsPartial(this.currentMessageBatchIndex));
396
+ const circuitInputs = (0, crypto_1.stringifyBigInts)(this.generateProcessMessagesCircuitInputsPartial(this.currentMessageBatchIndex));
390
397
  // we want to store the state leaves at this point in time
391
398
  // and the path elements of the state tree
392
399
  const currentStateLeaves = [];
@@ -405,29 +412,29 @@ class Poll {
405
412
  const idx = this.currentMessageBatchIndex * batchSize - i - 1;
406
413
  (0, assert_1.default)(idx >= 0, "The message index must be >= 0");
407
414
  let message;
408
- let encPubKey;
415
+ let encryptionPublicKey;
409
416
  if (idx < this.messages.length) {
410
417
  message = this.messages[idx];
411
- encPubKey = this.encPubKeys[idx];
418
+ encryptionPublicKey = this.encryptionPublicKeys[idx];
412
419
  try {
413
420
  // check if the command is valid
414
- const r = this.processMessage(message, encPubKey, qv);
415
- const index = r.stateLeafIndex;
421
+ const { stateLeafIndex, originalStateLeaf, originalBallot, originalVoteWeight, originalVoteWeightsPathElements, originalStateLeafPathElements, originalBallotPathElements, newStateLeaf, newBallot, } = this.processMessage(message, encryptionPublicKey);
422
+ const index = stateLeafIndex;
416
423
  // 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);
424
+ currentStateLeaves.unshift(originalStateLeaf);
425
+ currentBallots.unshift(originalBallot);
426
+ currentVoteWeights.unshift(originalVoteWeight);
427
+ currentVoteWeightsPathElements.unshift(originalVoteWeightsPathElements);
428
+ currentStateLeavesPathElements.unshift(originalStateLeafPathElements);
429
+ currentBallotsPathElements.unshift(originalBallotPathElements);
423
430
  // update the state leaves with the new state leaf (result of processing the message)
424
- this.pollStateLeaves[index] = r.newStateLeaf.copy();
431
+ this.pollStateLeaves[index] = newStateLeaf.copy();
425
432
  // we also update the state tree with the hash of the new state leaf
426
- this.pollStateTree?.update(index, r.newStateLeaf.hash());
433
+ this.pollStateTree?.update(index, newStateLeaf.hash());
427
434
  // store the new ballot
428
- this.ballots[index] = r.newBallot;
435
+ this.ballots[index] = newBallot;
429
436
  // update the ballot tree
430
- this.ballotTree?.update(index, r.newBallot.hash());
437
+ this.ballotTree?.update(index, newBallot.hash());
431
438
  }
432
439
  catch (e) {
433
440
  // if the error is not a ProcessMessageError we throw it and exit here
@@ -446,20 +453,20 @@ class Poll {
446
453
  // which sends a message that when force decrypted on the circuit
447
454
  // results in a valid state index thus forcing the circuit to look
448
455
  // for a valid state leaf, and failing to generate a proof
449
- // gen shared key
450
- const sharedKey = domainobjs_1.Keypair.genEcdhSharedKey(this.coordinatorKeypair.privKey, encPubKey);
456
+ // generate shared key
457
+ const sharedKey = domainobjs_1.Keypair.generateEcdhSharedKey(this.coordinatorKeypair.privateKey, encryptionPublicKey);
451
458
  // force decrypt it
452
- const { command } = domainobjs_1.PCommand.decrypt(message, sharedKey, true);
459
+ const { command } = domainobjs_1.VoteCommand.decrypt(message, sharedKey, true);
453
460
  // cache state leaf index
454
461
  const stateLeafIndex = command.stateIndex;
455
462
  // if the state leaf index is valid then use it
456
463
  if (stateLeafIndex < this.pollStateLeaves.length) {
457
464
  currentStateLeaves.unshift(this.pollStateLeaves[Number(stateLeafIndex)].copy());
458
- currentStateLeavesPathElements.unshift(this.pollStateTree.genProof(Number(stateLeafIndex)).pathElements);
465
+ currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(Number(stateLeafIndex)).pathElements);
459
466
  // copy the ballot
460
467
  const ballot = this.ballots[Number(stateLeafIndex)].copy();
461
468
  currentBallots.unshift(ballot);
462
- currentBallotsPathElements.unshift(this.ballotTree.genProof(Number(stateLeafIndex)).pathElements);
469
+ currentBallotsPathElements.unshift(this.ballotTree.generateProof(Number(stateLeafIndex)).pathElements);
463
470
  // @note we check that command.voteOptionIndex is valid so < voteOptions
464
471
  // this might be unnecessary but we do it to prevent a possible DoS attack
465
472
  // from voters who could potentially encrypt a message in such as way that
@@ -467,39 +474,39 @@ class Poll {
467
474
  if (command.voteOptionIndex < this.voteOptions) {
468
475
  currentVoteWeights.unshift(ballot.votes[Number(command.voteOptionIndex)]);
469
476
  // 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);
477
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
471
478
  // fill the vote option tree with the votes we have so far
472
479
  for (let j = 0; j < this.ballots[0].votes.length; j += 1) {
473
- vt.insert(ballot.votes[j]);
480
+ voteTree.insert(ballot.votes[j]);
474
481
  }
475
482
  // get the path elements for the first vote leaf
476
- currentVoteWeightsPathElements.unshift(vt.genProof(Number(command.voteOptionIndex)).pathElements);
483
+ currentVoteWeightsPathElements.unshift(voteTree.generateProof(Number(command.voteOptionIndex)).pathElements);
477
484
  }
478
485
  else {
479
486
  currentVoteWeights.unshift(ballot.votes[0]);
480
487
  // 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);
488
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
482
489
  // fill the vote option tree with the votes we have so far
483
490
  for (let j = 0; j < this.ballots[0].votes.length; j += 1) {
484
- vt.insert(ballot.votes[j]);
491
+ voteTree.insert(ballot.votes[j]);
485
492
  }
486
493
  // get the path elements for the first vote leaf
487
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
494
+ currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
488
495
  }
489
496
  }
490
497
  else {
491
498
  // just use state leaf index 0
492
499
  currentStateLeaves.unshift(this.pollStateLeaves[0].copy());
493
- currentStateLeavesPathElements.unshift(this.pollStateTree.genProof(0).pathElements);
500
+ currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(0).pathElements);
494
501
  currentBallots.unshift(this.ballots[0].copy());
495
- currentBallotsPathElements.unshift(this.ballotTree.genProof(0).pathElements);
502
+ currentBallotsPathElements.unshift(this.ballotTree.generateProof(0).pathElements);
496
503
  // Since the command is invalid, we use a zero vote weight
497
504
  currentVoteWeights.unshift(this.ballots[0].votes[0]);
498
505
  // 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]);
506
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
507
+ voteTree.insert(this.ballots[0].votes[0]);
501
508
  // get the path elements for this empty vote weight leaf
502
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
509
+ currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
503
510
  }
504
511
  }
505
512
  else {
@@ -510,17 +517,17 @@ class Poll {
510
517
  else {
511
518
  // Since we don't have a command at that position, use a blank state leaf
512
519
  currentStateLeaves.unshift(this.pollStateLeaves[0].copy());
513
- currentStateLeavesPathElements.unshift(this.pollStateTree.genProof(0).pathElements);
520
+ currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(0).pathElements);
514
521
  // since the command is invliad we use the blank ballot
515
522
  currentBallots.unshift(this.ballots[0].copy());
516
- currentBallotsPathElements.unshift(this.ballotTree.genProof(0).pathElements);
523
+ currentBallotsPathElements.unshift(this.ballotTree.generateProof(0).pathElements);
517
524
  // Since the command is invalid, we use a zero vote weight
518
525
  currentVoteWeights.unshift(this.ballots[0].votes[0]);
519
526
  // 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]);
527
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
528
+ voteTree.insert(this.ballots[0].votes[0]);
522
529
  // get the path elements for this empty vote weight leaf
523
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
530
+ currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
524
531
  }
525
532
  }
526
533
  // store the data in the circuit inputs object
@@ -538,14 +545,14 @@ class Poll {
538
545
  circuitInputs.currentVoteWeights = currentVoteWeights;
539
546
  circuitInputs.currentVoteWeightsPathElements = currentVoteWeightsPathElements;
540
547
  // record that we processed one batch
541
- this.numBatchesProcessed += 1;
548
+ this.totalBatchesProcessed += 1;
542
549
  if (this.currentMessageBatchIndex > 0) {
543
550
  this.currentMessageBatchIndex -= 1;
544
551
  }
545
552
  // ensure newSbSalt differs from currentSbSalt
546
- let newSbSalt = (0, crypto_1.genRandomSalt)();
553
+ let newSbSalt = (0, crypto_1.generateRandomSalt)();
547
554
  while (this.sbSalts[this.currentMessageBatchIndex] === newSbSalt) {
548
- newSbSalt = (0, crypto_1.genRandomSalt)();
555
+ newSbSalt = (0, crypto_1.generateRandomSalt)();
549
556
  }
550
557
  this.sbSalts[this.currentMessageBatchIndex] = newSbSalt;
551
558
  // store the salt in the circuit inputs
@@ -555,9 +562,9 @@ class Poll {
555
562
  // create a commitment to the state and ballot tree roots
556
563
  // this will be the hash of the roots with a salt
557
564
  circuitInputs.newSbCommitment = (0, crypto_1.hash3)([newStateRoot, newBallotRoot, newSbSalt]);
558
- const coordinatorPublicKeyHash = this.coordinatorKeypair.pubKey.hash();
565
+ const coordinatorPublicKeyHash = this.coordinatorKeypair.publicKey.hash();
559
566
  // If this is the last batch, release the lock
560
- if (this.numBatchesProcessed * batchSize >= this.messages.length) {
567
+ if (this.totalBatchesProcessed * batchSize >= this.messages.length) {
561
568
  this.maciStateRef.pollBeingProcessed = false;
562
569
  }
563
570
  // ensure we pass the dynamic tree depth
@@ -572,33 +579,33 @@ class Poll {
572
579
  * @param index - The index of the partial batch.
573
580
  * @returns stringified partial circuit inputs
574
581
  */
575
- this.genProcessMessagesCircuitInputsPartial = (index) => {
582
+ this.generateProcessMessagesCircuitInputsPartial = (index) => {
576
583
  const { messageBatchSize } = this.batchSizes;
577
584
  (0, assert_1.default)(index <= this.messages.length, "The index must be <= the number of messages");
578
- // fill the msgs array with a copy of the messages we have
585
+ // fill the messages array with a copy of the messages we have
579
586
  // plus empty messages to fill the batch
580
587
  // @note create a message with state index 0 to add as padding
581
588
  // this way the message will look for state leaf 0
582
589
  // and no effect will take place
583
590
  // create a random key
584
591
  const key = new domainobjs_1.Keypair();
585
- // gen ecdh key
586
- const ecdh = domainobjs_1.Keypair.genEcdhSharedKey(key.privKey, this.coordinatorKeypair.pubKey);
592
+ // generate ecdh key
593
+ const ecdh = domainobjs_1.Keypair.generateEcdhSharedKey(key.privateKey, this.coordinatorKeypair.publicKey);
587
594
  // create an empty command with state index 0n
588
- const emptyCommand = new domainobjs_1.PCommand(0n, key.pubKey, 0n, 0n, 0n, 0n, 0n);
595
+ const emptyCommand = new domainobjs_1.VoteCommand(0n, key.publicKey, 0n, 0n, 0n, 0n, 0n);
589
596
  // encrypt it
590
- const msg = emptyCommand.encrypt(emptyCommand.sign(key.privKey), ecdh);
597
+ const emptyMessage = emptyCommand.encrypt(emptyCommand.sign(key.privateKey), ecdh);
591
598
  // copy the messages to a new array
592
- let msgs = this.messages.map((x) => x.asCircuitInputs());
599
+ let messages = this.messages.map((x) => x.asCircuitInputs());
593
600
  // pad with our state index 0 message
594
- while (msgs.length % messageBatchSize > 0) {
595
- msgs.push(msg.asCircuitInputs());
601
+ while (messages.length % messageBatchSize > 0) {
602
+ messages.push(emptyMessage.asCircuitInputs());
596
603
  }
597
604
  // copy the public keys, pad the array with the last keys if needed
598
- let encPubKeys = this.encPubKeys.map((x) => x.copy());
599
- while (encPubKeys.length % messageBatchSize > 0) {
605
+ let encryptionPublicKeys = this.encryptionPublicKeys.map((x) => x.copy());
606
+ while (encryptionPublicKeys.length % messageBatchSize > 0) {
600
607
  // pad with the public key used to encrypt the message with state index 0 (padding)
601
- encPubKeys.push(key.pubKey.copy());
608
+ encryptionPublicKeys.push(key.publicKey.copy());
602
609
  }
603
610
  // validate that the batch index is correct, if not fix it
604
611
  // this means that the end will be the last message
@@ -608,11 +615,11 @@ class Poll {
608
615
  }
609
616
  const batchStartIndex = index > 0 ? (index - 1) * messageBatchSize : 0;
610
617
  // we only take the messages we need for this batch
611
- // it slice msgs array from index of first message in current batch to
618
+ // it slice messages array from index of first message in current batch to
612
619
  // index of last message in current batch
613
- msgs = msgs.slice(batchStartIndex, index * messageBatchSize);
620
+ messages = messages.slice(batchStartIndex, index * messageBatchSize);
614
621
  // then take the ones part of this batch
615
- encPubKeys = encPubKeys.slice(batchStartIndex, index * messageBatchSize);
622
+ encryptionPublicKeys = encryptionPublicKeys.slice(batchStartIndex, index * messageBatchSize);
616
623
  // cache tree roots
617
624
  const currentStateRoot = this.pollStateTree.root;
618
625
  const currentBallotRoot = this.ballotTree.root;
@@ -623,15 +630,15 @@ class Poll {
623
630
  const inputBatchHash = this.batchHashes[index - 1];
624
631
  const outputBatchHash = this.batchHashes[index];
625
632
  return (0, crypto_1.stringifyBigInts)({
626
- numSignUps: BigInt(this.numSignups),
633
+ totalSignups: BigInt(this.totalSignups),
627
634
  batchEndIndex: BigInt(batchEndIndex),
628
635
  index: BigInt(batchStartIndex),
629
636
  inputBatchHash,
630
637
  outputBatchHash,
631
- msgs,
638
+ messages,
632
639
  actualStateTreeDepth: BigInt(this.actualStateTreeDepth),
633
- coordPrivKey: this.coordinatorKeypair.privKey.asCircuitInputs(),
634
- encPubKeys: encPubKeys.map((x) => x.asCircuitInputs()),
640
+ coordinatorPrivateKey: this.coordinatorKeypair.privateKey.asCircuitInputs(),
641
+ encryptionPublicKeys: encryptionPublicKeys.map((x) => x.asCircuitInputs()),
635
642
  currentStateRoot,
636
643
  currentBallotRoot,
637
644
  currentSbCommitment,
@@ -661,7 +668,8 @@ class Poll {
661
668
  this.hasUntalliedBallots = () => this.numBatchesTallied * this.batchSizes.tallyBatchSize < this.ballots.length;
662
669
  /**
663
670
  * This method tallies a ballots and updates the tally results.
664
- * @returns the circuit inputs for the TallyVotes circuit.
671
+ *
672
+ * @returns the circuit inputs for the VoteTally circuit.
665
673
  */
666
674
  this.tallyVotes = () => {
667
675
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -674,193 +682,150 @@ class Poll {
674
682
  const batchStartIndex = this.numBatchesTallied * batchSize;
675
683
  // get the salts needed for the commitments
676
684
  const currentResultsRootSalt = batchStartIndex === 0 ? 0n : this.resultRootSalts[batchStartIndex - batchSize];
677
- const currentPerVOSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? 0n : this.preVOSpentVoiceCreditsRootSalts[batchStartIndex - batchSize];
685
+ const currentPerVoteOptionSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? 0n : this.perVoteOptionSpentVoiceCreditsRootSalts[batchStartIndex - batchSize];
678
686
  const currentSpentVoiceCreditSubtotalSalt = batchStartIndex === 0 ? 0n : this.spentVoiceCreditSubtotalSalts[batchStartIndex - batchSize];
679
687
  // generate a commitment to the current results
680
- const currentResultsCommitment = (0, crypto_1.genTreeCommitment)(this.tallyResult, currentResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
681
- // generate a commitment to the current per VO spent voice credits
682
- const currentPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment(currentPerVOSpentVoiceCreditsRootSalt, batchStartIndex, true);
688
+ const currentResultsCommitment = (0, crypto_1.generateTreeCommitment)(this.tallyResult, currentResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
689
+ // generate a commitment to the current per vote option spent voice credits
690
+ const currentPerVoteOptionSpentVoiceCreditsCommitment = this.generatePerVoteOptionSpentVoiceCreditsCommitment(currentPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex, this.mode);
683
691
  // generate a commitment to the current spent voice credits
684
- const currentSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, true);
692
+ const currentSpentVoiceCreditsCommitment = this.generateSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, this.mode);
685
693
  // the current commitment for the first batch will be 0
686
694
  // otherwise calculate as
687
695
  // hash([
688
696
  // currentResultsCommitment,
689
697
  // currentSpentVoiceCreditsCommitment,
690
- // currentPerVOSpentVoiceCreditsCommitment
691
698
  // ])
692
- const currentTallyCommitment = batchStartIndex === 0
699
+ // or for QV
700
+ // hash([
701
+ // currentResultsCommitment,
702
+ // currentSpentVoiceCreditsCommitment,
703
+ // currentPerVoteOptionSpentVoiceCreditsCommitment
704
+ // ])
705
+ // TODO: use commitment for vote counts
706
+ const currentTallyCommitmentQv = this.mode !== constants_1.EMode.QV || batchStartIndex === 0
693
707
  ? 0n
694
708
  : (0, crypto_1.hash3)([
695
709
  currentResultsCommitment,
696
710
  currentSpentVoiceCreditsCommitment,
697
- currentPerVOSpentVoiceCreditsCommitment,
711
+ currentPerVoteOptionSpentVoiceCreditsCommitment,
698
712
  ]);
713
+ const currentTallyCommitmentNonQv = this.mode === constants_1.EMode.QV || batchStartIndex === 0
714
+ ? 0n
715
+ : (0, crypto_1.hashLeftRight)(currentResultsCommitment, currentSpentVoiceCreditsCommitment);
716
+ const currentTallyCommitment = currentTallyCommitmentNonQv || currentTallyCommitmentQv;
717
+ const startIndex = this.numBatchesTallied * batchSize;
718
+ const endIndex = this.numBatchesTallied * batchSize + batchSize;
699
719
  const ballots = [];
720
+ const voteCounts = [];
721
+ const voteCountsData = [];
700
722
  const currentResults = this.tallyResult.map((x) => BigInt(x.toString()));
701
- const currentPerVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x) => BigInt(x.toString()));
723
+ const currentPerVoteOptionSpentVoiceCredits = this.perVoteOptionSpentVoiceCredits.map((x) => BigInt(x.toString()));
702
724
  const currentSpentVoiceCreditSubtotal = BigInt(this.totalSpentVoiceCredits.toString());
703
725
  // 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) {
726
+ for (let i = startIndex; i < endIndex; i += 1) {
705
727
  // we stop if we have no more ballots to tally
706
728
  if (i >= this.ballots.length) {
707
729
  break;
708
730
  }
709
731
  // save to the local ballot array
710
732
  ballots.push(this.ballots[i]);
733
+ const ballot = this.ballots[i];
734
+ const newVoteCounts = this.voteCounts[i].copy();
735
+ newVoteCounts.counts = ballot.votes.map((votes, voteOptionIndex) => this.voteCounts[i].counts[voteOptionIndex] + BigInt(votes !== 0n));
736
+ // save to the local vote counts array
737
+ this.voteCountsTree?.update(i, newVoteCounts.hash());
738
+ this.voteCounts[i] = newVoteCounts;
739
+ voteCounts.push(newVoteCounts);
740
+ voteCountsData.push(newVoteCounts.counts);
711
741
  // for each possible vote option we loop and calculate
712
742
  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.perVOSpentVoiceCredits[j] += v * v;
718
- // the total spent voice credits will be the sum of the squares of the votes
719
- this.totalSpentVoiceCredits += v * v;
743
+ const votes = this.ballots[i].votes[j];
744
+ this.tallyResult[j] += votes;
745
+ if (this.mode === constants_1.EMode.QV) {
746
+ // the per vote option spent voice credits will be the sum of the squares of the votes
747
+ this.perVoteOptionSpentVoiceCredits[j] += votes * votes;
748
+ // the total spent voice credits will be the sum of the squares of the votes
749
+ this.totalSpentVoiceCredits += votes * votes;
750
+ }
751
+ else {
752
+ // the total spent voice credits will be the sum of the votes
753
+ this.totalSpentVoiceCredits += votes;
754
+ }
720
755
  }
721
756
  }
722
757
  const emptyBallot = new domainobjs_1.Ballot(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
758
+ const emptyVoteCounts = new domainobjs_1.VoteCounts(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
723
759
  // pad the ballots array
724
760
  while (ballots.length < batchSize) {
725
761
  ballots.push(emptyBallot);
726
762
  }
727
- // generate the new salts
728
- const newResultsRootSalt = (0, crypto_1.genRandomSalt)();
729
- const newPerVOSpentVoiceCreditsRootSalt = (0, crypto_1.genRandomSalt)();
730
- const newSpentVoiceCreditSubtotalSalt = (0, crypto_1.genRandomSalt)();
731
- // and save them to be used in the next batch
732
- this.resultRootSalts[batchStartIndex] = newResultsRootSalt;
733
- this.preVOSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVOSpentVoiceCreditsRootSalt;
734
- this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt;
735
- // generate the new results commitment with the new salts and data
736
- const newResultsCommitment = (0, crypto_1.genTreeCommitment)(this.tallyResult, newResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
737
- // generate the new spent voice credits commitment with the new salts and data
738
- const newSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, true);
739
- // generate the new per VO spent voice credits commitment with the new salts and data
740
- const newPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment(newPerVOSpentVoiceCreditsRootSalt, batchStartIndex + batchSize, true);
741
- // generate the new tally commitment
742
- const newTallyCommitment = (0, crypto_1.hash3)([
743
- newResultsCommitment,
744
- newSpentVoiceCreditsCommitment,
745
- newPerVOSpentVoiceCreditsCommitment,
746
- ]);
747
- // cache vars
748
- const stateRoot = this.pollStateTree.root;
749
- const ballotRoot = this.ballotTree.root;
750
- const sbSalt = this.sbSalts[this.currentMessageBatchIndex];
751
- const sbCommitment = (0, crypto_1.hash3)([stateRoot, ballotRoot, sbSalt]);
752
- const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize);
753
- const votes = ballots.map((x) => x.votes);
754
- const circuitInputs = (0, crypto_1.stringifyBigInts)({
755
- stateRoot,
756
- ballotRoot,
757
- sbSalt,
758
- index: BigInt(batchStartIndex),
759
- numSignUps: BigInt(this.numSignups),
760
- sbCommitment,
761
- currentTallyCommitment,
762
- newTallyCommitment,
763
- ballots: ballots.map((x) => x.asCircuitInputs()),
764
- ballotPathElements: ballotSubrootProof.pathElements,
765
- votes,
766
- currentResults,
767
- currentResultsRootSalt,
768
- currentSpentVoiceCreditSubtotal,
769
- currentSpentVoiceCreditSubtotalSalt,
770
- currentPerVOSpentVoiceCredits,
771
- currentPerVOSpentVoiceCreditsRootSalt,
772
- newResultsRootSalt,
773
- newPerVOSpentVoiceCreditsRootSalt,
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.genTreeCommitment)(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);
763
+ // pad the vote counts array
764
+ while (voteCounts.length < batchSize) {
765
+ voteCounts.push(emptyVoteCounts);
825
766
  }
826
767
  // generate the new salts
827
- const newResultsRootSalt = (0, crypto_1.genRandomSalt)();
828
- const newSpentVoiceCreditSubtotalSalt = (0, crypto_1.genRandomSalt)();
768
+ const newResultsRootSalt = (0, crypto_1.generateRandomSalt)();
769
+ const newPerVoteOptionSpentVoiceCreditsRootSalt = (0, crypto_1.generateRandomSalt)();
770
+ const newSpentVoiceCreditSubtotalSalt = (0, crypto_1.generateRandomSalt)();
829
771
  // and save them to be used in the next batch
830
772
  this.resultRootSalts[batchStartIndex] = newResultsRootSalt;
773
+ this.perVoteOptionSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVoteOptionSpentVoiceCreditsRootSalt;
831
774
  this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt;
832
775
  // generate the new results commitment with the new salts and data
833
- const newResultsCommitment = (0, crypto_1.genTreeCommitment)(this.tallyResult, newResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
776
+ const newResultsCommitment = (0, crypto_1.generateTreeCommitment)(this.tallyResult, newResultsRootSalt, this.treeDepths.voteOptionTreeDepth);
834
777
  // generate the new spent voice credits commitment with the new salts and data
835
- const newSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, false);
778
+ const newSpentVoiceCreditsCommitment = this.generateSpentVoiceCreditSubtotalCommitment(newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, this.mode);
779
+ // generate the new per vote option spent voice credits commitment with the new salts and data
780
+ const newPerVoteOptionSpentVoiceCreditsCommitment = this.generatePerVoteOptionSpentVoiceCreditsCommitment(newPerVoteOptionSpentVoiceCreditsRootSalt, batchStartIndex + batchSize, this.mode);
836
781
  // generate the new tally commitment
837
- const newTallyCommitment = (0, crypto_1.hashLeftRight)(newResultsCommitment, newSpentVoiceCreditsCommitment);
782
+ const newTallyCommitment = this.mode === constants_1.EMode.QV
783
+ ? (0, crypto_1.hash3)([newResultsCommitment, newSpentVoiceCreditsCommitment, newPerVoteOptionSpentVoiceCreditsCommitment])
784
+ : (0, crypto_1.hashLeftRight)(newResultsCommitment, newSpentVoiceCreditsCommitment);
838
785
  // cache vars
839
786
  const stateRoot = this.pollStateTree.root;
840
787
  const ballotRoot = this.ballotTree.root;
788
+ const voteCountsRoot = this.voteCountsTree.root;
841
789
  const sbSalt = this.sbSalts[this.currentMessageBatchIndex];
842
790
  const sbCommitment = (0, crypto_1.hash3)([stateRoot, ballotRoot, sbSalt]);
843
- const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize);
791
+ const ballotSubrootProof = this.ballotTree?.generateSubrootProof(batchStartIndex, batchStartIndex + batchSize);
792
+ const voteCountsSubrootProof = this.voteCountsTree?.generateSubrootProof(batchStartIndex, batchStartIndex + batchSize);
844
793
  const votes = ballots.map((x) => x.votes);
845
- const circuitInputs = (0, crypto_1.stringifyBigInts)({
794
+ // Don't include these inputs in the circuit inputs until individual vote counts are implemented
795
+ const excludedCircuitInputs = ["voteCountsData", "voteCountsPathElements", "voteCounts", "voteCountsRoot"];
796
+ const circuitInputs = (0, crypto_1.stringifyBigInts)((0, omit_1.default)({
846
797
  stateRoot,
847
798
  ballotRoot,
848
799
  sbSalt,
849
800
  index: BigInt(batchStartIndex),
850
- numSignUps: BigInt(this.numSignups),
801
+ totalSignups: BigInt(this.totalSignups),
851
802
  sbCommitment,
852
803
  currentTallyCommitment,
853
804
  newTallyCommitment,
854
805
  ballots: ballots.map((x) => x.asCircuitInputs()),
855
806
  ballotPathElements: ballotSubrootProof.pathElements,
856
807
  votes,
808
+ voteCountsRoot,
809
+ voteCounts: voteCounts.map((x) => x.asCircuitInputs()),
810
+ voteCountsPathElements: voteCountsSubrootProof.pathElements,
811
+ voteCountsData,
857
812
  currentResults,
858
813
  currentResultsRootSalt,
859
814
  currentSpentVoiceCreditSubtotal,
860
815
  currentSpentVoiceCreditSubtotalSalt,
816
+ currentPerVoteOptionSpentVoiceCredits,
817
+ currentPerVoteOptionSpentVoiceCreditsRootSalt,
818
+ newPerVoteOptionSpentVoiceCreditsRootSalt,
861
819
  newResultsRootSalt,
862
820
  newSpentVoiceCreditSubtotalSalt,
863
- });
821
+ }, this.mode !== constants_1.EMode.QV
822
+ ? [
823
+ ...excludedCircuitInputs,
824
+ "currentPerVoteOptionSpentVoiceCredits",
825
+ "currentPerVoteOptionSpentVoiceCreditsRootSalt",
826
+ "newPerVoteOptionSpentVoiceCreditsRootSalt",
827
+ ]
828
+ : [...excludedCircuitInputs]));
864
829
  this.numBatchesTallied += 1;
865
830
  return circuitInputs;
866
831
  };
@@ -869,19 +834,19 @@ class Poll {
869
834
  *
870
835
  * This is the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]).
871
836
  * @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.
837
+ * @param ballotsToCount - The number of ballots to count for the calculation.
838
+ * @param mode - Voting mode, default is QV.
874
839
  * @returns Returns the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]).
875
840
  */
876
- this.genSpentVoiceCreditSubtotalCommitment = (salt, numBallotsToCount, useQuadraticVoting = true) => {
841
+ this.generateSpentVoiceCreditSubtotalCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
877
842
  let subtotal = 0n;
878
- for (let i = 0; i < numBallotsToCount; i += 1) {
843
+ for (let i = 0; i < ballotsToCount; i += 1) {
879
844
  if (this.ballots.length <= i) {
880
845
  break;
881
846
  }
882
847
  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;
848
+ const vote = BigInt(`${this.ballots[i].votes[j]}`);
849
+ subtotal += mode === constants_1.EMode.QV ? vote * vote : vote;
885
850
  }
886
851
  }
887
852
  return (0, crypto_1.hashLeftRight)(subtotal, salt);
@@ -891,23 +856,23 @@ class Poll {
891
856
  *
892
857
  * 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
858
  * @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.
859
+ * @param ballotsToCount - The number of ballots to count for the calculation.
860
+ * @param mode - Voting mode, default is QV.
896
861
  * @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
862
  */
898
- this.genPerVOSpentVoiceCreditsCommitment = (salt, numBallotsToCount, useQuadraticVoting = true) => {
863
+ this.generatePerVoteOptionSpentVoiceCreditsCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
899
864
  const leaves = Array(this.tallyResult.length).fill(0n);
900
- for (let i = 0; i < numBallotsToCount; i += 1) {
865
+ for (let i = 0; i < ballotsToCount; i += 1) {
901
866
  // check that is a valid index
902
867
  if (i >= this.ballots.length) {
903
868
  break;
904
869
  }
905
870
  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;
871
+ const vote = this.ballots[i].votes[j];
872
+ leaves[j] += mode === constants_1.EMode.QV ? vote * vote : vote;
908
873
  }
909
874
  }
910
- return (0, crypto_1.genTreeCommitment)(leaves, salt, this.treeDepths.voteOptionTreeDepth);
875
+ return (0, crypto_1.generateTreeCommitment)(leaves, salt, this.treeDepths.voteOptionTreeDepth);
911
876
  };
912
877
  /**
913
878
  * Create a deep copy of the Poll object.
@@ -915,33 +880,37 @@ class Poll {
915
880
  */
916
881
  this.copy = () => {
917
882
  const copied = new Poll(BigInt(this.pollEndTimestamp.toString()), this.coordinatorKeypair.copy(), {
918
- intStateTreeDepth: Number(this.treeDepths.intStateTreeDepth),
883
+ tallyProcessingStateTreeDepth: Number(this.treeDepths.tallyProcessingStateTreeDepth),
919
884
  voteOptionTreeDepth: Number(this.treeDepths.voteOptionTreeDepth),
920
885
  stateTreeDepth: Number(this.treeDepths.stateTreeDepth),
921
886
  }, {
922
887
  tallyBatchSize: Number(this.batchSizes.tallyBatchSize.toString()),
923
888
  messageBatchSize: Number(this.batchSizes.messageBatchSize.toString()),
924
- }, this.maciStateRef, this.voteOptions);
925
- copied.pubKeys = this.pubKeys.map((x) => x.copy());
889
+ }, this.maciStateRef, this.voteOptions, this.mode);
890
+ copied.publicKeys = this.publicKeys.map((x) => x.copy());
926
891
  copied.pollStateLeaves = this.pollStateLeaves.map((x) => x.copy());
927
892
  copied.messages = this.messages.map((x) => x.copy());
928
893
  copied.commands = this.commands.map((x) => x.copy());
929
894
  copied.ballots = this.ballots.map((x) => x.copy());
930
- copied.encPubKeys = this.encPubKeys.map((x) => x.copy());
895
+ copied.encryptionPublicKeys = this.encryptionPublicKeys.map((x) => x.copy());
896
+ copied.voteCounts = this.voteCounts.map((x) => x.copy());
931
897
  if (this.ballotTree) {
932
898
  copied.ballotTree = this.ballotTree.copy();
933
899
  }
900
+ if (this.voteCountsTree) {
901
+ copied.voteCountsTree = this.voteCountsTree.copy();
902
+ }
934
903
  copied.currentMessageBatchIndex = this.currentMessageBatchIndex;
935
904
  copied.maciStateRef = this.maciStateRef;
936
905
  copied.tallyResult = this.tallyResult.map((x) => BigInt(x.toString()));
937
- copied.perVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x) => BigInt(x.toString()));
938
- copied.numBatchesProcessed = Number(this.numBatchesProcessed.toString());
906
+ copied.perVoteOptionSpentVoiceCredits = this.perVoteOptionSpentVoiceCredits.map((x) => BigInt(x.toString()));
907
+ copied.totalBatchesProcessed = Number(this.totalBatchesProcessed.toString());
939
908
  copied.numBatchesTallied = Number(this.numBatchesTallied.toString());
940
909
  copied.pollId = this.pollId;
941
910
  copied.totalSpentVoiceCredits = BigInt(this.totalSpentVoiceCredits.toString());
942
911
  copied.sbSalts = {};
943
912
  copied.resultRootSalts = {};
944
- copied.preVOSpentVoiceCreditsRootSalts = {};
913
+ copied.perVoteOptionSpentVoiceCreditsRootSalts = {};
945
914
  copied.spentVoiceCreditSubtotalSalts = {};
946
915
  Object.keys(this.sbSalts).forEach((k) => {
947
916
  copied.sbSalts[k] = BigInt(this.sbSalts[k].toString());
@@ -949,41 +918,41 @@ class Poll {
949
918
  Object.keys(this.resultRootSalts).forEach((k) => {
950
919
  copied.resultRootSalts[k] = BigInt(this.resultRootSalts[k].toString());
951
920
  });
952
- Object.keys(this.preVOSpentVoiceCreditsRootSalts).forEach((k) => {
953
- copied.preVOSpentVoiceCreditsRootSalts[k] = BigInt(this.preVOSpentVoiceCreditsRootSalts[k].toString());
921
+ Object.keys(this.perVoteOptionSpentVoiceCreditsRootSalts).forEach((k) => {
922
+ copied.perVoteOptionSpentVoiceCreditsRootSalts[k] = BigInt(this.perVoteOptionSpentVoiceCreditsRootSalts[k].toString());
954
923
  });
955
924
  Object.keys(this.spentVoiceCreditSubtotalSalts).forEach((k) => {
956
925
  copied.spentVoiceCreditSubtotalSalts[k] = BigInt(this.spentVoiceCreditSubtotalSalts[k].toString());
957
926
  });
958
927
  // update the number of signups
959
- copied.setNumSignups(this.numSignups);
928
+ copied.setTotalSignups(this.totalSignups);
960
929
  return copied;
961
930
  };
962
931
  /**
963
932
  * Check if the Poll object is equal to another Poll object.
964
- * @param p - The Poll object to compare.
933
+ * @param poll - The Poll object to compare.
965
934
  * @returns True if the two Poll objects are equal, false otherwise.
966
935
  */
967
- this.equals = (p) => {
968
- const result = this.coordinatorKeypair.equals(p.coordinatorKeypair) &&
969
- this.treeDepths.intStateTreeDepth === p.treeDepths.intStateTreeDepth &&
970
- this.treeDepths.voteOptionTreeDepth === p.treeDepths.voteOptionTreeDepth &&
971
- this.batchSizes.tallyBatchSize === p.batchSizes.tallyBatchSize &&
972
- this.batchSizes.messageBatchSize === p.batchSizes.messageBatchSize &&
973
- this.maxVoteOptions === p.maxVoteOptions &&
974
- this.messages.length === p.messages.length &&
975
- this.encPubKeys.length === p.encPubKeys.length &&
976
- this.numSignups === p.numSignups;
936
+ this.equals = (poll) => {
937
+ const result = this.coordinatorKeypair.equals(poll.coordinatorKeypair) &&
938
+ this.treeDepths.tallyProcessingStateTreeDepth === poll.treeDepths.tallyProcessingStateTreeDepth &&
939
+ this.treeDepths.voteOptionTreeDepth === poll.treeDepths.voteOptionTreeDepth &&
940
+ this.batchSizes.tallyBatchSize === poll.batchSizes.tallyBatchSize &&
941
+ this.batchSizes.messageBatchSize === poll.batchSizes.messageBatchSize &&
942
+ this.maxVoteOptions === poll.maxVoteOptions &&
943
+ this.messages.length === poll.messages.length &&
944
+ this.encryptionPublicKeys.length === poll.encryptionPublicKeys.length &&
945
+ this.totalSignups === poll.totalSignups;
977
946
  if (!result) {
978
947
  return false;
979
948
  }
980
949
  for (let i = 0; i < this.messages.length; i += 1) {
981
- if (!this.messages[i].equals(p.messages[i])) {
950
+ if (!this.messages[i].equals(poll.messages[i])) {
982
951
  return false;
983
952
  }
984
953
  }
985
- for (let i = 0; i < this.encPubKeys.length; i += 1) {
986
- if (!this.encPubKeys[i].equals(p.encPubKeys[i])) {
954
+ for (let i = 0; i < this.encryptionPublicKeys.length; i += 1) {
955
+ if (!this.encryptionPublicKeys[i].equals(poll.encryptionPublicKeys[i])) {
987
956
  return false;
988
957
  }
989
958
  }
@@ -994,39 +963,73 @@ class Poll {
994
963
  * @param serializedPrivateKey - the serialized private key
995
964
  */
996
965
  this.setCoordinatorKeypair = (serializedPrivateKey) => {
997
- this.coordinatorKeypair = new domainobjs_1.Keypair(domainobjs_1.PrivKey.deserialize(serializedPrivateKey));
966
+ this.coordinatorKeypair = new domainobjs_1.Keypair(domainobjs_1.PrivateKey.deserialize(serializedPrivateKey));
998
967
  };
999
968
  /**
1000
969
  * Set the number of signups to match the ones from the contract
1001
- * @param numSignups - the number of signups
970
+ * @param totalSignups - the number of signups
1002
971
  */
1003
- this.setNumSignups = (numSignups) => {
1004
- this.numSignups = numSignups;
972
+ this.setTotalSignups = (totalSignups) => {
973
+ this.totalSignups = totalSignups;
1005
974
  };
1006
975
  /**
1007
976
  * Get the number of signups
1008
977
  * @returns The number of signups
1009
978
  */
1010
- this.getNumSignups = () => this.numSignups;
979
+ this.getTotalSignups = () => this.totalSignups;
980
+ if (voteOptions > constants_1.VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth) {
981
+ throw new Error("Vote options cannot be greater than the number of leaves in the vote option tree");
982
+ }
1011
983
  this.pollEndTimestamp = pollEndTimestamp;
1012
984
  this.coordinatorKeypair = coordinatorKeypair;
1013
985
  this.treeDepths = treeDepths;
1014
986
  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
987
  this.voteOptions = voteOptions;
1019
988
  this.maxVoteOptions = constants_1.VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth;
1020
989
  this.maciStateRef = maciStateRef;
1021
990
  this.pollId = BigInt(maciStateRef.polls.size);
1022
991
  this.actualStateTreeDepth = treeDepths.stateTreeDepth;
1023
992
  this.currentMessageBatchIndex = 0;
993
+ this.mode = mode;
1024
994
  this.pollNullifiers = new Map();
1025
995
  this.tallyResult = new Array(this.maxVoteOptions).fill(0n);
1026
- this.perVOSpentVoiceCredits = new Array(this.maxVoteOptions).fill(0n);
996
+ this.perVoteOptionSpentVoiceCredits = new Array(this.maxVoteOptions).fill(0n);
1027
997
  // we put a blank state leaf to prevent a DoS attack
1028
- this.emptyBallot = domainobjs_1.Ballot.genBlankBallot(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
998
+ this.emptyBallot = domainobjs_1.Ballot.generateBlank(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
1029
999
  this.ballots.push(this.emptyBallot);
1000
+ this.emptyBallotHash = this.emptyBallot.hash();
1001
+ this.emptyVoteCounts = domainobjs_1.VoteCounts.generateBlank(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
1002
+ this.voteCounts.push(this.emptyVoteCounts);
1003
+ this.emptyVoteCountsHash = this.emptyVoteCounts.hash();
1004
+ }
1005
+ /**
1006
+ * Get voice credits left for the voting command.
1007
+ *
1008
+ * @param args - arguments for getting voice credits
1009
+ * @returns voice credits left
1010
+ */
1011
+ getVoiceCreditsLeft({ stateLeaf, originalVoteWeight, newVoteWeight, mode }) {
1012
+ switch (mode) {
1013
+ case constants_1.EMode.QV: {
1014
+ // the voice credits left are:
1015
+ // voiceCreditsBalance (how many the user has) +
1016
+ // voiceCreditsPreviouslySpent (the original vote weight for this option) ** 2 -
1017
+ // command.newVoteWeight ** 2 (the new vote weight squared)
1018
+ // basically we are replacing the previous vote weight for this
1019
+ // particular vote option with the new one
1020
+ // but we need to ensure that we are not going >= balance
1021
+ return stateLeaf.voiceCreditBalance + originalVoteWeight * originalVoteWeight - newVoteWeight * newVoteWeight;
1022
+ }
1023
+ case constants_1.EMode.NON_QV:
1024
+ case constants_1.EMode.FULL: {
1025
+ // for non quadratic voting, we simply remove the exponentiation
1026
+ // for full credits voting, it will be zero
1027
+ return stateLeaf.voiceCreditBalance + originalVoteWeight - newVoteWeight;
1028
+ }
1029
+ default: {
1030
+ throw new Error("Voting mode is not supported");
1031
+ }
1032
+ }
1030
1033
  }
1031
1034
  /**
1032
1035
  * Serialize the Poll object to a JSON object
@@ -1043,16 +1046,18 @@ class Poll {
1043
1046
  messages: this.messages.map((message) => message.toJSON()),
1044
1047
  commands: this.commands.map((command) => command.toJSON()),
1045
1048
  ballots: this.ballots.map((ballot) => ballot.toJSON()),
1046
- encPubKeys: this.encPubKeys.map((encPubKey) => encPubKey.serialize()),
1049
+ voteCounts: this.voteCounts.map((voteCounts) => voteCounts.toJSON()),
1050
+ encryptionPublicKeys: this.encryptionPublicKeys.map((encryptionPublicKey) => encryptionPublicKey.serialize()),
1047
1051
  currentMessageBatchIndex: this.currentMessageBatchIndex,
1048
- pubKeys: this.pubKeys.map((leaf) => leaf.toJSON()),
1052
+ publicKeys: this.publicKeys.map((leaf) => leaf.toJSON()),
1049
1053
  pollStateLeaves: this.pollStateLeaves.map((leaf) => leaf.toJSON()),
1050
1054
  results: this.tallyResult.map((result) => result.toString()),
1051
- numBatchesProcessed: this.numBatchesProcessed,
1052
- numSignups: this.numSignups.toString(),
1055
+ totalBatchesProcessed: this.totalBatchesProcessed,
1056
+ totalSignups: this.totalSignups.toString(),
1053
1057
  chainHash: this.chainHash.toString(),
1054
1058
  pollNullifiers: [...this.pollNullifiers.keys()].map((nullifier) => nullifier.toString()),
1055
1059
  batchHashes: this.batchHashes.map((batchHash) => batchHash.toString()),
1060
+ mode: this.mode,
1056
1061
  };
1057
1062
  }
1058
1063
  /**
@@ -1062,21 +1067,22 @@ class Poll {
1062
1067
  * @returns a new Poll instance
1063
1068
  */
1064
1069
  static fromJSON(json, maciState) {
1065
- const poll = new Poll(BigInt(json.pollEndTimestamp), new domainobjs_1.Keypair(), json.treeDepths, json.batchSizes, maciState, BigInt(json.voteOptions));
1070
+ const poll = new Poll(BigInt(json.pollEndTimestamp), new domainobjs_1.Keypair(), json.treeDepths, json.batchSizes, maciState, BigInt(json.voteOptions), json.mode);
1066
1071
  // set all properties
1067
1072
  poll.pollStateLeaves = json.pollStateLeaves.map((leaf) => domainobjs_1.StateLeaf.fromJSON(leaf));
1068
1073
  poll.ballots = json.ballots.map((ballot) => domainobjs_1.Ballot.fromJSON(ballot));
1069
- poll.encPubKeys = json.encPubKeys.map((key) => domainobjs_1.PubKey.deserialize(key));
1074
+ poll.voteCounts = json.voteCounts.map((voteCounts) => domainobjs_1.VoteCounts.fromJSON(voteCounts));
1075
+ poll.encryptionPublicKeys = json.encryptionPublicKeys.map((key) => domainobjs_1.PublicKey.deserialize(key));
1070
1076
  poll.messages = json.messages.map((message) => domainobjs_1.Message.fromJSON(message));
1071
- poll.commands = json.commands.map((command) => domainobjs_1.PCommand.fromJSON(command));
1077
+ poll.commands = json.commands.map((command) => domainobjs_1.VoteCommand.fromJSON(command));
1072
1078
  poll.tallyResult = json.results.map((result) => BigInt(result));
1073
1079
  poll.currentMessageBatchIndex = json.currentMessageBatchIndex;
1074
- poll.numBatchesProcessed = json.numBatchesProcessed;
1080
+ poll.totalBatchesProcessed = json.totalBatchesProcessed;
1075
1081
  poll.chainHash = BigInt(json.chainHash);
1076
1082
  poll.batchHashes = json.batchHashes.map((batchHash) => BigInt(batchHash));
1077
1083
  poll.pollNullifiers = new Map(json.pollNullifiers.map((nullifier) => [BigInt(nullifier), true]));
1078
1084
  // copy maci state
1079
- poll.updatePoll(BigInt(json.numSignups));
1085
+ poll.updatePoll(BigInt(json.totalSignups));
1080
1086
  return poll;
1081
1087
  }
1082
1088
  }