From 22a4f286010ff5add413eee88d933c0e74898346 Mon Sep 17 00:00:00 2001 From: Daehyun Paik Date: Mon, 4 Dec 2023 21:24:05 +0100 Subject: [PATCH 1/4] refactor(core): remove unnecessary stateAq from MaciState --- circuits/ts/__tests__/ProcessMessages.test.ts | 11 ---- circuits/ts/__tests__/TallyVotes.test.ts | 6 --- contracts/tests/MACI.test.ts | 5 +- contracts/ts/genMaciState.ts | 6 --- core/ts/MaciState.ts | 50 ++++++++----------- core/ts/__tests__/MaciState.test.ts | 41 +++++++-------- core/ts/index.ts | 2 + core/ts/utils/constants.ts | 10 ++++ 8 files changed, 51 insertions(+), 80 deletions(-) create mode 100644 core/ts/utils/constants.ts diff --git a/circuits/ts/__tests__/ProcessMessages.test.ts b/circuits/ts/__tests__/ProcessMessages.test.ts index 7742bf32f4..f9cf42e431 100644 --- a/circuits/ts/__tests__/ProcessMessages.test.ts +++ b/circuits/ts/__tests__/ProcessMessages.test.ts @@ -64,9 +64,6 @@ describe("ProcessMessage circuit", function () { BigInt(Math.floor(Date.now() / 1000)), ); - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); - pollId = maciState.deployPoll( duration, // BigInt(2 + duration), @@ -197,9 +194,6 @@ describe("ProcessMessage circuit", function () { BigInt(1), //BigInt(Math.floor(Date.now() / 1000)), ); - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); - pollId = maciState.deployPoll( duration, BigInt(2 + duration), //BigInt(Math.floor(Date.now() / 1000) + duration), @@ -288,9 +282,6 @@ describe("ProcessMessage circuit", function () { BigInt(1), //BigInt(Math.floor(Date.now() / 1000)), ); - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); - pollId = maciState.deployPoll( duration, BigInt(2 + duration), //BigInt(Math.floor(Date.now() / 1000) + duration), @@ -410,8 +401,6 @@ describe("ProcessMessage circuit", function () { BigInt(Math.floor(Date.now() / 1000)), ); - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); // Sign up and publish const pollId = maciState.deployPoll( duration, diff --git a/circuits/ts/__tests__/TallyVotes.test.ts b/circuits/ts/__tests__/TallyVotes.test.ts index aa55b3aae1..4e65743ff0 100644 --- a/circuits/ts/__tests__/TallyVotes.test.ts +++ b/circuits/ts/__tests__/TallyVotes.test.ts @@ -54,9 +54,6 @@ describe("TallyVotes circuit", function () { const userKeypair = new Keypair(); stateIndex = maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); - pollId = maciState.deployPoll( duration, BigInt(Math.floor(Date.now() / 1000) + duration), @@ -173,9 +170,6 @@ describe("TallyVotes circuit", function () { maciState.signUp(k.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000) + duration)); } - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( duration, BigInt(Math.floor(Date.now() / 1000) + duration), diff --git a/contracts/tests/MACI.test.ts b/contracts/tests/MACI.test.ts index 75cf24fb30..0cfa18a787 100644 --- a/contracts/tests/MACI.test.ts +++ b/contracts/tests/MACI.test.ts @@ -457,14 +457,11 @@ describe("MACI", () => { }); receipt = await tx.wait(); expect(receipt.status).to.eq(1); - - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); }); it("the state root must be correct", async () => { const onChainStateRoot = await stateAqContract.getMainRoot(STATE_TREE_DEPTH); - expect(onChainStateRoot.toString()).to.eq(maciState.stateAq.mainRoots[STATE_TREE_DEPTH].toString()); + expect(onChainStateRoot.toString()).to.eq(maciState.stateTree.root.toString()); }); }); diff --git a/contracts/ts/genMaciState.ts b/contracts/ts/genMaciState.ts index 93168ffd38..cc68eda8b8 100644 --- a/contracts/ts/genMaciState.ts +++ b/contracts/ts/genMaciState.ts @@ -449,12 +449,6 @@ const genMaciStateFromContract = async ( maciState.polls[pollId].publishMessage(action.data.message, action.data.encPubKey); } else if (action["type"] === "TopupMessage") { maciState.polls[pollId].topupMessage(action.data.message); - } else if (action["type"] === "MergeMaciStateAqSubRoots") { - maciState.stateAq.mergeSubRoots(action.data.numSrQueueOps); - } else if (action["type"] === "MergeMaciStateAq") { - if (pollId == 0) { - maciState.stateAq.merge(stateTreeDepth); - } } else if (action["type"] === "MergeMessageAqSubRoots") { maciState.polls[pollId].messageAq.mergeSubRoots(action.data.numSrQueueOps); } else if (action["type"] === "MergeMessageAq") { diff --git a/core/ts/MaciState.ts b/core/ts/MaciState.ts index 64092b6dc1..92c65e5c0c 100644 --- a/core/ts/MaciState.ts +++ b/core/ts/MaciState.ts @@ -1,9 +1,9 @@ -import { AccQueue, IncrementalQuinTree, hash5 } from "maci-crypto"; +import { IncrementalQuinTree, hash5 } from "maci-crypto"; import { PubKey, Keypair, StateLeaf } from "maci-domainobjs"; -// import order? import { Poll } from "./Poll"; import { TreeDepths, MaxValues } from "./utils/utils"; +import { STATE_TREE_ARITY } from "./utils/constants"; // todo: organize this in domainobjs const blankStateLeaf = StateLeaf.genBlankLeaf(); @@ -17,6 +17,7 @@ const blankStateLeafHash = blankStateLeaf.hash(); interface IMaciState { signUp(_pubKey: PubKey, _initialVoiceCreditBalance: bigint, _timestamp: bigint): number; + deployPoll( _duration: number, _pollEndTimestamp: bigint, @@ -34,35 +35,34 @@ interface IMaciState { // A representation of the MACI contract // Also see MACI.sol class MaciState implements IMaciState { - public STATE_TREE_ARITY = 5; - public STATE_TREE_SUBDEPTH = 2; - public MESSAGE_TREE_ARITY = 5; - public VOTE_OPTION_TREE_ARITY = 5; - - public stateTreeDepth: number; public polls: Poll[] = []; - public stateLeaves: StateLeaf[] = []; + public stateTree: IncrementalQuinTree; - public stateAq: AccQueue = new AccQueue(this.STATE_TREE_SUBDEPTH, this.STATE_TREE_ARITY, blankStateLeafHash); - public pollBeingProcessed = true; - public currentPollBeingProcessed; + public stateLeaves: StateLeaf[] = []; + public numSignUps = 0; + public stateTreeDepth: number; + + public pollBeingProcessed: boolean; + public currentPollBeingProcessed: number; + constructor(_stateTreeDepth: number) { this.stateTreeDepth = _stateTreeDepth; - this.stateTree = new IncrementalQuinTree(this.stateTreeDepth, blankStateLeafHash, this.STATE_TREE_ARITY, hash5); + this.stateTree = new IncrementalQuinTree(this.stateTreeDepth, blankStateLeafHash, STATE_TREE_ARITY, hash5); + this.stateLeaves.push(blankStateLeaf); this.stateTree.insert(blankStateLeafHash); - this.stateAq.enqueue(blankStateLeafHash); } public signUp(_pubKey: PubKey, _initialVoiceCreditBalance: bigint, _timestamp: bigint): number { + this.numSignUps++; const stateLeaf = new StateLeaf(_pubKey, _initialVoiceCreditBalance, _timestamp); const h = stateLeaf.hash(); - const leafIndex = this.stateAq.enqueue(h); + this.stateTree.insert(h); - this.stateLeaves.push(stateLeaf.copy()); - this.numSignUps++; + const arrayLength = this.stateLeaves.push(stateLeaf.copy()); + const leafIndex = arrayLength - 1; return leafIndex; } @@ -74,6 +74,7 @@ class MaciState implements IMaciState { _messageBatchSize: number, _coordinatorKeypair: Keypair, ): number { + // TODO: fix the order of the arguments const poll: Poll = new Poll( _duration, _pollEndTimestamp, @@ -81,8 +82,8 @@ class MaciState implements IMaciState { _treeDepths, { messageBatchSize: _messageBatchSize, - subsidyBatchSize: this.STATE_TREE_ARITY ** _treeDepths.intStateTreeDepth, - tallyBatchSize: this.STATE_TREE_ARITY ** _treeDepths.intStateTreeDepth, + subsidyBatchSize: STATE_TREE_ARITY ** _treeDepths.intStateTreeDepth, + tallyBatchSize: STATE_TREE_ARITY ** _treeDepths.intStateTreeDepth, }, _maxValues, this, @@ -111,9 +112,6 @@ class MaciState implements IMaciState { public equals = (m: MaciState): boolean => { const result = - this.STATE_TREE_ARITY === m.STATE_TREE_ARITY && - this.MESSAGE_TREE_ARITY === m.MESSAGE_TREE_ARITY && - this.VOTE_OPTION_TREE_ARITY === m.VOTE_OPTION_TREE_ARITY && this.stateTreeDepth === m.stateTreeDepth && this.polls.length === m.polls.length && this.stateLeaves.length === m.stateLeaves.length; @@ -140,10 +138,6 @@ class MaciState implements IMaciState { */ toJSON() { return { - STATE_TREE_ARITY: this.STATE_TREE_ARITY, - STATE_TREE_SUBDEPTH: this.STATE_TREE_SUBDEPTH, - MESSAGE_TREE_ARITY: this.MESSAGE_TREE_ARITY, - VOTE_OPTION_TREE_ARITY: this.VOTE_OPTION_TREE_ARITY, stateTreeDepth: this.stateTreeDepth, polls: this.polls.map((poll) => poll.toJSON()), stateLeaves: this.stateLeaves.map((leaf) => leaf.toJSON()), @@ -163,10 +157,6 @@ class MaciState implements IMaciState { maciState.pollBeingProcessed = json.pollBeingProcessed; maciState.currentPollBeingProcessed = json.currentPollBeingProcessed; maciState.numSignUps = json.numSignUps; - maciState.STATE_TREE_ARITY = json.STATE_TREE_ARITY; - maciState.STATE_TREE_SUBDEPTH = json.STATE_TREE_SUBDEPTH; - maciState.MESSAGE_TREE_ARITY = json.MESSAGE_TREE_ARITY; - maciState.VOTE_OPTION_TREE_ARITY = json.VOTE_OPTION_TREE_ARITY; // re create the state tree (start from index 1 as in the constructor we already add the blank leaf) for (let i = 1; i < json.stateLeaves.length; i++) { diff --git a/core/ts/__tests__/MaciState.test.ts b/core/ts/__tests__/MaciState.test.ts index e543fb2301..df4d71a6ef 100644 --- a/core/ts/__tests__/MaciState.test.ts +++ b/core/ts/__tests__/MaciState.test.ts @@ -1,9 +1,18 @@ -import { MaciState, packProcessMessageSmallVals, unpackProcessMessageSmallVals } from "../"; +import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs"; + import { expect } from "chai"; + import { PCommand, Message, Keypair, VerifyingKey, StateLeaf } from "maci-domainobjs"; +import { hash5, G1Point, G2Point, NOTHING_UP_MY_SLEEVE, IncrementalQuinTree, AccQueue } from "maci-crypto"; -import { hash5, G1Point, G2Point, NOTHING_UP_MY_SLEEVE, IncrementalQuinTree } from "maci-crypto"; -import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs"; +import { + STATE_TREE_ARITY, + STATE_TREE_SUBDEPTH, + BlankStateLeafHash, + MaciState, + packProcessMessageSmallVals, + unpackProcessMessageSmallVals, +} from "../"; const voiceCreditBalance = BigInt(100); @@ -43,15 +52,11 @@ const testTallyVk = new VerifyingKey( const coordinatorKeypair = new Keypair(); -const blankStateLeaf = StateLeaf.genBlankLeaf(); -const blankStateLeafHash = blankStateLeaf.hash(); - describe("MaciState", function () { this.timeout(100000); describe("Process and tally 1 message from 1 user", () => { let maciState: MaciState; let pollId; - let stateTree; let msgTree; const voteWeight = BigInt(9); const voteOptionIndex = BigInt(0); @@ -60,31 +65,25 @@ describe("MaciState", function () { before(() => { maciState = new MaciState(STATE_TREE_DEPTH); - stateTree = new IncrementalQuinTree(STATE_TREE_DEPTH, blankStateLeafHash, 5, hash5); - - stateTree.insert(blankStateLeafHash); - msgTree = new IncrementalQuinTree(treeDepths.messageTreeDepth, NOTHING_UP_MY_SLEEVE, 5, hash5); }); // The end result should be that option 0 gets 3 votes // because the user spends 9 voice credits on it it("the state root should be correct", () => { + const accumulatorQueue: AccQueue = new AccQueue(STATE_TREE_SUBDEPTH, STATE_TREE_ARITY, BlankStateLeafHash); const timestamp = BigInt(Math.floor(Date.now() / 1000)); - const stateLeaf = new StateLeaf(userKeypair.pubKey, voiceCreditBalance, timestamp); - stateTree.insert(stateLeaf.hash()); + accumulatorQueue.enqueue(BlankStateLeafHash); + accumulatorQueue.enqueue(stateLeaf.hash()); + accumulatorQueue.mergeSubRoots(0); + accumulatorQueue.merge(STATE_TREE_DEPTH); stateIndex = maciState.signUp(userKeypair.pubKey, voiceCreditBalance, timestamp); - expect(stateIndex.toString()).to.eq("1"); - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); - console.log(`root=${stateTree.root.toString()}`); - - expect(maciState.stateAq.getRoot(STATE_TREE_DEPTH).toString()).to.eq(stateTree.root.toString()); + expect(accumulatorQueue.getRoot(STATE_TREE_DEPTH).toString()).to.eq(maciState.stateTree.root.toString()); }); it("the message root should be correct", () => { @@ -246,10 +245,6 @@ describe("MaciState", function () { maciState.polls[pollId].processMessages(); }).to.throw; - // Merge the state aq - maciState.stateAq.mergeSubRoots(0); - maciState.stateAq.merge(STATE_TREE_DEPTH); - expect(() => { maciState.polls[pollId].processMessages(); }).to.throw; diff --git a/core/ts/index.ts b/core/ts/index.ts index 456580f87f..97ab184032 100644 --- a/core/ts/index.ts +++ b/core/ts/index.ts @@ -13,3 +13,5 @@ export { unpackTallyVotesSmallVals, packSubsidySmallVals, } from "./utils/utils"; + +export { STATE_TREE_ARITY, STATE_TREE_SUBDEPTH, BlankStateLeaf, BlankStateLeafHash } from "./utils/constants"; diff --git a/core/ts/utils/constants.ts b/core/ts/utils/constants.ts new file mode 100644 index 0000000000..d651e07279 --- /dev/null +++ b/core/ts/utils/constants.ts @@ -0,0 +1,10 @@ +import { StateLeaf } from "maci-domainobjs"; + +const STATE_TREE_ARITY = 5; +const STATE_TREE_SUBDEPTH = 2; + +// todo: organize this in domainobjs +const BlankStateLeaf = StateLeaf.genBlankLeaf(); +const BlankStateLeafHash = BlankStateLeaf.hash(); + +export { STATE_TREE_ARITY, STATE_TREE_SUBDEPTH, BlankStateLeaf, BlankStateLeafHash }; From d3930f706963747903305cce1ee5a716ea28c31d Mon Sep 17 00:00:00 2001 From: Daehyun Paik Date: Tue, 5 Dec 2023 03:53:15 +0100 Subject: [PATCH 2/4] refactor(core): remove unnecessary messageAq from Poll --- circuits/ts/__tests__/ProcessMessages.test.ts | 71 +++++++++++++------ circuits/ts/__tests__/TallyVotes.test.ts | 31 ++++---- cli/ts/commands/genProofs.ts | 4 +- contracts/tests/MACI.test.ts | 22 +++--- contracts/ts/genMaciState.ts | 6 +- core/ts/MaciState.ts | 12 ++-- core/ts/Poll.ts | 54 +++----------- core/ts/__tests__/MaciState.test.ts | 69 +++++------------- core/ts/index.ts | 10 ++- core/ts/utils/constants.ts | 17 ++++- 10 files changed, 134 insertions(+), 162 deletions(-) diff --git a/circuits/ts/__tests__/ProcessMessages.test.ts b/circuits/ts/__tests__/ProcessMessages.test.ts index f9cf42e431..e2ff25f4f3 100644 --- a/circuits/ts/__tests__/ProcessMessages.test.ts +++ b/circuits/ts/__tests__/ProcessMessages.test.ts @@ -1,14 +1,14 @@ import fs from "fs"; +import path from "path"; -import { MaciState, packProcessMessageSmallVals } from "maci-core"; +import { expect } from "chai"; +import { MaciState, packProcessMessageSmallVals, STATE_TREE_ARITY } from "maci-core"; import { PrivKey, Keypair, PCommand, Message, Ballot } from "maci-domainobjs"; - -import { hash5, IncrementalQuinTree, stringifyBigInts, NOTHING_UP_MY_SLEEVE } from "maci-crypto"; +import { AccQueue, hash5, IncrementalQuinTree, stringifyBigInts, NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { STATE_TREE_DEPTH, getSignal } from "./utils"; -import path from "path"; -import { expect } from "chai"; + const tester = require("circom_tester").wasm; const voiceCreditBalance = BigInt(100); @@ -114,17 +114,27 @@ describe("ProcessMessage circuit", function () { commands.push(command2); poll.publishMessage(message2, ecdhKeypair2.pubKey); - poll.messageAq.mergeSubRoots(0); - poll.messageAq.merge(treeDepths.messageTreeDepth); + // Use the accumulator queue to compare the root of the message tree + const accumulatorQueue: AccQueue = new AccQueue( + treeDepths.messageTreeSubDepth, + STATE_TREE_ARITY, + NOTHING_UP_MY_SLEEVE, + ); + accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); + accumulatorQueue.enqueue(message2.hash(ecdhKeypair2.pubKey)); + accumulatorQueue.mergeSubRoots(0); + accumulatorQueue.merge(treeDepths.messageTreeDepth); - expect(poll.messageTree.root.toString()).to.be.eq(poll.messageAq.getRoot(treeDepths.messageTreeDepth).toString()); + expect(poll.messageTree.root.toString()).to.be.eq( + accumulatorQueue.mainRoots[treeDepths.messageTreeDepth].toString(), + ); }); it("should produce the correct state root and ballot root", async () => { // The current roots const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), poll.STATE_TREE_ARITY, hash5); + const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); ballotTree.insert(emptyBallot.hash()); @@ -224,18 +234,26 @@ describe("ProcessMessage circuit", function () { poll.publishMessage(message, ecdhKeypair.pubKey); - // Merge - poll.messageAq.mergeSubRoots(0); - poll.messageAq.merge(treeDepths.messageTreeDepth); + // Use the accumulator queue to compare the root of the message tree + const accumulatorQueue: AccQueue = new AccQueue( + treeDepths.messageTreeSubDepth, + STATE_TREE_ARITY, + NOTHING_UP_MY_SLEEVE, + ); + accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); + accumulatorQueue.mergeSubRoots(0); + accumulatorQueue.merge(treeDepths.messageTreeDepth); - expect(poll.messageTree.root.toString()).to.be.eq(poll.messageAq.getRoot(treeDepths.messageTreeDepth).toString()); + expect(poll.messageTree.root.toString()).to.be.eq( + accumulatorQueue.getRoot(treeDepths.messageTreeDepth).toString(), + ); }); it("should produce the correct state root and ballot root", async () => { // The current roots const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), poll.STATE_TREE_ARITY, hash5); + const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); ballotTree.insert(emptyBallot.hash()); @@ -353,18 +371,28 @@ describe("ProcessMessage circuit", function () { commands.push(command3); poll.publishMessage(message3, ecdhKeypair3.pubKey); - // Merge - poll.messageAq.mergeSubRoots(0); - poll.messageAq.merge(treeDepths.messageTreeDepth); - - expect(poll.messageTree.root.toString()).to.be.eq(poll.messageAq.getRoot(treeDepths.messageTreeDepth).toString()); + // Use the accumulator queue to compare the root of the message tree + const accumulatorQueue: AccQueue = new AccQueue( + treeDepths.messageTreeSubDepth, + STATE_TREE_ARITY, + NOTHING_UP_MY_SLEEVE, + ); + accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); + accumulatorQueue.enqueue(message2.hash(ecdhKeypair2.pubKey)); + accumulatorQueue.enqueue(message3.hash(ecdhKeypair3.pubKey)); + accumulatorQueue.mergeSubRoots(0); + accumulatorQueue.merge(treeDepths.messageTreeDepth); + + expect(poll.messageTree.root.toString()).to.be.eq( + accumulatorQueue.getRoot(treeDepths.messageTreeDepth).toString(), + ); }); it("should produce the correct state root and ballot root", async () => { // The current roots const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), poll.STATE_TREE_ARITY, hash5); + const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); ballotTree.insert(NOTHING_UP_MY_SLEEVE); @@ -433,9 +461,6 @@ describe("ProcessMessage circuit", function () { poll.publishMessage(message, ecdhKeypair.pubKey); } - poll.messageAq.mergeSubRoots(0); - poll.messageAq.merge(treeDepths.messageTreeDepth); - for (let i = 0; i < NUM_BATCHES; i++) { const generatedInputs = poll.processMessages(pollId); diff --git a/circuits/ts/__tests__/TallyVotes.test.ts b/circuits/ts/__tests__/TallyVotes.test.ts index 4e65743ff0..d321f9198a 100644 --- a/circuits/ts/__tests__/TallyVotes.test.ts +++ b/circuits/ts/__tests__/TallyVotes.test.ts @@ -1,12 +1,13 @@ -import { STATE_TREE_DEPTH } from "./utils"; - -import { MaciState } from "maci-core"; - -import { Keypair, PCommand, Message } from "maci-domainobjs"; - import path from "path"; import { expect } from "chai"; import { beforeEach } from "mocha"; + +import { Keypair, PCommand, Message } from "maci-domainobjs"; +import { MaciState, STATE_TREE_ARITY } from "maci-core"; +import { AccQueue, NOTHING_UP_MY_SLEEVE } from "maci-crypto"; + +import { STATE_TREE_DEPTH } from "./utils"; + const tester = require("circom_tester").wasm; const voiceCreditBalance = BigInt(100); @@ -85,10 +86,19 @@ describe("TallyVotes circuit", function () { poll.publishMessage(message, ecdhKeypair.pubKey); - poll.messageAq.mergeSubRoots(0); - poll.messageAq.merge(treeDepths.messageTreeDepth); + // Use the accumulator queue to compare the root of the message tree + const accumulatorQueue: AccQueue = new AccQueue( + treeDepths.messageTreeSubDepth, + STATE_TREE_ARITY, + NOTHING_UP_MY_SLEEVE, + ); + accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); + accumulatorQueue.mergeSubRoots(0); + accumulatorQueue.merge(treeDepths.messageTreeDepth); - expect(poll.messageTree.root.toString()).to.be.eq(poll.messageAq.getRoot(treeDepths.messageTreeDepth).toString()); + expect(poll.messageTree.root.toString()).to.be.eq( + accumulatorQueue.mainRoots[treeDepths.messageTreeDepth].toString(), + ); // Process messages poll.processMessages(); }); @@ -200,9 +210,6 @@ describe("TallyVotes circuit", function () { poll.publishMessage(message, ecdhKeypair.pubKey); } - poll.messageAq.mergeSubRoots(0); - poll.messageAq.merge(treeDepths.messageTreeDepth); - for (let i = 0; i < NUM_BATCHES; i++) { poll.processMessages(pollId); } diff --git a/cli/ts/commands/genProofs.ts b/cli/ts/commands/genProofs.ts index 4b7b2866eb..65ef4317d1 100644 --- a/cli/ts/commands/genProofs.ts +++ b/cli/ts/commands/genProofs.ts @@ -16,7 +16,7 @@ import { Keypair, PrivKey, VerifyingKey } from "maci-domainobjs"; import { extractVk, genProof, verifyProof } from "maci-circuits"; import { genMaciStateFromContract, getDefaultSigner, parseArtifact } from "maci-contracts"; import { Contract } from "ethers"; -import { MaciState, Poll } from "maci-core"; +import { MaciState } from "maci-core"; import { hash3, hashLeftRight, genTreeCommitment } from "maci-crypto"; import { join } from "path"; @@ -184,8 +184,6 @@ export const genProofs = async ( } catch (error: any) { logError(error.message); } - // ensure we merge all messages - maciState.polls.forEach((poll: Poll) => poll.mergeAllMessages()); } else { maciState = await genMaciStateFromContract( signer.provider, diff --git a/contracts/tests/MACI.test.ts b/contracts/tests/MACI.test.ts index 0cfa18a787..6b6a7e6cb5 100644 --- a/contracts/tests/MACI.test.ts +++ b/contracts/tests/MACI.test.ts @@ -1,9 +1,7 @@ import { utils, Contract } from "ethers"; -import { timeTravel } from "./utils"; -import { parseArtifact, getDefaultSigner } from "../ts/deploy"; -import { deployTestContracts } from "../ts/utils"; -import { PCommand, VerifyingKey, Keypair, PubKey, Message } from "maci-domainobjs"; +import { expect } from "chai"; +import { PCommand, VerifyingKey, Keypair, PubKey, Message } from "maci-domainobjs"; import { MaciState, genProcessVkSig, @@ -12,10 +10,12 @@ import { packProcessMessageSmallVals, packTallyVotesSmallVals, } from "maci-core"; -import { expect } from "chai"; - import { G1Point, G2Point, NOTHING_UP_MY_SLEEVE } from "maci-crypto"; +import { timeTravel } from "./utils"; +import { parseArtifact, getDefaultSigner } from "../ts/deploy"; +import { deployTestContracts } from "../ts/utils"; + const STATE_TREE_DEPTH = 10; const STATE_TREE_ARITY = 5; const MESSAGE_TREE_DEPTH = 4; @@ -404,17 +404,13 @@ describe("MACI", () => { tx = await pollContract.mergeMessageAq({ gasLimit: 4000000 }); receipt = await tx.wait(); expect(receipt.status).to.eq(1); - - const poll = maciState.polls[pollId]; - poll.messageAq.mergeSubRoots(0); - poll.messageAq.merge(MESSAGE_TREE_DEPTH); }); it("the message root must be correct", async () => { const onChainMessageRoot = await messageAqContract.getMainRoot(MESSAGE_TREE_DEPTH); - expect(onChainMessageRoot.toString()).to.eq( - maciState.polls[pollId].messageAq.mainRoots[MESSAGE_TREE_DEPTH].toString(), - ); + const offChainMessageRoot = maciState.polls[pollId].messageTree.root; + + expect(onChainMessageRoot.toString()).to.eq(offChainMessageRoot.toString()); }); }); diff --git a/contracts/ts/genMaciState.ts b/contracts/ts/genMaciState.ts index cc68eda8b8..3d431bcad0 100644 --- a/contracts/ts/genMaciState.ts +++ b/contracts/ts/genMaciState.ts @@ -449,12 +449,8 @@ const genMaciStateFromContract = async ( maciState.polls[pollId].publishMessage(action.data.message, action.data.encPubKey); } else if (action["type"] === "TopupMessage") { maciState.polls[pollId].topupMessage(action.data.message); - } else if (action["type"] === "MergeMessageAqSubRoots") { - maciState.polls[pollId].messageAq.mergeSubRoots(action.data.numSrQueueOps); } else if (action["type"] === "MergeMessageAq") { - maciState.polls[pollId].messageAq.merge(treeDepths.messageTreeDepth); - const poll = maciState.polls[pollId]; - assert(poll.messageAq.mainRoots[treeDepths.messageTreeDepth] === action.data.messageRoot); + assert(maciState.polls[pollId].messageTree.root === action.data.messageRoot); } } diff --git a/core/ts/MaciState.ts b/core/ts/MaciState.ts index 92c65e5c0c..6aec2579d7 100644 --- a/core/ts/MaciState.ts +++ b/core/ts/MaciState.ts @@ -3,11 +3,7 @@ import { PubKey, Keypair, StateLeaf } from "maci-domainobjs"; import { Poll } from "./Poll"; import { TreeDepths, MaxValues } from "./utils/utils"; -import { STATE_TREE_ARITY } from "./utils/constants"; - -// todo: organize this in domainobjs -const blankStateLeaf = StateLeaf.genBlankLeaf(); -const blankStateLeafHash = blankStateLeaf.hash(); +import { STATE_TREE_ARITY, BlankStateLeaf, BlankStateLeafHash } from "./utils/constants"; /* * File Overview: @@ -49,10 +45,10 @@ class MaciState implements IMaciState { constructor(_stateTreeDepth: number) { this.stateTreeDepth = _stateTreeDepth; - this.stateTree = new IncrementalQuinTree(this.stateTreeDepth, blankStateLeafHash, STATE_TREE_ARITY, hash5); + this.stateTree = new IncrementalQuinTree(this.stateTreeDepth, BlankStateLeafHash, STATE_TREE_ARITY, hash5); - this.stateLeaves.push(blankStateLeaf); - this.stateTree.insert(blankStateLeafHash); + this.stateLeaves.push(BlankStateLeaf); + this.stateTree.insert(BlankStateLeafHash); } public signUp(_pubKey: PubKey, _initialVoiceCreditBalance: bigint, _timestamp: bigint): number { diff --git a/core/ts/Poll.ts b/core/ts/Poll.ts index ade91f0d89..5184e16dd7 100644 --- a/core/ts/Poll.ts +++ b/core/ts/Poll.ts @@ -1,6 +1,5 @@ import assert from "assert"; import { - AccQueue, IncrementalQuinTree, genRandomSalt, SNARK_FIELD_SIZE, @@ -15,8 +14,8 @@ import { import { PubKey, Command, PCommand, TCommand, Message, Keypair, StateLeaf, Ballot, PrivKey } from "maci-domainobjs"; import { MaciState } from "./MaciState"; -import { TreeDepths, MaxValues, BatchSizes } from "./utils/utils"; -import { packTallyVotesSmallVals, packSubsidySmallVals } from "./utils/utils"; +import { TreeDepths, MaxValues, BatchSizes, packTallyVotesSmallVals, packSubsidySmallVals } from "./utils/utils"; +import { STATE_TREE_ARITY, MESSAGE_TREE_ARITY, VOTE_OPTION_TREE_ARITY } from "./utils/constants"; // todo: organize this in domainobjs const blankStateLeaf = StateLeaf.genBlankLeaf(); @@ -31,9 +30,8 @@ const blankStateLeafHash = blankStateLeaf.hash(); interface IPoll { topupMessage(_message: Message): void; publishMessage(_message: Message, _encPubKey: PubKey): void; - mergeAllMessages(): void; hasUnprocessedMessages(): boolean; - processMessages(_pollId: number): unknown; + processMessages(_pollId: number): any; genProcessMessagesCircuitInputsPartial(_index: number): any; processAllMessages(): { stateLeaves: StateLeaf[]; ballots: Ballot[] }; hasUntalliedBallots(): boolean; @@ -41,7 +39,7 @@ interface IPoll { subsidyPerBatch(): bigint[]; increaseSubsidyIndex(): void; previousSubsidyIndexToString(): string; - tallyVotes(): void; + tallyVotes(): any; coefficientCalculation(rowBallot: Ballot, colBallot: Ballot): bigint; subsidyCalculation(rowStartIndex: number, colStartIndex: number): Ballot[][]; genResultsCommitment(_salt: bigint): bigint; @@ -74,14 +72,10 @@ class Poll implements IPoll { public ballotTree: IncrementalQuinTree; public messages: Message[] = []; - public messageAq: AccQueue; public messageTree: IncrementalQuinTree; public commands: Command[] = []; public encPubKeys: PubKey[] = []; - public STATE_TREE_ARITY = 5; - public MESSAGE_TREE_ARITY = 5; - public VOTE_OPTION_TREE_ARITY = 5; public stateCopied = false; public stateLeaves: StateLeaf[] = [blankStateLeaf]; @@ -134,14 +128,13 @@ class Poll implements IPoll { this.numSignUps = Number(_maciStateRef.numSignUps.toString()); this.stateTreeDepth = _stateTreeDepth; - this.stateTree = new IncrementalQuinTree(this.stateTreeDepth, blankStateLeafHash, this.STATE_TREE_ARITY, hash5); + this.stateTree = new IncrementalQuinTree(this.stateTreeDepth, blankStateLeafHash, STATE_TREE_ARITY, hash5); this.messageTree = new IncrementalQuinTree( this.treeDepths.messageTreeDepth, NOTHING_UP_MY_SLEEVE, - this.MESSAGE_TREE_ARITY, + MESSAGE_TREE_ARITY, hash5, ); - this.messageAq = new AccQueue(this.treeDepths.messageTreeSubDepth, this.MESSAGE_TREE_ARITY, NOTHING_UP_MY_SLEEVE); for (let i = 0; i < this.maxValues.maxVoteOptions; i++) { this.results.push(BigInt(0)); @@ -163,7 +156,7 @@ class Poll implements IPoll { // Create as many ballots as state leaves const emptyBallot = new Ballot(this.maxValues.maxVoteOptions, this.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); - this.ballotTree = new IncrementalQuinTree(this.stateTreeDepth, emptyBallot.hash(), this.STATE_TREE_ARITY, hash5); + this.ballotTree = new IncrementalQuinTree(this.stateTreeDepth, emptyBallot.hash(), STATE_TREE_ARITY, hash5); this.ballotTree.insert(emptyBallotHash); while (this.ballots.length < this.stateLeaves.length) { @@ -302,10 +295,6 @@ class Poll implements IPoll { } }; - private isMessageAqMerged = (): boolean => { - return this.messageAq.getRoot(this.treeDepths.messageTreeDepth) === this.messageTree.root; - }; - // Insert topup message into commands public topupMessage = (_message: Message) => { assert(_message.msgType == BigInt(2)); @@ -320,7 +309,6 @@ class Poll implements IPoll { this.encPubKeys.push(padKey); const messageLeaf = _message.hash(padKey); - this.messageAq.enqueue(messageLeaf); this.messageTree.insert(messageLeaf); const command = new TCommand(_message.data[0], _message.data[1], BigInt(this.pollId)); @@ -344,7 +332,6 @@ class Poll implements IPoll { this.messages.push(_message); const messageLeaf = _message.hash(_encPubKey); - this.messageAq.enqueue(messageLeaf); this.messageTree.insert(messageLeaf); // Decrypt the message and store the Command @@ -360,18 +347,6 @@ class Poll implements IPoll { } }; - /* - * Merge all enqueued messages into a tree. - */ - public mergeAllMessages = () => { - this.messageAq.mergeSubRoots(0); - this.messageAq.merge(this.treeDepths.messageTreeDepth); - assert(this.isMessageAqMerged()); - - // TODO: Validate that a tree from this.messages matches the messageAq - // main root - }; - public hasUnprocessedMessages = (): boolean => { const batchSize = this.batchSizes.messageBatchSize; @@ -399,10 +374,6 @@ class Poll implements IPoll { public processMessages = (_pollId: number): any => { assert(this.hasUnprocessedMessages(), "No more messages to process"); - // Require that the message queue has been merged - assert(this.isMessageAqMerged()); - assert(this.messageAq.hasRoot(this.treeDepths.messageTreeDepth)); - const batchSize = this.batchSizes.messageBatchSize; if (this.numBatchesProcessed === 0) { @@ -656,8 +627,7 @@ class Poll implements IPoll { } encPubKeys = encPubKeys.slice(_index, _index + messageBatchSize); - const msgRoot = this.messageAq.getRoot(this.treeDepths.messageTreeDepth); - + const msgRoot = this.messageTree.root; const currentStateRoot = this.stateTree.root; const currentBallotRoot = this.ballotTree.root; const currentSbCommitment = hash3([ @@ -859,7 +829,7 @@ class Poll implements IPoll { /** * Tally a batch of Ballots and update this.results */ - public tallyVotes = () => { + public tallyVotes = (): any => { const batchSize = this.batchSizes.tallyBatchSize; assert(this.hasUntalliedBallots(), "No more ballots to tally"); @@ -1004,7 +974,7 @@ class Poll implements IPoll { const resultsTree = new IncrementalQuinTree( this.treeDepths.voteOptionTreeDepth, BigInt(0), - this.VOTE_OPTION_TREE_ARITY, + VOTE_OPTION_TREE_ARITY, hash5, ); @@ -1033,7 +1003,7 @@ class Poll implements IPoll { const resultsTree = new IncrementalQuinTree( this.treeDepths.voteOptionTreeDepth, BigInt(0), - this.VOTE_OPTION_TREE_ARITY, + VOTE_OPTION_TREE_ARITY, hash5, ); @@ -1094,7 +1064,6 @@ class Poll implements IPoll { } copied.currentMessageBatchIndex = this.currentMessageBatchIndex; copied.maciStateRef = this.maciStateRef; - copied.messageAq = this.messageAq.copy(); copied.messageTree = this.messageTree.copy(); copied.results = this.results.map((x: bigint) => BigInt(x.toString())); copied.perVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x: bigint) => BigInt(x.toString())); @@ -1229,7 +1198,6 @@ class Poll implements IPoll { // fill the trees for (let i = 0; i < poll.messages.length; i++) { const messageLeaf = poll.messages[i].hash(poll.encPubKeys[i]); - poll.messageAq.enqueue(messageLeaf); poll.messageTree.insert(messageLeaf); } diff --git a/core/ts/__tests__/MaciState.test.ts b/core/ts/__tests__/MaciState.test.ts index df4d71a6ef..5a807a3139 100644 --- a/core/ts/__tests__/MaciState.test.ts +++ b/core/ts/__tests__/MaciState.test.ts @@ -2,8 +2,8 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs"; import { expect } from "chai"; -import { PCommand, Message, Keypair, VerifyingKey, StateLeaf } from "maci-domainobjs"; -import { hash5, G1Point, G2Point, NOTHING_UP_MY_SLEEVE, IncrementalQuinTree, AccQueue } from "maci-crypto"; +import { PCommand, Message, Keypair, StateLeaf } from "maci-domainobjs"; +import { hash5, NOTHING_UP_MY_SLEEVE, IncrementalQuinTree, AccQueue } from "maci-crypto"; import { STATE_TREE_ARITY, @@ -34,22 +34,6 @@ const messageBatchSize = 25; const STATE_TREE_DEPTH = 10; -const testProcessVk = new VerifyingKey( - new G1Point(BigInt(0), BigInt(1)), - new G2Point([BigInt(0), BigInt(0)], [BigInt(1), BigInt(1)]), - new G2Point([BigInt(3), BigInt(0)], [BigInt(1), BigInt(1)]), - new G2Point([BigInt(4), BigInt(0)], [BigInt(1), BigInt(1)]), - [new G1Point(BigInt(5), BigInt(1)), new G1Point(BigInt(6), BigInt(1))], -); - -const testTallyVk = new VerifyingKey( - new G1Point(BigInt(2), BigInt(3)), - new G2Point([BigInt(3), BigInt(0)], [BigInt(3), BigInt(1)]), - new G2Point([BigInt(4), BigInt(0)], [BigInt(3), BigInt(1)]), - new G2Point([BigInt(5), BigInt(0)], [BigInt(4), BigInt(1)]), - [new G1Point(BigInt(6), BigInt(1)), new G1Point(BigInt(7), BigInt(1))], -); - const coordinatorKeypair = new Keypair(); describe("MaciState", function () { @@ -114,12 +98,17 @@ describe("MaciState", function () { maciState.polls[pollId].publishMessage(message, ecdhKeypair.pubKey); msgTree.insert(message.hash(ecdhKeypair.pubKey)); - maciState.polls[pollId].messageAq.mergeSubRoots(0); - maciState.polls[pollId].messageAq.merge(treeDepths.messageTreeDepth); - - expect(maciState.polls[pollId].messageAq.getRoot(treeDepths.messageTreeDepth).toString()).to.eq( - msgTree.root.toString(), + // Use the accumulator queue to compare the root of the message tree + const accumulatorQueue: AccQueue = new AccQueue( + treeDepths.messageTreeSubDepth, + STATE_TREE_ARITY, + NOTHING_UP_MY_SLEEVE, ); + accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); + accumulatorQueue.mergeSubRoots(0); + accumulatorQueue.merge(treeDepths.messageTreeDepth); + + expect(accumulatorQueue.getRoot(treeDepths.messageTreeDepth).toString()).to.eq(msgTree.root.toString()); }); it("packProcessMessageSmallVals and unpackProcessMessageSmallVals", () => { @@ -167,7 +156,7 @@ describe("MaciState", function () { }); describe(`Process and tally ${messageBatchSize * 2} messages from ${messageBatchSize} users`, () => { - let maciState; + let maciState: MaciState; let pollId; const voteWeight = BigInt(9); @@ -190,8 +179,6 @@ describe("MaciState", function () { treeDepths, messageBatchSize, coordinatorKeypair, - testProcessVk, - testTallyVk, ); }); @@ -217,7 +204,7 @@ describe("MaciState", function () { maciState.polls[pollId].publishMessage(message, ecdhKeypair.pubKey); } - expect(maciState.polls[pollId].messageAq.numLeaves).to.eq(messageBatchSize - 1); + expect(maciState.polls[pollId].messages.length).to.eq(messageBatchSize - 1); // 24 invalid votes for (let i = 0; i < messageBatchSize - 1; i++) { @@ -239,27 +226,14 @@ describe("MaciState", function () { maciState.polls[pollId].publishMessage(message, ecdhKeypair.pubKey); } - // processMessages() should fail if the state and message AQs are - // not merged yet - expect(() => { - maciState.polls[pollId].processMessages(); - }).to.throw; - - expect(() => { - maciState.polls[pollId].processMessages(); - }).to.throw; - - // Merge the message aq - maciState.polls[pollId].messageAq.mergeSubRoots(0); - maciState.polls[pollId].messageAq.merge(treeDepths.messageTreeDepth); - - expect(maciState.polls[pollId].messageAq.numLeaves).to.eq(messageBatchSize * 2); + // 48 messages in total + expect(maciState.polls[pollId].messages.length).to.eq(48); expect(maciState.polls[pollId].currentMessageBatchIndex).to.eq(undefined); expect(maciState.polls[pollId].numBatchesProcessed).to.eq(0); // Process messages - maciState.polls[pollId].processMessages(); + maciState.polls[pollId].processMessages(pollId); // currentMessageBatchIndex is 0 because the current batch starts // with index 0. @@ -267,18 +241,11 @@ describe("MaciState", function () { expect(maciState.polls[pollId].numBatchesProcessed).to.eq(1); // Process messages - maciState.polls[pollId].processMessages(); + maciState.polls[pollId].processMessages(pollId); expect(maciState.polls[pollId].currentMessageBatchIndex).to.eq(0); expect(maciState.polls[pollId].numBatchesProcessed).to.eq(2); - // Attempt to process messages, but this should fail as there are - // no more messages to process - // TODO: use VError to test for specific errors - expect(() => { - maciState.polls[pollId].processMessages(); - }).to.throw; - for (let i = 1; i < messageBatchSize; i++) { const leaf = maciState.polls[pollId].ballots[i].votes[i - 1]; expect(leaf.toString()).to.eq(voteWeight.toString()); diff --git a/core/ts/index.ts b/core/ts/index.ts index 97ab184032..9c238546ce 100644 --- a/core/ts/index.ts +++ b/core/ts/index.ts @@ -14,4 +14,12 @@ export { packSubsidySmallVals, } from "./utils/utils"; -export { STATE_TREE_ARITY, STATE_TREE_SUBDEPTH, BlankStateLeaf, BlankStateLeafHash } from "./utils/constants"; +export { + STATE_TREE_DEPTH, + STATE_TREE_ARITY, + STATE_TREE_SUBDEPTH, + MESSAGE_TREE_ARITY, + VOTE_OPTION_TREE_ARITY, + BlankStateLeaf, + BlankStateLeafHash, +} from "./utils/constants"; diff --git a/core/ts/utils/constants.ts b/core/ts/utils/constants.ts index d651e07279..e8bc0d5984 100644 --- a/core/ts/utils/constants.ts +++ b/core/ts/utils/constants.ts @@ -1,10 +1,21 @@ import { StateLeaf } from "maci-domainobjs"; +const STATE_TREE_DEPTH = 10; const STATE_TREE_ARITY = 5; const STATE_TREE_SUBDEPTH = 2; +const MESSAGE_TREE_ARITY = 5; +const VOTE_OPTION_TREE_ARITY = 5; // todo: organize this in domainobjs -const BlankStateLeaf = StateLeaf.genBlankLeaf(); -const BlankStateLeafHash = BlankStateLeaf.hash(); +const BlankStateLeaf: StateLeaf = StateLeaf.genBlankLeaf(); +const BlankStateLeafHash: bigint = BlankStateLeaf.hash(); -export { STATE_TREE_ARITY, STATE_TREE_SUBDEPTH, BlankStateLeaf, BlankStateLeafHash }; +export { + STATE_TREE_DEPTH, + STATE_TREE_ARITY, + STATE_TREE_SUBDEPTH, + MESSAGE_TREE_ARITY, + VOTE_OPTION_TREE_ARITY, + BlankStateLeaf, + BlankStateLeafHash, +}; From 11ba386a37cc802c4fd6a3c243e493de8e04a339 Mon Sep 17 00:00:00 2001 From: Daehyun Paik Date: Tue, 5 Dec 2023 06:07:27 +0100 Subject: [PATCH 3/4] refactor(core): update README.md --- core/README.md | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/core/README.md b/core/README.md index afd4faf998..6104d1c9e3 100644 --- a/core/README.md +++ b/core/README.md @@ -52,14 +52,9 @@ A function that deep-copies an object. ### Key data structures -#### **`stateAq`**, **`stateTree`** +#### **`stateTree`** -The Merkle tree of state leaves. `stateAq` must be merged (subroots and/or main -root) whenever the MACI contract's `mergeStateAqSubRoots()` and -`mergeStateAq()` are invoked. - -They should contain the same leaves, even if the `stateAq` is not yet merged. -`stateTree` exists for developer convenience. +`stateTree` is a quinary Merkle tree, chosen over a binary tree due to the gas and circuit constraints associated with the Poseidon hash function ([details here][ethresearch-link]). Each leaf in this tree represents a participant's public key, their voice credit balance and the block timestamp at which they signed up. The tree features a configurable depth and an arity of five, with insertions starting at index 1. The zeroth leaf is reserved as a security measure against denial-of-service attacks. ## **`Poll`** @@ -91,14 +86,9 @@ Deep-copies and returns this object. ### Key data structures -#### **`messageAq`**, **`messageTree`** - -The Merkle tree of message leaves. `messageAq` must be merged (subroots and/or -main root) whenever the MACI contract's `mergeMessageAqSubRoots()` and -`mergeMessageAq()` are invoked. +#### **`messageTree`** -They should contain the same leaves, even if the `messageAq` is not yet merged. -`messageTree` exists for developer convenience. +`messageTree` is also a quinary Merkle tree. Each message in this tree represents an encrypted command, such as a user casting a vote in a poll or changing their public key. ## Testing @@ -108,6 +98,7 @@ For more details about testing please refer to the [tests documentation](https:/ [core-npm-link]: https://www.npmjs.com/package/maci-core [core-actions-badge]: https://github.com/privacy-scaling-explorations/maci/actions/workflows/core-build.yml/badge.svg [core-actions-link]: https://github.com/privacy-scaling-explorations/maci/actions?query=workflow%3Acore +[ethresearch-link]: https://ethresear.ch/t/gas-and-circuit-constraint-benchmarks-of-binary-and-quinary-incremental-merkle-trees-using-the-poseidon-hash-function/7446 ## Directory Structure From 0588ef649420b850c9fe6f115dc5431016b741fb Mon Sep 17 00:00:00 2001 From: Daehyun Paik Date: Tue, 5 Dec 2023 14:52:38 +0100 Subject: [PATCH 4/4] chore(core): replace repeated chaining call --- core/ts/__tests__/MaciState.test.ts | 85 +++++++++++++++-------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/core/ts/__tests__/MaciState.test.ts b/core/ts/__tests__/MaciState.test.ts index 5a807a3139..414c992672 100644 --- a/core/ts/__tests__/MaciState.test.ts +++ b/core/ts/__tests__/MaciState.test.ts @@ -6,12 +6,14 @@ import { PCommand, Message, Keypair, StateLeaf } from "maci-domainobjs"; import { hash5, NOTHING_UP_MY_SLEEVE, IncrementalQuinTree, AccQueue } from "maci-crypto"; import { + STATE_TREE_DEPTH, STATE_TREE_ARITY, STATE_TREE_SUBDEPTH, BlankStateLeafHash, MaciState, packProcessMessageSmallVals, unpackProcessMessageSmallVals, + Poll, } from "../"; const voiceCreditBalance = BigInt(100); @@ -32,19 +34,18 @@ const treeDepths = { const messageBatchSize = 25; -const STATE_TREE_DEPTH = 10; - const coordinatorKeypair = new Keypair(); describe("MaciState", function () { this.timeout(100000); describe("Process and tally 1 message from 1 user", () => { let maciState: MaciState; - let pollId; - let msgTree; + let pollId: number; + let poll: Poll; + let msgTree: IncrementalQuinTree; const voteWeight = BigInt(9); const voteOptionIndex = BigInt(0); - let stateIndex; + let stateIndex: number; const userKeypair = new Keypair(); before(() => { @@ -80,8 +81,10 @@ describe("MaciState", function () { coordinatorKeypair, ); + poll = maciState.polls[pollId]; + const command = new PCommand( - stateIndex, + BigInt(stateIndex), userKeypair.pubKey, voteOptionIndex, voteWeight, @@ -95,7 +98,7 @@ describe("MaciState", function () { const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); const message = command.encrypt(signature, sharedKey); - maciState.polls[pollId].publishMessage(message, ecdhKeypair.pubKey); + poll.publishMessage(message, ecdhKeypair.pubKey); msgTree.insert(message.hash(ecdhKeypair.pubKey)); // Use the accumulator queue to compare the root of the message tree @@ -126,29 +129,29 @@ describe("MaciState", function () { }); it("Process a batch of messages (though only 1 message is in the batch)", () => { - maciState.polls[pollId].processMessages(pollId); + poll.processMessages(pollId); // Check the ballot - expect(maciState.polls[pollId].ballots[1].votes[Number(voteOptionIndex)].toString()).to.eq(voteWeight.toString()); + expect(poll.ballots[1].votes[Number(voteOptionIndex)].toString()).to.eq(voteWeight.toString()); // Check the state leaf in the poll - expect(maciState.polls[pollId].stateLeaves[1].voiceCreditBalance.toString()).to.eq( + expect(poll.stateLeaves[1].voiceCreditBalance.toString()).to.eq( (voiceCreditBalance - voteWeight * voteWeight).toString(), ); }); it("Tally ballots", () => { let total = BigInt(0); - for (const v of maciState.polls[pollId].results) { + for (const v of poll.results) { total = BigInt(Number(total) + Number(v)); } expect(total.toString()).to.eq("0"); - expect(maciState.polls[pollId].hasUntalliedBallots()).to.be.true; + expect(poll.hasUntalliedBallots()).to.be.true; - maciState.polls[pollId].tallyVotes(); + poll.tallyVotes(); total = BigInt(0); - for (const v of maciState.polls[pollId].results) { + for (const v of poll.results) { total = BigInt(Number(total) + Number(v)); } expect(total.toString()).to.eq(voteWeight.toString()); @@ -157,7 +160,8 @@ describe("MaciState", function () { describe(`Process and tally ${messageBatchSize * 2} messages from ${messageBatchSize} users`, () => { let maciState: MaciState; - let pollId; + let pollId: number; + let poll: Poll; const voteWeight = BigInt(9); const users: Keypair[] = []; @@ -180,6 +184,7 @@ describe("MaciState", function () { messageBatchSize, coordinatorKeypair, ); + poll = maciState.polls[pollId]; }); it("should process votes correctly", () => { @@ -201,10 +206,10 @@ describe("MaciState", function () { const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); const message = command.encrypt(signature, sharedKey); - maciState.polls[pollId].publishMessage(message, ecdhKeypair.pubKey); + poll.publishMessage(message, ecdhKeypair.pubKey); } - expect(maciState.polls[pollId].messages.length).to.eq(messageBatchSize - 1); + expect(poll.messages.length).to.eq(messageBatchSize - 1); // 24 invalid votes for (let i = 0; i < messageBatchSize - 1; i++) { @@ -223,74 +228,74 @@ describe("MaciState", function () { const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); const message = command.encrypt(signature, sharedKey); - maciState.polls[pollId].publishMessage(message, ecdhKeypair.pubKey); + poll.publishMessage(message, ecdhKeypair.pubKey); } // 48 messages in total - expect(maciState.polls[pollId].messages.length).to.eq(48); + expect(poll.messages.length).to.eq(2 * (messageBatchSize - 1)); - expect(maciState.polls[pollId].currentMessageBatchIndex).to.eq(undefined); - expect(maciState.polls[pollId].numBatchesProcessed).to.eq(0); + expect(poll.currentMessageBatchIndex).to.eq(undefined); + expect(poll.numBatchesProcessed).to.eq(0); // Process messages - maciState.polls[pollId].processMessages(pollId); + poll.processMessages(pollId); // currentMessageBatchIndex is 0 because the current batch starts // with index 0. - expect(maciState.polls[pollId].currentMessageBatchIndex).to.eq(0); - expect(maciState.polls[pollId].numBatchesProcessed).to.eq(1); + expect(poll.currentMessageBatchIndex).to.eq(0); + expect(poll.numBatchesProcessed).to.eq(1); // Process messages - maciState.polls[pollId].processMessages(pollId); + poll.processMessages(pollId); - expect(maciState.polls[pollId].currentMessageBatchIndex).to.eq(0); - expect(maciState.polls[pollId].numBatchesProcessed).to.eq(2); + expect(poll.currentMessageBatchIndex).to.eq(0); + expect(poll.numBatchesProcessed).to.eq(2); for (let i = 1; i < messageBatchSize; i++) { - const leaf = maciState.polls[pollId].ballots[i].votes[i - 1]; + const leaf = poll.ballots[i].votes[i - 1]; expect(leaf.toString()).to.eq(voteWeight.toString()); } // Test processAllMessages - const r = maciState.polls[pollId].processAllMessages(); + const r = poll.processAllMessages(); - expect(r.stateLeaves.length).to.eq(maciState.polls[pollId].stateLeaves.length); + expect(r.stateLeaves.length).to.eq(poll.stateLeaves.length); - expect(r.ballots.length).to.eq(maciState.polls[pollId].ballots.length); + expect(r.ballots.length).to.eq(poll.ballots.length); expect(r.ballots.length).to.eq(r.stateLeaves.length); for (let i = 0; i < r.stateLeaves.length; i++) { - expect(r.stateLeaves[i].equals(maciState.polls[pollId].stateLeaves[i])).to.be.true; + expect(r.stateLeaves[i].equals(poll.stateLeaves[i])).to.be.true; - expect(r.ballots[i].equals(maciState.polls[pollId].ballots[i])).to.be.true; + expect(r.ballots[i].equals(poll.ballots[i])).to.be.true; } }); it("should tally ballots correctly", () => { // Start with results = [0...0] let total = BigInt(0); - for (const v of maciState.polls[pollId].results) { + for (const v of poll.results) { total = total + v; } expect(total.toString()).to.eq("0"); // Check that there are untallied results - expect(maciState.polls[pollId].hasUntalliedBallots()).to.be.true; + expect(poll.hasUntalliedBallots()).to.be.true; // First batch tally - maciState.polls[pollId].tallyVotes(); + poll.tallyVotes(); // Recall that each user `i` cast the same number of votes for // their option `i` - for (let i = 0; i < maciState.polls[pollId].results.length - 1; i++) { - expect(maciState.polls[pollId].results[i].toString()).to.eq(voteWeight.toString()); + for (let i = 0; i < poll.results.length - 1; i++) { + expect(poll.results[i].toString()).to.eq(voteWeight.toString()); } - expect(maciState.polls[pollId].hasUntalliedBallots()).to.be.false; + expect(poll.hasUntalliedBallots()).to.be.false; expect(() => { - maciState.polls[pollId].tallyVotes(); + poll.tallyVotes(); }).to.throw; }); });