@maci-protocol/core 0.0.0-ci.a8b537a → 0.0.0-ci.aa81863

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