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