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