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