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