From 0af7dd2d448c4796c3872665d43654343f9fc3e3 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Fri, 5 Jan 2024 18:42:30 +0000 Subject: [PATCH] refactor(core): refactor the core package subsidy and tally functions Refactor the last few functions left inside the Poll.ts file, as well as add interfaces for circuit inputs --- circuits/ts/__tests__/CeremonyParams.test.ts | 2 +- circuits/ts/__tests__/TallyVotes.test.ts | 6 +- cli/ts/commands/genProofs.ts | 10 +- contracts/tests/MessageProcessor.test.ts | 12 +- contracts/tests/Subsidy.test.ts | 10 +- contracts/tests/Tally.test.ts | 16 ++- core/ts/Poll.ts | 144 +++++++++---------- core/ts/__tests__/MaciState.test.ts | 82 ++--------- core/ts/index.ts | 14 +- core/ts/utils/types.ts | 82 ++++++++++- core/ts/utils/utils.ts | 6 +- integrationTests/package.json | 2 +- 12 files changed, 206 insertions(+), 180 deletions(-) diff --git a/circuits/ts/__tests__/CeremonyParams.test.ts b/circuits/ts/__tests__/CeremonyParams.test.ts index 9dc8ba2609..60fcec82ba 100644 --- a/circuits/ts/__tests__/CeremonyParams.test.ts +++ b/circuits/ts/__tests__/CeremonyParams.test.ts @@ -277,7 +277,7 @@ describe("Ceremony param tests", () => { randIdx = generateRandomIndex(Object.keys(generatedInputs).length); } - (generatedInputs.currentResults as string[])[randIdx] = "1"; + generatedInputs.currentResults[randIdx] = "1"; const witness = await testCircuit.calculateWitness(generatedInputs); await testCircuit.checkConstraints(witness); }); diff --git a/circuits/ts/__tests__/TallyVotes.test.ts b/circuits/ts/__tests__/TallyVotes.test.ts index 60683417c4..a6a52cece1 100644 --- a/circuits/ts/__tests__/TallyVotes.test.ts +++ b/circuits/ts/__tests__/TallyVotes.test.ts @@ -117,7 +117,7 @@ describe("TallyVotes circuit", function test() { randIdx = generateRandomIndex(Object.keys(generatedInputs).length); } - (generatedInputs.currentResults as string[])[randIdx] = "1"; + generatedInputs.currentResults[randIdx] = "1"; const witness = await circuit.calculateWitness(generatedInputs); await circuit.checkConstraints(witness); }); @@ -176,9 +176,9 @@ describe("TallyVotes circuit", function test() { // currentSpentVoiceCreditSubtotal, and // currentPerVOSpentVoiceCredits if (i === 0) { - (generatedInputs.currentResults as string[])[0] = "123"; + generatedInputs.currentResults[0] = "123"; generatedInputs.currentSpentVoiceCreditSubtotal = "456"; - (generatedInputs.currentPerVOSpentVoiceCredits as string[])[0] = "789"; + generatedInputs.currentPerVOSpentVoiceCredits[0] = "789"; } // eslint-disable-next-line no-await-in-loop diff --git a/cli/ts/commands/genProofs.ts b/cli/ts/commands/genProofs.ts index c8ac630c5f..4083f1031e 100644 --- a/cli/ts/commands/genProofs.ts +++ b/cli/ts/commands/genProofs.ts @@ -236,7 +236,7 @@ export const genProofs = async ( let maciState: MaciState | undefined; if (stateFile) { - const content = JSON.parse(fs.readFileSync(stateFile).toString()) as IJsonMaciState; + const content = JSON.parse(fs.readFileSync(stateFile).toString()) as unknown as IJsonMaciState; const serializedPrivateKey = maciPrivKey.serialize(); try { @@ -287,7 +287,7 @@ export const genProofs = async ( // while we have unprocessed messages, process them while (poll.hasUnprocessedMessages()) { // process messages in batches - const circuitInputs = poll.processMessages(pollId); + const circuitInputs = poll.processMessages(pollId) as unknown as CircuitInputs; try { // generate the proof for this batch // eslint-disable-next-line no-await-in-loop @@ -345,13 +345,11 @@ export const genProofs = async ( let numBatchesCalulated = 0; - // @todo fix types in the circuits package - // @todo why this next part works let subsidyCircuitInputs: CircuitInputs; // calculate the subsidy for each batch while (poll.hasUnfinishedSubsidyCalculation()) { // calculate subsidy in batches - subsidyCircuitInputs = poll.subsidyPerBatch(); + subsidyCircuitInputs = poll.subsidyPerBatch() as unknown as CircuitInputs; try { // generate proof for this batch // eslint-disable-next-line no-await-in-loop @@ -423,7 +421,7 @@ export const genProofs = async ( // tally all ballots for this poll while (poll.hasUntalliedBallots()) { // tally votes in batches - tallyCircuitInputs = poll.tallyVotes(); + tallyCircuitInputs = poll.tallyVotes() as unknown as CircuitInputs; try { // generate the proof diff --git a/contracts/tests/MessageProcessor.test.ts b/contracts/tests/MessageProcessor.test.ts index 35c9b40d48..988ec55fa0 100644 --- a/contracts/tests/MessageProcessor.test.ts +++ b/contracts/tests/MessageProcessor.test.ts @@ -2,7 +2,13 @@ import { expect } from "chai"; import { BaseContract, Signer } from "ethers"; import { EthereumProvider } from "hardhat/types"; -import { MaciState, Poll, STATE_TREE_DEPTH, packProcessMessageSmallVals } from "maci-core"; +import { + MaciState, + Poll, + STATE_TREE_DEPTH, + packProcessMessageSmallVals, + IProcessMessagesCircuitInputs, +} from "maci-core"; import { NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { Keypair, Message, PubKey } from "maci-domainobjs"; @@ -36,7 +42,7 @@ describe("MessageProcessor", () => { const maciState = new MaciState(STATE_TREE_DEPTH); let signer: Signer; - let generatedInputs: { newSbCommitment: bigint }; + let generatedInputs: IProcessMessagesCircuitInputs; const coordinator = new Keypair(); const users = [new Keypair(), new Keypair()]; @@ -87,7 +93,7 @@ describe("MessageProcessor", () => { maciState.polls[pollId].publishMessage(message, padKey); poll = maciState.polls[pollId]; - generatedInputs = poll.processMessages(pollId) as typeof generatedInputs; + generatedInputs = poll.processMessages(pollId); // set the verification keys on the vk smart contract const vkContract = r.vkRegistryContract; diff --git a/contracts/tests/Subsidy.test.ts b/contracts/tests/Subsidy.test.ts index 95f43a0d0a..4bde80c2b3 100644 --- a/contracts/tests/Subsidy.test.ts +++ b/contracts/tests/Subsidy.test.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { BaseContract, Signer } from "ethers"; import { EthereumProvider } from "hardhat/types"; -import { MaciState, Poll, packSubsidySmallVals } from "maci-core"; +import { MaciState, Poll, packSubsidySmallVals, IProcessMessagesCircuitInputs, ISubsidyCircuitInputs } from "maci-core"; import { NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { Keypair, Message, PubKey } from "maci-domainobjs"; @@ -37,7 +37,7 @@ describe("Subsidy", () => { let pollId: number; let poll: Poll; - let generatedInputs: { newSbCommitment: bigint }; + let generatedInputs: IProcessMessagesCircuitInputs; before(async () => { signer = await getDefaultSigner(); @@ -87,7 +87,7 @@ describe("Subsidy", () => { poll = maciState.polls[pollId]; // process messages locally - generatedInputs = poll.processMessages(pollId) as typeof generatedInputs; + generatedInputs = poll.processMessages(pollId); // set the verification keys on the vk smart contract const vkContract = r.vkRegistryContract; @@ -145,14 +145,14 @@ describe("Subsidy", () => { }); describe("after merging acc queues", () => { - let subsidyGeneratedInputs: { newSubsidyCommitment: bigint }; + let subsidyGeneratedInputs: ISubsidyCircuitInputs; before(async () => { await pollContract.mergeMaciStateAqSubRoots(0, pollId); await pollContract.mergeMaciStateAq(0); await pollContract.mergeMessageAqSubRoots(0); await pollContract.mergeMessageAq(); - subsidyGeneratedInputs = poll.subsidyPerBatch() as { newSubsidyCommitment: bigint }; + subsidyGeneratedInputs = poll.subsidyPerBatch(); }); it("updateSubsidy() should update the tally commitment", async () => { // do the processing on the message processor contract diff --git a/contracts/tests/Tally.test.ts b/contracts/tests/Tally.test.ts index 3f1ef77b50..80fde86c59 100644 --- a/contracts/tests/Tally.test.ts +++ b/contracts/tests/Tally.test.ts @@ -2,7 +2,13 @@ import { expect } from "chai"; import { BaseContract, Signer } from "ethers"; import { EthereumProvider } from "hardhat/types"; -import { MaciState, Poll, packTallyVotesSmallVals } from "maci-core"; +import { + MaciState, + Poll, + packTallyVotesSmallVals, + IProcessMessagesCircuitInputs, + ITallyCircuitInputs, +} from "maci-core"; import { NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { Keypair, Message, PubKey } from "maci-domainobjs"; @@ -39,7 +45,7 @@ describe("TallyVotes", () => { let pollId: number; let poll: Poll; - let generatedInputs: { newSbCommitment: bigint }; + let generatedInputs: IProcessMessagesCircuitInputs; before(async () => { signer = await getDefaultSigner(); @@ -89,7 +95,7 @@ describe("TallyVotes", () => { poll = maciState.polls[pollId]; // process messages locally - generatedInputs = poll.processMessages(pollId) as typeof generatedInputs; + generatedInputs = poll.processMessages(pollId); // set the verification keys on the vk smart contract const vkContract = r.vkRegistryContract; @@ -140,14 +146,14 @@ describe("TallyVotes", () => { }); describe("after merging acc queues", () => { - let tallyGeneratedInputs: { newTallyCommitment: bigint }; + let tallyGeneratedInputs: ITallyCircuitInputs; before(async () => { await pollContract.mergeMaciStateAqSubRoots(0, pollId); await pollContract.mergeMaciStateAq(0); await pollContract.mergeMessageAqSubRoots(0); await pollContract.mergeMessageAq(); - tallyGeneratedInputs = poll.tallyVotes() as { newTallyCommitment: bigint }; + tallyGeneratedInputs = poll.tallyVotes(); }); it("tallyVotes() should update the tally commitment", async () => { // do the processing on the message processor contract diff --git a/core/ts/Poll.ts b/core/ts/Poll.ts index c7beade4d8..f254b07925 100644 --- a/core/ts/Poll.ts +++ b/core/ts/Poll.ts @@ -32,7 +32,7 @@ import assert from "assert"; import type { MaciState } from "./MaciState"; import type { PathElements } from "maci-crypto"; -import { STATE_TREE_ARITY, MESSAGE_TREE_ARITY, VOTE_OPTION_TREE_ARITY } from "./utils/constants"; +import { STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "./utils/constants"; import { ProcessMessageErrors, ProcessMessageError } from "./utils/errors"; import { CircuitInputs, @@ -42,6 +42,9 @@ import { IPoll, IJsonPoll, IProcessMessagesOutput, + ITallyCircuitInputs, + ISubsidyCircuitInputs, + IProcessMessagesCircuitInputs, } from "./utils/types"; import { packTallyVotesSmallVals, packSubsidySmallVals } from "./utils/utils"; @@ -407,7 +410,7 @@ export class Poll implements IPoll { * process * @returns stringified circuit inputs */ - processMessages = (pollId: number): CircuitInputs => { + processMessages = (pollId: number): IProcessMessagesCircuitInputs => { assert(this.hasUnprocessedMessages(), "No more messages to process"); const batchSize = this.batchSizes.messageBatchSize; @@ -530,12 +533,7 @@ export class Poll implements IPoll { currentVoteWeights.unshift(this.ballots[0].votes[0]); // create a new quinary tree and add an empty vote - const vt = new IncrementalQuinTree( - this.treeDepths.voteOptionTreeDepth, - BigInt(0), - STATE_TREE_ARITY, - hash5, - ); + const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, STATE_TREE_ARITY, hash5); vt.insert(this.ballots[0].votes[0]); // get the path elements for this empty vote weight leaf currentVoteWeightsPathElements.unshift(vt.genMerklePath(0).pathElements); @@ -570,12 +568,7 @@ export class Poll implements IPoll { currentVoteWeights.unshift(currentBallot.votes[0]); // create a quinary tree to fill with the votes of the current ballot - const vt = new IncrementalQuinTree( - this.treeDepths.voteOptionTreeDepth, - BigInt(0), - STATE_TREE_ARITY, - hash5, - ); + const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, STATE_TREE_ARITY, hash5); for (let j = 0; j < this.ballots[0].votes.length; j += 1) { vt.insert(currentBallot.votes[j]); @@ -660,7 +653,8 @@ export class Poll implements IPoll { if (this.numBatchesProcessed * batchSize >= this.messages.length) { this.maciStateRef.pollBeingProcessed = false; } - return stringifyBigInts(circuitInputs) as CircuitInputs; + + return stringifyBigInts(circuitInputs) as unknown as IProcessMessagesCircuitInputs; }; /** @@ -777,10 +771,7 @@ export class Poll implements IPoll { * Checks whether there are any untallied ballots. * @returns Whether there are any untallied ballots */ - hasUntalliedBallots = (): boolean => { - const batchSize = this.batchSizes.tallyBatchSize; - return this.numBatchesTallied * batchSize < this.ballots.length; - }; + hasUntalliedBallots = (): boolean => this.numBatchesTallied * this.batchSizes.tallyBatchSize < this.ballots.length; /** * This method checks if there are any unfinished subsidy calculations. @@ -798,7 +789,7 @@ export class Poll implements IPoll { * This method calculates the subsidy per batch. * @returns Returns an array of big integers which represent the circuit inputs for the subsidy calculation. */ - subsidyPerBatch = (): CircuitInputs => { + subsidyPerBatch = (): ISubsidyCircuitInputs => { const batchSize = this.batchSizes.subsidyBatchSize; assert(this.hasUnfinishedSubsidyCalculation(), "No more subsidy batches to calculate"); @@ -854,10 +845,10 @@ export class Poll implements IPoll { votes2: ballots2.map((x) => x.votes), ballotPathElements1: ballotSubrootProof1!.pathElements, ballotPathElements2: ballotSubrootProof2!.pathElements, - }); + }) as unknown as ISubsidyCircuitInputs; this.increaseSubsidyIndex(); - return circuitInputs as CircuitInputs; + return circuitInputs; }; /** @@ -865,6 +856,7 @@ export class Poll implements IPoll { */ private increaseSubsidyIndex = (): void => { const batchSize = this.batchSizes.subsidyBatchSize; + if (this.cbi * batchSize + batchSize < this.ballots.length) { this.cbi += 1; } else { @@ -882,6 +874,7 @@ export class Poll implements IPoll { private previousSubsidyIndexToString = (): string => { const batchSize = this.batchSizes.subsidyBatchSize; const numBatches = Math.ceil(this.ballots.length / batchSize); + let { cbi } = this; let { rbi } = this; @@ -895,6 +888,7 @@ export class Poll implements IPoll { rbi -= 1; cbi = numBatches - 1; } + return `${rbi.toString()}-${cbi.toString()}`; }; @@ -908,7 +902,7 @@ export class Poll implements IPoll { private coefficientCalculation = (rowBallot: Ballot, colBallot: Ballot): bigint => { let sum = BigInt(0); for (let p = 0; p < this.maxValues.maxVoteOptions; p += 1) { - sum += BigInt(rowBallot.votes[p].valueOf()) * BigInt(colBallot.votes[p].valueOf()); + sum += BigInt(rowBallot.votes[p].valueOf()) * colBallot.votes[p]; } const res = BigInt(this.MM * 10 ** this.WW) / (BigInt(this.MM) + BigInt(sum)); return res; @@ -946,7 +940,7 @@ export class Poll implements IPoll { const vip = BigInt(rowBallot.votes[p].valueOf()); const vjp = BigInt(colBallot.votes[p].valueOf()); if (rowStartIndex !== colStartIndex || (rowStartIndex === colStartIndex && i < j)) { - this.subsidy[p] = BigInt(this.subsidy[p].valueOf()) + BigInt(2) * BigInt(kij.valueOf()) * vip * vjp; + this.subsidy[p] += 2n * kij * vip * vjp; } } } @@ -959,15 +953,16 @@ export class Poll implements IPoll { * This method tallies a ballots and updates the tally results. * @returns the circuit inputs for the TallyVotes circuit. */ - tallyVotes = (): CircuitInputs => { + tallyVotes = (): ITallyCircuitInputs => { const batchSize = this.batchSizes.tallyBatchSize; assert(this.hasUntalliedBallots(), "No more ballots to tally"); + // calculate where we start tallying next const batchStartIndex = this.numBatchesTallied * batchSize; - const currentResultsRootSalt = - batchStartIndex === 0 ? BigInt(0) : this.resultRootSalts[batchStartIndex - batchSize]; + // get the salts needed for the commitments + const currentResultsRootSalt = batchStartIndex === 0 ? 0n : this.resultRootSalts[batchStartIndex - batchSize]; const currentPerVOSpentVoiceCreditsRootSalt = batchStartIndex === 0 ? BigInt(0) : this.preVOSpentVoiceCreditsRootSalts[batchStartIndex - batchSize]; @@ -975,18 +970,32 @@ export class Poll implements IPoll { const currentSpentVoiceCreditSubtotalSalt = batchStartIndex === 0 ? BigInt(0) : this.spentVoiceCreditSubtotalSalts[batchStartIndex - batchSize]; - const currentResultsCommitment = this.genResultsCommitment(currentResultsRootSalt); + // generate a commitment to the current results + const currentResultsCommitment = genTreeCommitment( + this.tallyResult, + currentResultsRootSalt, + this.treeDepths.voteOptionTreeDepth, + ); + // generate a commitment to the current per VO spent voice credits const currentPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment( currentPerVOSpentVoiceCreditsRootSalt, batchStartIndex, ); + // generate a commitment to the current spent voice credits const currentSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment( currentSpentVoiceCreditSubtotalSalt, batchStartIndex, ); + // the current commitment for the first batch will be 0 + // otherwise calculate as + // hash([ + // currentResultsCommitment, + // currentSpentVoiceCreditsCommitment, + // currentPerVOSpentVoiceCreditsCommitment + // ]) const currentTallyCommitment = batchStartIndex === 0 ? BigInt(0) @@ -1001,56 +1010,75 @@ export class Poll implements IPoll { const currentPerVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x) => BigInt(x.toString())); const currentSpentVoiceCreditSubtotal = BigInt(this.totalSpentVoiceCredits.toString()); + // loop in normal order to tally the ballots one by one for (let i = this.numBatchesTallied * batchSize; i < this.numBatchesTallied * batchSize + batchSize; i += 1) { + // we stop if we have no more ballots to tally if (i >= this.ballots.length) { break; } + // save to the local ballot array ballots.push(this.ballots[i]); + // for each possible vote option we loop and calculate for (let j = 0; j < this.maxValues.maxVoteOptions; j += 1) { - const v = BigInt(`${this.ballots[i].votes[j]}`); + const v = this.ballots[i].votes[j]; - this.tallyResult[j] = BigInt(`${this.tallyResult[j]}`) + v; + // the vote itself will be a quadratic vote (sqrt(voiceCredits)) + this.tallyResult[j] += v; - this.perVOSpentVoiceCredits[j] = BigInt(`${this.perVOSpentVoiceCredits[j]}`) + BigInt(v) * BigInt(v); + // the per vote option spent voice credits will be the sum of the squares of the votes + this.perVOSpentVoiceCredits[j] += v * v; - this.totalSpentVoiceCredits = BigInt(`${this.totalSpentVoiceCredits}`) + BigInt(v) * BigInt(v); + // the total spent voice credits will be the sum of the squares of the votes + this.totalSpentVoiceCredits += v * v; } } const emptyBallot = new Ballot(this.maxValues.maxVoteOptions, this.treeDepths.voteOptionTreeDepth); + // pad the ballots array while (ballots.length < batchSize) { ballots.push(emptyBallot); } + // generate the new salts const newResultsRootSalt = genRandomSalt(); const newPerVOSpentVoiceCreditsRootSalt = genRandomSalt(); const newSpentVoiceCreditSubtotalSalt = genRandomSalt(); + // and save them to be used in the next batch this.resultRootSalts[batchStartIndex] = newResultsRootSalt; this.preVOSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVOSpentVoiceCreditsRootSalt; this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt; - const newResultsCommitment = this.genResultsCommitment(newResultsRootSalt); + // generate the new results commitment with the new salts and data + const newResultsCommitment = genTreeCommitment( + this.tallyResult, + newResultsRootSalt, + this.treeDepths.voteOptionTreeDepth, + ); + // generate the new spent voice credits commitment with the new salts and data const newSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment( newSpentVoiceCreditSubtotalSalt, batchStartIndex + batchSize, ); + // generate the new per VO spent voice credits commitment with the new salts and data const newPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment( newPerVOSpentVoiceCreditsRootSalt, batchStartIndex + batchSize, ); + // generate the new tally commitment const newTallyCommitment = hash3([ newResultsCommitment, newSpentVoiceCreditsCommitment, newPerVOSpentVoiceCreditsCommitment, ]); + // cache vars const stateRoot = this.stateTree!.root; const ballotRoot = this.ballotTree!.root; const sbSalt = this.sbSalts[this.currentMessageBatchIndex!]; @@ -1084,32 +1112,11 @@ export class Poll implements IPoll { newResultsRootSalt, newPerVOSpentVoiceCreditsRootSalt, newSpentVoiceCreditSubtotalSalt, - }); + }) as unknown as ITallyCircuitInputs; this.numBatchesTallied += 1; - return circuitInputs as CircuitInputs; - }; - - /** - * This method generates a commitment to the votes per option. - * This is the hash of the Merkle root of the votes and a salt, computed as Poseidon([root, _salt]). - * @param _salt - The salt used in the hash function. - * @returns Returns the hash of the Merkle root of the votes and a salt, computed as Poseidon([root, _salt]). - */ - private genResultsCommitment = (salt: bigint): bigint => { - const resultsTree = new IncrementalQuinTree( - this.treeDepths.voteOptionTreeDepth, - BigInt(0), - VOTE_OPTION_TREE_ARITY, - hash5, - ); - - this.tallyResult.forEach((r) => { - resultsTree.insert(r); - }); - - return hashLeftRight(resultsTree.root, salt); + return circuitInputs; }; /** @@ -1145,34 +1152,21 @@ export class Poll implements IPoll { * @returns Returns the hash of the Merkle root of the spent voice credits per vote option and a salt, computed as Poseidon([root, _salt]). */ private genPerVOSpentVoiceCreditsCommitment = (salt: bigint, numBallotsToCount: number): bigint => { - const resultsTree = new IncrementalQuinTree( - this.treeDepths.voteOptionTreeDepth, - BigInt(0), - VOTE_OPTION_TREE_ARITY, - hash5, - ); - - const leaves: bigint[] = []; - - this.tallyResult.forEach(() => { - leaves.push(BigInt(0)); - }); + const leaves: bigint[] = Array(this.tallyResult.length).fill(0n); for (let i = 0; i < numBallotsToCount; i += 1) { + // check that is a valid index if (i >= this.ballots.length) { break; } + for (let j = 0; j < this.tallyResult.length; j += 1) { - const v = BigInt(`${this.ballots[i].votes[j]}`); - leaves[j] = BigInt(`${leaves[j]}`) + v * v; + const v = this.ballots[i].votes[j]; + leaves[j] += v * v; } } - leaves.forEach((leaf) => { - resultsTree.insert(leaf); - }); - - return hashLeftRight(resultsTree.root, salt); + return genTreeCommitment(leaves, salt, this.treeDepths.voteOptionTreeDepth); }; /** diff --git a/core/ts/__tests__/MaciState.test.ts b/core/ts/__tests__/MaciState.test.ts index 5d597419d0..0165597686 100644 --- a/core/ts/__tests__/MaciState.test.ts +++ b/core/ts/__tests__/MaciState.test.ts @@ -34,15 +34,7 @@ describe("MaciState", function test() { messageBatchSize, coordinatorKeypair, ); - const command = new PCommand( - BigInt(0), - userKeypair.pubKey, - BigInt(0), - BigInt(0), - BigInt(0), - BigInt(pollId), - BigInt(0), - ); + const command = new PCommand(0n, userKeypair.pubKey, 0n, 0n, 0n, BigInt(pollId), 0n); const encKeypair = new Keypair(); const signature = command.sign(encKeypair.privKey); @@ -186,14 +178,7 @@ describe("MaciState", function test() { }); it("should throw if a message has an invalid nonce", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - BigInt(0), - BigInt(0), - BigInt(0), - BigInt(pollId), - ); + const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 0n, 0n, BigInt(pollId)); const signature = command.sign(user1Keypair.privKey); @@ -209,14 +194,7 @@ describe("MaciState", function test() { }); it("should throw if a message has an invalid signature", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - BigInt(0), - BigInt(0), - BigInt(0), - BigInt(pollId), - ); + const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 0n, 0n, BigInt(pollId)); const signature = command.sign(new PrivKey(BigInt(0))); const ecdhKeypair = new Keypair(); @@ -279,14 +257,7 @@ describe("MaciState", function test() { }); it("should throw if a message has an invalid vote option index (< 0)", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - BigInt(-1), - BigInt(1), - BigInt(1), - BigInt(pollId), - ); + const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, BigInt(-1), 1n, 1n, BigInt(pollId)); const signature = command.sign(user1Keypair.privKey); @@ -302,14 +273,7 @@ describe("MaciState", function test() { }); it("should throw when passed a message that cannot be decrypted (wrong encPubKey)", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - BigInt(0), - BigInt(1), - BigInt(1), - BigInt(pollId), - ); + const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 1n, BigInt(pollId)); const signature = command.sign(user1Keypair.privKey); @@ -325,14 +289,7 @@ describe("MaciState", function test() { }); it("should throw when passed a corrupted message", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - BigInt(0), - BigInt(1), - BigInt(1), - BigInt(pollId), - ); + const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 1n, BigInt(pollId)); const signature = command.sign(user1Keypair.privKey); @@ -371,14 +328,7 @@ describe("MaciState", function test() { ); it("should throw if this is the first batch and currentMessageBatchIndex is defined", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - BigInt(0), - BigInt(1), - BigInt(0), - BigInt(pollId), - ); + const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); const signature = command.sign(user1Keypair.privKey); @@ -490,14 +440,7 @@ describe("MaciState", function test() { }); it("should return the correct state leaves and ballots", () => { - const command = new PCommand( - BigInt(user1StateIndex + 1), - user1Keypair.pubKey, - BigInt(0), - BigInt(1), - BigInt(0), - BigInt(pollId), - ); + const command = new PCommand(BigInt(user1StateIndex + 1), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); const signature = command.sign(user1Keypair.privKey); @@ -517,14 +460,7 @@ describe("MaciState", function test() { }); it("should have processed all messages", () => { - const command = new PCommand( - BigInt(user1StateIndex + 1), - user1Keypair.pubKey, - BigInt(0), - BigInt(1), - BigInt(0), - BigInt(pollId), - ); + const command = new PCommand(BigInt(user1StateIndex + 1), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); const signature = command.sign(user1Keypair.privKey); diff --git a/core/ts/index.ts b/core/ts/index.ts index 5b61860f6b..ed26a5bea1 100644 --- a/core/ts/index.ts +++ b/core/ts/index.ts @@ -1,5 +1,7 @@ export { MaciState } from "./MaciState"; + export { Poll } from "./Poll"; + export { genProcessVkSig, genTallyVkSig, @@ -11,7 +13,17 @@ export { packSubsidySmallVals, } from "./utils/utils"; -export type { CircuitInputs, MaxValues, TreeDepths, BatchSizes, IJsonMaciState } from "./utils/types"; +export type { + ITallyCircuitInputs, + IProcessMessagesCircuitInputs, + ISubsidyCircuitInputs, + CircuitInputs, + MaxValues, + TreeDepths, + BatchSizes, + IJsonMaciState, +} from "./utils/types"; + export { STATE_TREE_DEPTH, STATE_TREE_ARITY, diff --git a/core/ts/utils/types.ts b/core/ts/utils/types.ts index 9199c003c6..974f50ebf8 100644 --- a/core/ts/utils/types.ts +++ b/core/ts/utils/types.ts @@ -86,14 +86,14 @@ export interface IPoll { publishMessage(message: Message, encPubKey: PubKey): void; topupMessage(message: Message): void; // These methods are used to generate circuit inputs - processMessages(pollId: number): CircuitInputs; - tallyVotes(): CircuitInputs; + processMessages(pollId: number): IProcessMessagesCircuitInputs; + tallyVotes(): ITallyCircuitInputs; // These methods are helper functions hasUnprocessedMessages(): boolean; processAllMessages(): { stateLeaves: StateLeaf[]; ballots: Ballot[] }; hasUntalliedBallots(): boolean; hasUnfinishedSubsidyCalculation(): boolean; - subsidyPerBatch(): CircuitInputs; + subsidyPerBatch(): ISubsidyCircuitInputs; copy(): Poll; equals(p: Poll): boolean; toJSON(): IJsonPoll; @@ -145,3 +145,79 @@ export interface IProcessMessagesOutput { originalBallotPathElements?: PathElements; command?: PCommand | TCommand; } + +/** + * An interface describing the circuit inputs to the ProcessMessage circuit + */ +export interface IProcessMessagesCircuitInputs { + pollEndTimestamp: string; + packedVals: string; + msgRoot: string; + msgs: string[]; + msgSubrootPathElements: string[][]; + coordPrivKey: string; + coordPubKey: string; + encPubKeys: string[]; + currentStateRoot: string; + currentBallotRoot: string; + currentSbCommitment: string; + currentSbSalt: string; + currentStateLeaves: string[]; + currentStateLeavesPathElements: string[][]; + currentBallots: string[]; + currentBallotsPathElements: string[][]; + currentVoteWeights: string[]; + currentVoteWeightsPathElements: string[][]; + inputHash: string; + newSbSalt: string; + newSbCommitment: string; +} + +/** + * An interface describing the circuit inputs to the TallyVotes circuit + */ +export interface ITallyCircuitInputs { + stateRoot: string; + ballotRoot: string; + sbSalt: string; + sbCommitment: string; + currentTallyCommitment: string; + newTallyCommitment: string; + packedVals: string; + inputHash: string; + ballots: string[]; + ballotPathElements: PathElements; + votes: string[][]; + currentResults: string[]; + currentResultsRootSalt: string; + currentSpentVoiceCreditSubtotal: string; + currentSpentVoiceCreditSubtotalSalt: string; + currentPerVOSpentVoiceCredits: string[]; + currentPerVOSpentVoiceCreditsRootSalt: string; + newResultsRootSalt: string; + newPerVOSpentVoiceCreditsRootSalt: string; + newSpentVoiceCreditSubtotalSalt: string; +} + +/** + * An interface describing the circuit inputs to the Subsidy circuit + */ +export interface ISubsidyCircuitInputs { + stateRoot: string; + ballotRoot: string; + sbSalt: string; + currentSubsidySalt: string; + newSubsidySalt: string; + sbCommitment: string; + currentSubsidyCommitment: string; + newSubsidyCommitment: string; + currentSubsidy: string[]; + packedVals: string; + inputHash: string; + ballots1: string[]; + ballots2: string[]; + votes1: number[]; + votes2: number[]; + ballotPathElements1: string[]; + ballotPathElements2: string[]; +} diff --git a/core/ts/utils/utils.ts b/core/ts/utils/utils.ts index c509343638..cc6653eaaa 100644 --- a/core/ts/utils/utils.ts +++ b/core/ts/utils/utils.ts @@ -36,8 +36,7 @@ export const genTallyVkSig = ( _stateTreeDepth: number, _intStateTreeDepth: number, _voteOptionTreeDepth: number, -): bigint => - (BigInt(_stateTreeDepth) << BigInt(128)) + (BigInt(_intStateTreeDepth) << BigInt(64)) + BigInt(_voteOptionTreeDepth); +): bigint => (BigInt(_stateTreeDepth) << 128n) + (BigInt(_intStateTreeDepth) << 64n) + BigInt(_voteOptionTreeDepth); /** * This function generates the signature of a Subsidy Verifying Key(VK). @@ -53,8 +52,7 @@ export const genSubsidyVkSig = ( _stateTreeDepth: number, _intStateTreeDepth: number, _voteOptionTreeDepth: number, -): bigint => - (BigInt(_stateTreeDepth) << BigInt(128)) + (BigInt(_intStateTreeDepth) << BigInt(64)) + BigInt(_voteOptionTreeDepth); +): bigint => (BigInt(_stateTreeDepth) << 128n) + (BigInt(_intStateTreeDepth) << 64n) + BigInt(_voteOptionTreeDepth); /** * This function packs it's parameters into a single bigint. diff --git a/integrationTests/package.json b/integrationTests/package.json index 6e7fd73c28..9b6f144d03 100644 --- a/integrationTests/package.json +++ b/integrationTests/package.json @@ -9,7 +9,7 @@ "test": "ts-mocha --exit ./ts/__tests__/**.test.ts", "test:genMaciKeypair": "ts-mocha --exit ./ts/__tests__/cli-genMaciKeypair.test.ts", "test:genMaciPubkey": "ts-mocha --exit ./ts/__tests__/cli-genMaciPubkey.test.ts", - "test:suites": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ./ts/__tests__/suites.test.ts", + "test:integration": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ./ts/__tests__/integration.test.ts", "test:maciKeys": "ts-mocha --exit ./ts/__tests__/maci-keys.test.ts", "download-zkeys": "./scripts/download_zkeys.sh" },