@maci-protocol/core 0.0.0-ci.84b0e62 → 0.0.0-ci.85a90db

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,16 +273,16 @@ 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
280
  // 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,
281
+ // The circuit tree depth is this.treeDepths.stateTreeDepth, so the number of siblings must be this.treeDepths.stateTreeDepth,
276
282
  // even if the tree depth is actually 3. The missing siblings can be set to 0, as they
277
283
  // won't be used to calculate the root in the circuit.
278
284
  const indices = [];
279
- for (let i = 0; i < this.stateTreeDepth; i += 1) {
285
+ for (let i = 0; i < this.treeDepths.stateTreeDepth; i += 1) {
280
286
  // eslint-disable-next-line no-bitwise
281
287
  indices.push(BigInt((index >> i) & 1));
282
288
  if (i >= siblingsLength) {
@@ -285,15 +291,15 @@ class Poll {
285
291
  }
286
292
  const siblingsArray = siblings.map((sibling) => [sibling]);
287
293
  // Create nullifier from private key
288
- const inputNullifier = BigInt(maciPrivKey.asCircuitInputs());
294
+ const inputNullifier = BigInt(maciPrivateKey.asCircuitInputs());
289
295
  const nullifier = (0, crypto_1.poseidon)([inputNullifier, this.pollId]);
290
296
  // Get state tree's root
291
297
  const stateRoot = this.stateTree.root;
292
298
  // Set actualStateTreeDepth as number of initial siblings length
293
299
  const actualStateTreeDepth = BigInt(siblingsLength);
294
300
  const circuitInputs = {
295
- privKey: maciPrivKey.asCircuitInputs(),
296
- pollPubKey: pollPubKey.asCircuitInputs(),
301
+ privateKey: maciPrivateKey.asCircuitInputs(),
302
+ pollPublicKey: pollPublicKey.asCircuitInputs(),
297
303
  siblings: siblingsArray,
298
304
  indices,
299
305
  nullifier,
@@ -308,23 +314,20 @@ class Poll {
308
314
  * @param args Poll joined circuit inputs
309
315
  * @returns stringified circuit inputs
310
316
  */
311
- this.joinedCircuitInputs = ({ maciPrivKey, stateLeafIndex, voiceCreditsBalance, joinTimestamp, }) => {
317
+ this.joinedCircuitInputs = ({ maciPrivateKey, stateLeafIndex, voiceCreditsBalance, }) => {
312
318
  // 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;
319
+ const { root: stateRoot, pathElements, pathIndices } = this.pollStateTree.generateProof(Number(stateLeafIndex));
316
320
  const elementsLength = pathIndices.length;
317
- for (let i = 0; i < this.stateTreeDepth; i += 1) {
321
+ for (let i = 0; i < this.treeDepths.stateTreeDepth; i += 1) {
318
322
  if (i >= elementsLength) {
319
323
  pathElements[i] = [0n];
320
324
  pathIndices[i] = 0;
321
325
  }
322
326
  }
323
327
  const circuitInputs = {
324
- privKey: maciPrivKey.asCircuitInputs(),
328
+ privateKey: maciPrivateKey.asCircuitInputs(),
325
329
  pathElements: pathElements.map((item) => item.toString()),
326
330
  voiceCreditsBalance: voiceCreditsBalance.toString(),
327
- joinTimestamp: joinTimestamp.toString(),
328
331
  pathIndices: pathIndices.map((item) => item.toString()),
329
332
  actualStateTreeDepth: BigInt(this.actualStateTreeDepth),
330
333
  stateRoot,
@@ -350,7 +353,7 @@ class Poll {
350
353
  if (this.messages.length > batchSize && this.messages.length % batchSize > 0) {
351
354
  totalBatches += 1;
352
355
  }
353
- return this.numBatchesProcessed < totalBatches;
356
+ return this.totalBatchesProcessed < totalBatches;
354
357
  };
355
358
  /**
356
359
  * Process _batchSize messages starting from the saved index. This
@@ -366,10 +369,10 @@ class Poll {
366
369
  * @param quiet - Whether to log errors or not
367
370
  * @returns stringified circuit inputs
368
371
  */
369
- this.processMessages = (pollId, qv = true, quiet = true) => {
372
+ this.processMessages = (pollId, quiet = true) => {
370
373
  (0, assert_1.default)(this.hasUnprocessedMessages(), "No more messages to process");
371
374
  const batchSize = this.batchSizes.messageBatchSize;
372
- if (this.numBatchesProcessed === 0) {
375
+ if (this.totalBatchesProcessed === 0) {
373
376
  // Prevent other polls from being processed until this poll has
374
377
  // been fully processed
375
378
  this.maciStateRef.pollBeingProcessed = true;
@@ -390,7 +393,7 @@ class Poll {
390
393
  throw new Error("You must update the poll with the correct data first");
391
394
  }
392
395
  // Generate circuit inputs
393
- const circuitInputs = (0, crypto_1.stringifyBigInts)(this.genProcessMessagesCircuitInputsPartial(this.currentMessageBatchIndex));
396
+ const circuitInputs = (0, crypto_1.stringifyBigInts)(this.generateProcessMessagesCircuitInputsPartial(this.currentMessageBatchIndex));
394
397
  // we want to store the state leaves at this point in time
395
398
  // and the path elements of the state tree
396
399
  const currentStateLeaves = [];
@@ -409,29 +412,29 @@ class Poll {
409
412
  const idx = this.currentMessageBatchIndex * batchSize - i - 1;
410
413
  (0, assert_1.default)(idx >= 0, "The message index must be >= 0");
411
414
  let message;
412
- let encPubKey;
415
+ let encryptionPublicKey;
413
416
  if (idx < this.messages.length) {
414
417
  message = this.messages[idx];
415
- encPubKey = this.encPubKeys[idx];
418
+ encryptionPublicKey = this.encryptionPublicKeys[idx];
416
419
  try {
417
420
  // check if the command is valid
418
- const r = this.processMessage(message, encPubKey, qv);
419
- const index = r.stateLeafIndex;
421
+ const { stateLeafIndex, originalStateLeaf, originalBallot, originalVoteWeight, originalVoteWeightsPathElements, originalStateLeafPathElements, originalBallotPathElements, newStateLeaf, newBallot, } = this.processMessage(message, encryptionPublicKey);
422
+ const index = stateLeafIndex;
420
423
  // 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);
424
+ currentStateLeaves.unshift(originalStateLeaf);
425
+ currentBallots.unshift(originalBallot);
426
+ currentVoteWeights.unshift(originalVoteWeight);
427
+ currentVoteWeightsPathElements.unshift(originalVoteWeightsPathElements);
428
+ currentStateLeavesPathElements.unshift(originalStateLeafPathElements);
429
+ currentBallotsPathElements.unshift(originalBallotPathElements);
427
430
  // update the state leaves with the new state leaf (result of processing the message)
428
- this.pollStateLeaves[index] = r.newStateLeaf.copy();
431
+ this.pollStateLeaves[index] = newStateLeaf.copy();
429
432
  // we also update the state tree with the hash of the new state leaf
430
- this.pollStateTree?.update(index, r.newStateLeaf.hash());
433
+ this.pollStateTree?.update(index, newStateLeaf.hash());
431
434
  // store the new ballot
432
- this.ballots[index] = r.newBallot;
435
+ this.ballots[index] = newBallot;
433
436
  // update the ballot tree
434
- this.ballotTree?.update(index, r.newBallot.hash());
437
+ this.ballotTree?.update(index, newBallot.hash());
435
438
  }
436
439
  catch (e) {
437
440
  // if the error is not a ProcessMessageError we throw it and exit here
@@ -450,20 +453,20 @@ class Poll {
450
453
  // which sends a message that when force decrypted on the circuit
451
454
  // results in a valid state index thus forcing the circuit to look
452
455
  // 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);
456
+ // generate shared key
457
+ const sharedKey = domainobjs_1.Keypair.generateEcdhSharedKey(this.coordinatorKeypair.privateKey, encryptionPublicKey);
455
458
  // force decrypt it
456
- const { command } = domainobjs_1.PCommand.decrypt(message, sharedKey, true);
459
+ const { command } = domainobjs_1.VoteCommand.decrypt(message, sharedKey, true);
457
460
  // cache state leaf index
458
461
  const stateLeafIndex = command.stateIndex;
459
462
  // if the state leaf index is valid then use it
460
463
  if (stateLeafIndex < this.pollStateLeaves.length) {
461
464
  currentStateLeaves.unshift(this.pollStateLeaves[Number(stateLeafIndex)].copy());
462
- currentStateLeavesPathElements.unshift(this.pollStateTree.genProof(Number(stateLeafIndex)).pathElements);
465
+ currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(Number(stateLeafIndex)).pathElements);
463
466
  // copy the ballot
464
467
  const ballot = this.ballots[Number(stateLeafIndex)].copy();
465
468
  currentBallots.unshift(ballot);
466
- currentBallotsPathElements.unshift(this.ballotTree.genProof(Number(stateLeafIndex)).pathElements);
469
+ currentBallotsPathElements.unshift(this.ballotTree.generateProof(Number(stateLeafIndex)).pathElements);
467
470
  // @note we check that command.voteOptionIndex is valid so < voteOptions
468
471
  // this might be unnecessary but we do it to prevent a possible DoS attack
469
472
  // from voters who could potentially encrypt a message in such as way that
@@ -471,39 +474,39 @@ class Poll {
471
474
  if (command.voteOptionIndex < this.voteOptions) {
472
475
  currentVoteWeights.unshift(ballot.votes[Number(command.voteOptionIndex)]);
473
476
  // 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);
477
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
475
478
  // fill the vote option tree with the votes we have so far
476
479
  for (let j = 0; j < this.ballots[0].votes.length; j += 1) {
477
- vt.insert(ballot.votes[j]);
480
+ voteTree.insert(ballot.votes[j]);
478
481
  }
479
482
  // get the path elements for the first vote leaf
480
- currentVoteWeightsPathElements.unshift(vt.genProof(Number(command.voteOptionIndex)).pathElements);
483
+ currentVoteWeightsPathElements.unshift(voteTree.generateProof(Number(command.voteOptionIndex)).pathElements);
481
484
  }
482
485
  else {
483
486
  currentVoteWeights.unshift(ballot.votes[0]);
484
487
  // 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);
488
+ const voteTree = new crypto_1.IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, constants_1.VOTE_OPTION_TREE_ARITY, crypto_1.hash5);
486
489
  // fill the vote option tree with the votes we have so far
487
490
  for (let j = 0; j < this.ballots[0].votes.length; j += 1) {
488
- vt.insert(ballot.votes[j]);
491
+ voteTree.insert(ballot.votes[j]);
489
492
  }
490
493
  // get the path elements for the first vote leaf
491
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
494
+ currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
492
495
  }
493
496
  }
494
497
  else {
495
498
  // just use state leaf index 0
496
499
  currentStateLeaves.unshift(this.pollStateLeaves[0].copy());
497
- currentStateLeavesPathElements.unshift(this.pollStateTree.genProof(0).pathElements);
500
+ currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(0).pathElements);
498
501
  currentBallots.unshift(this.ballots[0].copy());
499
- currentBallotsPathElements.unshift(this.ballotTree.genProof(0).pathElements);
502
+ currentBallotsPathElements.unshift(this.ballotTree.generateProof(0).pathElements);
500
503
  // Since the command is invalid, we use a zero vote weight
501
504
  currentVoteWeights.unshift(this.ballots[0].votes[0]);
502
505
  // 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]);
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]);
505
508
  // get the path elements for this empty vote weight leaf
506
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
509
+ currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
507
510
  }
508
511
  }
509
512
  else {
@@ -514,17 +517,17 @@ class Poll {
514
517
  else {
515
518
  // Since we don't have a command at that position, use a blank state leaf
516
519
  currentStateLeaves.unshift(this.pollStateLeaves[0].copy());
517
- currentStateLeavesPathElements.unshift(this.pollStateTree.genProof(0).pathElements);
520
+ currentStateLeavesPathElements.unshift(this.pollStateTree.generateProof(0).pathElements);
518
521
  // since the command is invliad we use the blank ballot
519
522
  currentBallots.unshift(this.ballots[0].copy());
520
- currentBallotsPathElements.unshift(this.ballotTree.genProof(0).pathElements);
523
+ currentBallotsPathElements.unshift(this.ballotTree.generateProof(0).pathElements);
521
524
  // Since the command is invalid, we use a zero vote weight
522
525
  currentVoteWeights.unshift(this.ballots[0].votes[0]);
523
526
  // 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]);
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]);
526
529
  // get the path elements for this empty vote weight leaf
527
- currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
530
+ currentVoteWeightsPathElements.unshift(voteTree.generateProof(0).pathElements);
528
531
  }
529
532
  }
530
533
  // store the data in the circuit inputs object
@@ -532,7 +535,7 @@ class Poll {
532
535
  // we need to fill the array with 0s to match the length of the state leaves
533
536
  // eslint-disable-next-line @typescript-eslint/prefer-for-of
534
537
  for (let i = 0; i < currentStateLeavesPathElements.length; i += 1) {
535
- while (currentStateLeavesPathElements[i].length < this.stateTreeDepth) {
538
+ while (currentStateLeavesPathElements[i].length < this.treeDepths.stateTreeDepth) {
536
539
  currentStateLeavesPathElements[i].push([0n]);
537
540
  }
538
541
  }
@@ -542,14 +545,14 @@ class Poll {
542
545
  circuitInputs.currentVoteWeights = currentVoteWeights;
543
546
  circuitInputs.currentVoteWeightsPathElements = currentVoteWeightsPathElements;
544
547
  // record that we processed one batch
545
- this.numBatchesProcessed += 1;
548
+ this.totalBatchesProcessed += 1;
546
549
  if (this.currentMessageBatchIndex > 0) {
547
550
  this.currentMessageBatchIndex -= 1;
548
551
  }
549
552
  // ensure newSbSalt differs from currentSbSalt
550
- let newSbSalt = (0, crypto_1.genRandomSalt)();
553
+ let newSbSalt = (0, crypto_1.generateRandomSalt)();
551
554
  while (this.sbSalts[this.currentMessageBatchIndex] === newSbSalt) {
552
- newSbSalt = (0, crypto_1.genRandomSalt)();
555
+ newSbSalt = (0, crypto_1.generateRandomSalt)();
553
556
  }
554
557
  this.sbSalts[this.currentMessageBatchIndex] = newSbSalt;
555
558
  // store the salt in the circuit inputs
@@ -559,9 +562,9 @@ class Poll {
559
562
  // create a commitment to the state and ballot tree roots
560
563
  // this will be the hash of the roots with a salt
561
564
  circuitInputs.newSbCommitment = (0, crypto_1.hash3)([newStateRoot, newBallotRoot, newSbSalt]);
562
- const coordinatorPublicKeyHash = this.coordinatorKeypair.pubKey.hash();
565
+ const coordinatorPublicKeyHash = this.coordinatorKeypair.publicKey.hash();
563
566
  // If this is the last batch, release the lock
564
- if (this.numBatchesProcessed * batchSize >= this.messages.length) {
567
+ if (this.totalBatchesProcessed * batchSize >= this.messages.length) {
565
568
  this.maciStateRef.pollBeingProcessed = false;
566
569
  }
567
570
  // ensure we pass the dynamic tree depth
@@ -576,33 +579,33 @@ class Poll {
576
579
  * @param index - The index of the partial batch.
577
580
  * @returns stringified partial circuit inputs
578
581
  */
579
- this.genProcessMessagesCircuitInputsPartial = (index) => {
582
+ this.generateProcessMessagesCircuitInputsPartial = (index) => {
580
583
  const { messageBatchSize } = this.batchSizes;
581
584
  (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
585
+ // fill the messages array with a copy of the messages we have
583
586
  // plus empty messages to fill the batch
584
587
  // @note create a message with state index 0 to add as padding
585
588
  // this way the message will look for state leaf 0
586
589
  // and no effect will take place
587
590
  // create a random key
588
591
  const key = new domainobjs_1.Keypair();
589
- // gen ecdh key
590
- 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);
591
594
  // create an empty command with state index 0n
592
- 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);
593
596
  // encrypt it
594
- const msg = emptyCommand.encrypt(emptyCommand.sign(key.privKey), ecdh);
597
+ const emptyMessage = emptyCommand.encrypt(emptyCommand.sign(key.privateKey), ecdh);
595
598
  // copy the messages to a new array
596
- let msgs = this.messages.map((x) => x.asCircuitInputs());
599
+ let messages = this.messages.map((x) => x.asCircuitInputs());
597
600
  // pad with our state index 0 message
598
- while (msgs.length % messageBatchSize > 0) {
599
- msgs.push(msg.asCircuitInputs());
601
+ while (messages.length % messageBatchSize > 0) {
602
+ messages.push(emptyMessage.asCircuitInputs());
600
603
  }
601
604
  // 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) {
605
+ let encryptionPublicKeys = this.encryptionPublicKeys.map((x) => x.copy());
606
+ while (encryptionPublicKeys.length % messageBatchSize > 0) {
604
607
  // pad with the public key used to encrypt the message with state index 0 (padding)
605
- encPubKeys.push(key.pubKey.copy());
608
+ encryptionPublicKeys.push(key.publicKey.copy());
606
609
  }
607
610
  // validate that the batch index is correct, if not fix it
608
611
  // this means that the end will be the last message
@@ -612,11 +615,11 @@ class Poll {
612
615
  }
613
616
  const batchStartIndex = index > 0 ? (index - 1) * messageBatchSize : 0;
614
617
  // we only take the messages we need for this batch
615
- // 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
616
619
  // index of last message in current batch
617
- msgs = msgs.slice(batchStartIndex, index * messageBatchSize);
620
+ messages = messages.slice(batchStartIndex, index * messageBatchSize);
618
621
  // then take the ones part of this batch
619
- encPubKeys = encPubKeys.slice(batchStartIndex, index * messageBatchSize);
622
+ encryptionPublicKeys = encryptionPublicKeys.slice(batchStartIndex, index * messageBatchSize);
620
623
  // cache tree roots
621
624
  const currentStateRoot = this.pollStateTree.root;
622
625
  const currentBallotRoot = this.ballotTree.root;
@@ -627,15 +630,15 @@ class Poll {
627
630
  const inputBatchHash = this.batchHashes[index - 1];
628
631
  const outputBatchHash = this.batchHashes[index];
629
632
  return (0, crypto_1.stringifyBigInts)({
630
- numSignUps: BigInt(this.numSignups),
633
+ totalSignups: BigInt(this.totalSignups),
631
634
  batchEndIndex: BigInt(batchEndIndex),
632
635
  index: BigInt(batchStartIndex),
633
636
  inputBatchHash,
634
637
  outputBatchHash,
635
- msgs,
638
+ messages,
636
639
  actualStateTreeDepth: BigInt(this.actualStateTreeDepth),
637
- coordPrivKey: this.coordinatorKeypair.privKey.asCircuitInputs(),
638
- encPubKeys: encPubKeys.map((x) => x.asCircuitInputs()),
640
+ coordinatorPrivateKey: this.coordinatorKeypair.privateKey.asCircuitInputs(),
641
+ encryptionPublicKeys: encryptionPublicKeys.map((x) => x.asCircuitInputs()),
639
642
  currentStateRoot,
640
643
  currentBallotRoot,
641
644
  currentSbCommitment,
@@ -665,7 +668,8 @@ class Poll {
665
668
  this.hasUntalliedBallots = () => this.numBatchesTallied * this.batchSizes.tallyBatchSize < this.ballots.length;
666
669
  /**
667
670
  * This method tallies a ballots and updates the tally results.
668
- * @returns the circuit inputs for the TallyVotes circuit.
671
+ *
672
+ * @returns the circuit inputs for the VoteTally circuit.
669
673
  */
670
674
  this.tallyVotes = () => {
671
675
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -678,193 +682,150 @@ class Poll {
678
682
  const batchStartIndex = this.numBatchesTallied * batchSize;
679
683
  // get the salts needed for the commitments
680
684
  const currentResultsRootSalt = batchStartIndex === 0 ? 0n : this.resultRootSalts[batchStartIndex - batchSize];
681
- const currentPerVOSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? 0n : this.preVOSpentVoiceCreditsRootSalts[batchStartIndex - batchSize];
685
+ const currentPerVoteOptionSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? 0n : this.perVoteOptionSpentVoiceCreditsRootSalts[batchStartIndex - batchSize];
682
686
  const currentSpentVoiceCreditSubtotalSalt = batchStartIndex === 0 ? 0n : this.spentVoiceCreditSubtotalSalts[batchStartIndex - batchSize];
683
687
  // 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);
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);
687
691
  // generate a commitment to the current spent voice credits
688
- const currentSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, true);
692
+ const currentSpentVoiceCreditsCommitment = this.generateSpentVoiceCreditSubtotalCommitment(currentSpentVoiceCreditSubtotalSalt, batchStartIndex, this.mode);
689
693
  // the current commitment for the first batch will be 0
690
694
  // otherwise calculate as
691
695
  // hash([
692
696
  // currentResultsCommitment,
693
697
  // currentSpentVoiceCreditsCommitment,
694
- // currentPerVOSpentVoiceCreditsCommitment
695
698
  // ])
696
- 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
697
707
  ? 0n
698
708
  : (0, crypto_1.hash3)([
699
709
  currentResultsCommitment,
700
710
  currentSpentVoiceCreditsCommitment,
701
- currentPerVOSpentVoiceCreditsCommitment,
711
+ currentPerVoteOptionSpentVoiceCreditsCommitment,
702
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;
703
719
  const ballots = [];
720
+ const voteCounts = [];
721
+ const voteCountsData = [];
704
722
  const currentResults = this.tallyResult.map((x) => BigInt(x.toString()));
705
- const currentPerVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x) => BigInt(x.toString()));
723
+ const currentPerVoteOptionSpentVoiceCredits = this.perVoteOptionSpentVoiceCredits.map((x) => BigInt(x.toString()));
706
724
  const currentSpentVoiceCreditSubtotal = BigInt(this.totalSpentVoiceCredits.toString());
707
725
  // 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) {
726
+ for (let i = startIndex; i < endIndex; i += 1) {
709
727
  // we stop if we have no more ballots to tally
710
728
  if (i >= this.ballots.length) {
711
729
  break;
712
730
  }
713
731
  // save to the local ballot array
714
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);
715
741
  // for each possible vote option we loop and calculate
716
742
  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;
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
+ }
724
755
  }
725
756
  }
726
757
  const emptyBallot = new domainobjs_1.Ballot(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
758
+ const emptyVoteCounts = new domainobjs_1.VoteCounts(this.maxVoteOptions, this.treeDepths.voteOptionTreeDepth);
727
759
  // pad the ballots array
728
760
  while (ballots.length < batchSize) {
729
761
  ballots.push(emptyBallot);
730
762
  }
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);
763
+ // pad the vote counts array
764
+ while (voteCounts.length < batchSize) {
765
+ voteCounts.push(emptyVoteCounts);
829
766
  }
830
767
  // generate the new salts
831
- const newResultsRootSalt = (0, crypto_1.genRandomSalt)();
832
- 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)();
833
771
  // and save them to be used in the next batch
834
772
  this.resultRootSalts[batchStartIndex] = newResultsRootSalt;
773
+ this.perVoteOptionSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVoteOptionSpentVoiceCreditsRootSalt;
835
774
  this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt;
836
775
  // generate the new results commitment with the new salts and data
837
- 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);
838
777
  // generate the new spent voice credits commitment with the new salts and data
839
- 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);
840
781
  // generate the new tally commitment
841
- 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);
842
785
  // cache vars
843
786
  const stateRoot = this.pollStateTree.root;
844
787
  const ballotRoot = this.ballotTree.root;
788
+ const voteCountsRoot = this.voteCountsTree.root;
845
789
  const sbSalt = this.sbSalts[this.currentMessageBatchIndex];
846
790
  const sbCommitment = (0, crypto_1.hash3)([stateRoot, ballotRoot, sbSalt]);
847
- 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);
848
793
  const votes = ballots.map((x) => x.votes);
849
- 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)({
850
797
  stateRoot,
851
798
  ballotRoot,
852
799
  sbSalt,
853
800
  index: BigInt(batchStartIndex),
854
- numSignUps: BigInt(this.numSignups),
801
+ totalSignups: BigInt(this.totalSignups),
855
802
  sbCommitment,
856
803
  currentTallyCommitment,
857
804
  newTallyCommitment,
858
805
  ballots: ballots.map((x) => x.asCircuitInputs()),
859
806
  ballotPathElements: ballotSubrootProof.pathElements,
860
807
  votes,
808
+ voteCountsRoot,
809
+ voteCounts: voteCounts.map((x) => x.asCircuitInputs()),
810
+ voteCountsPathElements: voteCountsSubrootProof.pathElements,
811
+ voteCountsData,
861
812
  currentResults,
862
813
  currentResultsRootSalt,
863
814
  currentSpentVoiceCreditSubtotal,
864
815
  currentSpentVoiceCreditSubtotalSalt,
816
+ currentPerVoteOptionSpentVoiceCredits,
817
+ currentPerVoteOptionSpentVoiceCreditsRootSalt,
818
+ newPerVoteOptionSpentVoiceCreditsRootSalt,
865
819
  newResultsRootSalt,
866
820
  newSpentVoiceCreditSubtotalSalt,
867
- });
821
+ }, this.mode !== constants_1.EMode.QV
822
+ ? [
823
+ ...excludedCircuitInputs,
824
+ "currentPerVoteOptionSpentVoiceCredits",
825
+ "currentPerVoteOptionSpentVoiceCreditsRootSalt",
826
+ "newPerVoteOptionSpentVoiceCreditsRootSalt",
827
+ ]
828
+ : [...excludedCircuitInputs]));
868
829
  this.numBatchesTallied += 1;
869
830
  return circuitInputs;
870
831
  };
@@ -873,19 +834,19 @@ class Poll {
873
834
  *
874
835
  * This is the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]).
875
836
  * @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.
837
+ * @param ballotsToCount - The number of ballots to count for the calculation.
838
+ * @param mode - Voting mode, default is QV.
878
839
  * @returns Returns the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]).
879
840
  */
880
- this.genSpentVoiceCreditSubtotalCommitment = (salt, numBallotsToCount, useQuadraticVoting = true) => {
841
+ this.generateSpentVoiceCreditSubtotalCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
881
842
  let subtotal = 0n;
882
- for (let i = 0; i < numBallotsToCount; i += 1) {
843
+ for (let i = 0; i < ballotsToCount; i += 1) {
883
844
  if (this.ballots.length <= i) {
884
845
  break;
885
846
  }
886
847
  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;
848
+ const vote = BigInt(`${this.ballots[i].votes[j]}`);
849
+ subtotal += mode === constants_1.EMode.QV ? vote * vote : vote;
889
850
  }
890
851
  }
891
852
  return (0, crypto_1.hashLeftRight)(subtotal, salt);
@@ -895,23 +856,23 @@ class Poll {
895
856
  *
896
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]).
897
858
  * @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.
859
+ * @param ballotsToCount - The number of ballots to count for the calculation.
860
+ * @param mode - Voting mode, default is QV.
900
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]).
901
862
  */
902
- this.genPerVOSpentVoiceCreditsCommitment = (salt, numBallotsToCount, useQuadraticVoting = true) => {
863
+ this.generatePerVoteOptionSpentVoiceCreditsCommitment = (salt, ballotsToCount, mode = constants_1.EMode.QV) => {
903
864
  const leaves = Array(this.tallyResult.length).fill(0n);
904
- for (let i = 0; i < numBallotsToCount; i += 1) {
865
+ for (let i = 0; i < ballotsToCount; i += 1) {
905
866
  // check that is a valid index
906
867
  if (i >= this.ballots.length) {
907
868
  break;
908
869
  }
909
870
  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;
871
+ const vote = this.ballots[i].votes[j];
872
+ leaves[j] += mode === constants_1.EMode.QV ? vote * vote : vote;
912
873
  }
913
874
  }
914
- return (0, crypto_1.genTreeCommitment)(leaves, salt, this.treeDepths.voteOptionTreeDepth);
875
+ return (0, crypto_1.generateTreeCommitment)(leaves, salt, this.treeDepths.voteOptionTreeDepth);
915
876
  };
916
877
  /**
917
878
  * Create a deep copy of the Poll object.
@@ -919,32 +880,37 @@ class Poll {
919
880
  */
920
881
  this.copy = () => {
921
882
  const copied = new Poll(BigInt(this.pollEndTimestamp.toString()), this.coordinatorKeypair.copy(), {
922
- intStateTreeDepth: Number(this.treeDepths.intStateTreeDepth),
883
+ tallyProcessingStateTreeDepth: Number(this.treeDepths.tallyProcessingStateTreeDepth),
923
884
  voteOptionTreeDepth: Number(this.treeDepths.voteOptionTreeDepth),
885
+ stateTreeDepth: Number(this.treeDepths.stateTreeDepth),
924
886
  }, {
925
887
  tallyBatchSize: Number(this.batchSizes.tallyBatchSize.toString()),
926
888
  messageBatchSize: Number(this.batchSizes.messageBatchSize.toString()),
927
- }, this.maciStateRef, this.voteOptions);
928
- copied.pubKeys = this.pubKeys.map((x) => x.copy());
889
+ }, this.maciStateRef, this.voteOptions, this.mode);
890
+ copied.publicKeys = this.publicKeys.map((x) => x.copy());
929
891
  copied.pollStateLeaves = this.pollStateLeaves.map((x) => x.copy());
930
892
  copied.messages = this.messages.map((x) => x.copy());
931
893
  copied.commands = this.commands.map((x) => x.copy());
932
894
  copied.ballots = this.ballots.map((x) => x.copy());
933
- 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());
934
897
  if (this.ballotTree) {
935
898
  copied.ballotTree = this.ballotTree.copy();
936
899
  }
900
+ if (this.voteCountsTree) {
901
+ copied.voteCountsTree = this.voteCountsTree.copy();
902
+ }
937
903
  copied.currentMessageBatchIndex = this.currentMessageBatchIndex;
938
904
  copied.maciStateRef = this.maciStateRef;
939
905
  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());
906
+ copied.perVoteOptionSpentVoiceCredits = this.perVoteOptionSpentVoiceCredits.map((x) => BigInt(x.toString()));
907
+ copied.totalBatchesProcessed = Number(this.totalBatchesProcessed.toString());
942
908
  copied.numBatchesTallied = Number(this.numBatchesTallied.toString());
943
909
  copied.pollId = this.pollId;
944
910
  copied.totalSpentVoiceCredits = BigInt(this.totalSpentVoiceCredits.toString());
945
911
  copied.sbSalts = {};
946
912
  copied.resultRootSalts = {};
947
- copied.preVOSpentVoiceCreditsRootSalts = {};
913
+ copied.perVoteOptionSpentVoiceCreditsRootSalts = {};
948
914
  copied.spentVoiceCreditSubtotalSalts = {};
949
915
  Object.keys(this.sbSalts).forEach((k) => {
950
916
  copied.sbSalts[k] = BigInt(this.sbSalts[k].toString());
@@ -952,41 +918,41 @@ class Poll {
952
918
  Object.keys(this.resultRootSalts).forEach((k) => {
953
919
  copied.resultRootSalts[k] = BigInt(this.resultRootSalts[k].toString());
954
920
  });
955
- Object.keys(this.preVOSpentVoiceCreditsRootSalts).forEach((k) => {
956
- copied.preVOSpentVoiceCreditsRootSalts[k] = BigInt(this.preVOSpentVoiceCreditsRootSalts[k].toString());
921
+ Object.keys(this.perVoteOptionSpentVoiceCreditsRootSalts).forEach((k) => {
922
+ copied.perVoteOptionSpentVoiceCreditsRootSalts[k] = BigInt(this.perVoteOptionSpentVoiceCreditsRootSalts[k].toString());
957
923
  });
958
924
  Object.keys(this.spentVoiceCreditSubtotalSalts).forEach((k) => {
959
925
  copied.spentVoiceCreditSubtotalSalts[k] = BigInt(this.spentVoiceCreditSubtotalSalts[k].toString());
960
926
  });
961
927
  // update the number of signups
962
- copied.setNumSignups(this.numSignups);
928
+ copied.setTotalSignups(this.totalSignups);
963
929
  return copied;
964
930
  };
965
931
  /**
966
932
  * Check if the Poll object is equal to another Poll object.
967
- * @param p - The Poll object to compare.
933
+ * @param poll - The Poll object to compare.
968
934
  * @returns True if the two Poll objects are equal, false otherwise.
969
935
  */
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;
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;
980
946
  if (!result) {
981
947
  return false;
982
948
  }
983
949
  for (let i = 0; i < this.messages.length; i += 1) {
984
- if (!this.messages[i].equals(p.messages[i])) {
950
+ if (!this.messages[i].equals(poll.messages[i])) {
985
951
  return false;
986
952
  }
987
953
  }
988
- for (let i = 0; i < this.encPubKeys.length; i += 1) {
989
- 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])) {
990
956
  return false;
991
957
  }
992
958
  }
@@ -997,40 +963,73 @@ class Poll {
997
963
  * @param serializedPrivateKey - the serialized private key
998
964
  */
999
965
  this.setCoordinatorKeypair = (serializedPrivateKey) => {
1000
- this.coordinatorKeypair = new domainobjs_1.Keypair(domainobjs_1.PrivKey.deserialize(serializedPrivateKey));
966
+ this.coordinatorKeypair = new domainobjs_1.Keypair(domainobjs_1.PrivateKey.deserialize(serializedPrivateKey));
1001
967
  };
1002
968
  /**
1003
969
  * Set the number of signups to match the ones from the contract
1004
- * @param numSignups - the number of signups
970
+ * @param totalSignups - the number of signups
1005
971
  */
1006
- this.setNumSignups = (numSignups) => {
1007
- this.numSignups = numSignups;
972
+ this.setTotalSignups = (totalSignups) => {
973
+ this.totalSignups = totalSignups;
1008
974
  };
1009
975
  /**
1010
976
  * Get the number of signups
1011
977
  * @returns The number of signups
1012
978
  */
1013
- 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
+ }
1014
983
  this.pollEndTimestamp = pollEndTimestamp;
1015
984
  this.coordinatorKeypair = coordinatorKeypair;
1016
985
  this.treeDepths = treeDepths;
1017
986
  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
987
  this.voteOptions = voteOptions;
1022
988
  this.maxVoteOptions = constants_1.VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth;
1023
989
  this.maciStateRef = maciStateRef;
1024
990
  this.pollId = BigInt(maciStateRef.polls.size);
1025
- this.stateTreeDepth = maciStateRef.stateTreeDepth;
1026
- this.actualStateTreeDepth = maciStateRef.stateTreeDepth;
991
+ this.actualStateTreeDepth = treeDepths.stateTreeDepth;
1027
992
  this.currentMessageBatchIndex = 0;
993
+ this.mode = mode;
1028
994
  this.pollNullifiers = new Map();
1029
995
  this.tallyResult = new Array(this.maxVoteOptions).fill(0n);
1030
- this.perVOSpentVoiceCredits = new Array(this.maxVoteOptions).fill(0n);
996
+ this.perVoteOptionSpentVoiceCredits = new Array(this.maxVoteOptions).fill(0n);
1031
997
  // we put a blank state leaf to prevent a DoS attack
1032
- this.emptyBallot = domainobjs_1.Ballot.genBlankBallot(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
998
+ this.emptyBallot = domainobjs_1.Ballot.generateBlank(this.maxVoteOptions, treeDepths.voteOptionTreeDepth);
1033
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
+ }
1034
1033
  }
1035
1034
  /**
1036
1035
  * Serialize the Poll object to a JSON object
@@ -1038,6 +1037,7 @@ class Poll {
1038
1037
  */
1039
1038
  toJSON() {
1040
1039
  return {
1040
+ stateTreeDepth: Number(this.treeDepths.stateTreeDepth),
1041
1041
  pollEndTimestamp: this.pollEndTimestamp.toString(),
1042
1042
  treeDepths: this.treeDepths,
1043
1043
  batchSizes: this.batchSizes,
@@ -1046,16 +1046,18 @@ class Poll {
1046
1046
  messages: this.messages.map((message) => message.toJSON()),
1047
1047
  commands: this.commands.map((command) => command.toJSON()),
1048
1048
  ballots: this.ballots.map((ballot) => ballot.toJSON()),
1049
- encPubKeys: this.encPubKeys.map((encPubKey) => encPubKey.serialize()),
1049
+ voteCounts: this.voteCounts.map((voteCounts) => voteCounts.toJSON()),
1050
+ encryptionPublicKeys: this.encryptionPublicKeys.map((encryptionPublicKey) => encryptionPublicKey.serialize()),
1050
1051
  currentMessageBatchIndex: this.currentMessageBatchIndex,
1051
- pubKeys: this.pubKeys.map((leaf) => leaf.toJSON()),
1052
+ publicKeys: this.publicKeys.map((leaf) => leaf.toJSON()),
1052
1053
  pollStateLeaves: this.pollStateLeaves.map((leaf) => leaf.toJSON()),
1053
1054
  results: this.tallyResult.map((result) => result.toString()),
1054
- numBatchesProcessed: this.numBatchesProcessed,
1055
- numSignups: this.numSignups.toString(),
1055
+ totalBatchesProcessed: this.totalBatchesProcessed,
1056
+ totalSignups: this.totalSignups.toString(),
1056
1057
  chainHash: this.chainHash.toString(),
1057
1058
  pollNullifiers: [...this.pollNullifiers.keys()].map((nullifier) => nullifier.toString()),
1058
1059
  batchHashes: this.batchHashes.map((batchHash) => batchHash.toString()),
1060
+ mode: this.mode,
1059
1061
  };
1060
1062
  }
1061
1063
  /**
@@ -1065,21 +1067,22 @@ class Poll {
1065
1067
  * @returns a new Poll instance
1066
1068
  */
1067
1069
  static fromJSON(json, maciState) {
1068
- 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);
1069
1071
  // set all properties
1070
1072
  poll.pollStateLeaves = json.pollStateLeaves.map((leaf) => domainobjs_1.StateLeaf.fromJSON(leaf));
1071
1073
  poll.ballots = json.ballots.map((ballot) => domainobjs_1.Ballot.fromJSON(ballot));
1072
- 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));
1073
1076
  poll.messages = json.messages.map((message) => domainobjs_1.Message.fromJSON(message));
1074
- poll.commands = json.commands.map((command) => domainobjs_1.PCommand.fromJSON(command));
1077
+ poll.commands = json.commands.map((command) => domainobjs_1.VoteCommand.fromJSON(command));
1075
1078
  poll.tallyResult = json.results.map((result) => BigInt(result));
1076
1079
  poll.currentMessageBatchIndex = json.currentMessageBatchIndex;
1077
- poll.numBatchesProcessed = json.numBatchesProcessed;
1080
+ poll.totalBatchesProcessed = json.totalBatchesProcessed;
1078
1081
  poll.chainHash = BigInt(json.chainHash);
1079
1082
  poll.batchHashes = json.batchHashes.map((batchHash) => BigInt(batchHash));
1080
1083
  poll.pollNullifiers = new Map(json.pollNullifiers.map((nullifier) => [BigInt(nullifier), true]));
1081
1084
  // copy maci state
1082
- poll.updatePoll(BigInt(json.numSignups));
1085
+ poll.updatePoll(BigInt(json.totalSignups));
1083
1086
  return poll;
1084
1087
  }
1085
1088
  }