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