From f5df5de5ae952afa0498c521b2f2795980b62aac Mon Sep 17 00:00:00 2001 From: aleksandar-veljkovic <97101657+aleksandar-veljkovic@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:11:09 +0200 Subject: [PATCH] feat: anonymous poll joining milestone 1 (#1625) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(poll): add chain hash features BREAKING CHANGE: message processing is changed * fix(ipoll): add missing parameter * fix(poll-tests): add missing parameter maxMessagebatchSize * feat(poll.ts): add chain hash updating * test(poll tests): add test for checking chain hash computation * feat(poll.ts): add batch hashes array computation * feat(poll.sol): pad zeroes to the maximum size of batch * feat(messageprocessor): update process messages to use chain hash * refactor(vkregistry): refactor function call * feat(processmessages.circom): add chainHash feature in circuits and test for that * test(processmessages): rearrange test for key-change * refactor(mergemessages): refactor functions calls which include mergemessages * refactor(mergemessages): add some more changes about functions call which include mergemessages * test(all tests): fixing tests after refactoring code * refactor(accqueue): remove all calls for accqueue * fix(currentmessagebatchindex): fix message batch indexing * refactor(circuit tests): refactor code for circuit testing * test(ceremonyparams.test): correct constants for CeremonyParams test * perf(processmessages.circom + contracts): optimize last batch padding, remove unused inputs * docs(padlastbatch method): update doc comment * docs(poll.ts): remove stale comments * docs(test comments): fix typos * ci(treedepths mock): modify interface for mocked function * fix(ceremony params test): fix circuit inputs * test(messagevalidator): fix function calls for messagevalidator circuit in tests * chore(comments): fix unusefull comments * refactor(poll.sol): replace external contracts with maci only * perf(messageprocessor.sol): hardcode initialization for batchHashes array * docs(comments): fix some more comments * test(test for pr checks): correct some of tests for PR checks * ci: 🎡 renamed old ProcessMessages_10-2-1-2_test * ci: 🎡 correct rapidsnark/build/prover path * style(reviews): solve some reviews for merging * refactor(messageaqq): remove more message merging and message aqq * style(messageaqq): remove more message merging and message aqq * refactor(messageaqq): remove message aqq from subgraph * test(coordinator): hide NOT_MERGED_MESSAGE_TREE error * test(coordinator): fix test about message merging * test(proveonchain): change chainHash calculation * test(proveonchain): fix chainHashes declaration * test(proveonchain): fix chainHash calculation * test(proveonchain): fix chainHashes calculations * test(proveonchain): fix chainHashes calculation * test(proveonchain): fix loop limit * style(review comments): resolve some of review comments * style(review comments): resolve some of review comments * test(lint:ts): fix e2e test because of lint:ts check * docs(wrong changes): fix wrong changes about documentation that is not in our scope * refactor(batchsizes): change batchSizes struct with messageBatchSize variable * refactor(contracts): rollback to provide external contract references * docs(messageprocessor.sol): fix typo * refactor(messagebatchsize): chenge messageBatchSize location from Params.sol to Poll.sol * refactor(maxmessages): remove maxMessages from maxValues * refactor(sltimestemp): remove slTimestamp from circuits * refactor(review comments): resolve more review comments * fix(subgraph): fix bug about maxVoteOptions dunction call * fix(sltimestamp): fix test for removing slTimestap signal * refactor(promise.all): refactor promise.all for only one async call * fix(subgraph): try to fix subgraph build * revert(.nx folder): remove .nx folder from cli folder --------- Co-authored-by: radojevicMihailo Co-authored-by: Aleksandar Veljković Co-authored-by: Boris Cvitak --- apps/subgraph/schemas/schema.v1.graphql | 5 - apps/subgraph/src/maci.ts | 22 +- apps/subgraph/src/poll.ts | 22 - .../subgraph/templates/subgraph.template.yaml | 4 - apps/subgraph/tests/common.ts | 8 +- apps/subgraph/tests/poll/poll.test.ts | 39 +- apps/subgraph/tests/poll/utils.ts | 25 +- apps/website/static/img/completingAPoll.svg | 2 +- packages/circuits/circom/circuits.json | 14 +- .../circom/core/non-qv/processMessages.circom | 105 ++- .../circom/core/qv/processMessages.circom | 112 ++-- .../utils/non-qv/messageValidator.circom | 14 +- .../stateLeafAndBallotTransformer.circom | 6 - .../circom/utils/qv/messageValidator.circom | 16 +- .../qv/stateLeafAndBallotTransformer.circom | 6 - packages/circuits/package.json | 1 - .../ts/__tests__/CeremonyParams.test.ts | 38 +- .../ts/__tests__/MessageValidator.test.ts | 35 - .../ts/__tests__/ProcessMessages.test.ts | 186 +++--- .../StateLeafAndBallotTransformer.test.ts | 14 - .../circuits/ts/__tests__/TallyVotes.test.ts | 7 +- .../circuits/ts/__tests__/utils/constants.ts | 11 +- packages/circuits/ts/__tests__/utils/types.ts | 2 - packages/circuits/ts/types.ts | 4 +- packages/cli/testScript.sh | 7 +- .../ceremony-params/ceremonyParams.test.ts | 21 +- packages/cli/tests/constants.ts | 36 +- packages/cli/tests/e2e/e2e.nonQv.test.ts | 3 - packages/cli/tests/e2e/e2e.test.ts | 16 - packages/cli/tests/e2e/keyChange.test.ts | 5 - packages/cli/tests/unit/poll.test.ts | 2 - .../cli/ts/commands/checkVerifyingKeys.ts | 5 +- packages/cli/ts/commands/deploy.ts | 2 +- packages/cli/ts/commands/deployPoll.ts | 15 +- packages/cli/ts/commands/genLocalState.ts | 19 +- packages/cli/ts/commands/genProofs.ts | 25 +- packages/cli/ts/commands/index.ts | 1 - packages/cli/ts/commands/joinPoll.ts | 461 +++++++++++++ packages/cli/ts/commands/mergeMessages.ts | 121 ---- packages/cli/ts/commands/proveOnChain.ts | 74 +-- packages/cli/ts/commands/publish.ts | 9 +- packages/cli/ts/commands/setVerifyingKeys.ts | 45 +- packages/cli/ts/index.ts | 43 +- packages/cli/ts/sdk/index.ts | 2 - packages/cli/ts/utils/index.ts | 1 - packages/cli/ts/utils/interfaces.ts | 57 +- packages/contracts/contracts/MACI.sol | 44 +- .../contracts/contracts/MessageProcessor.sol | 125 ++-- packages/contracts/contracts/Poll.sol | 108 +-- packages/contracts/contracts/PollFactory.sol | 23 +- packages/contracts/contracts/Tally.sol | 10 +- packages/contracts/contracts/VkRegistry.sol | 28 +- .../interfaces/IMessageProcessor.sol | 4 +- .../contracts/contracts/interfaces/IPoll.sol | 41 +- .../contracts/interfaces/IPollFactory.sol | 16 +- .../contracts/interfaces/IVkRegistry.sol | 4 +- .../contracts/contracts/trees/AccQueue.sol | 467 ------------- .../contracts/trees/AccQueueBinary.sol | 59 -- .../contracts/trees/AccQueueBinary0.sol | 22 - .../contracts/trees/AccQueueBinaryMaci.sol | 21 - .../contracts/trees/AccQueueQuinary.sol | 82 --- .../contracts/trees/AccQueueQuinary0.sol | 22 - .../trees/AccQueueQuinaryBlankSl.sol | 22 - .../contracts/trees/AccQueueQuinaryMaci.sol | 22 - .../contracts/trees/EmptyBallotRoots.sol | 30 + .../contracts/contracts/utilities/Params.sol | 8 +- packages/contracts/deploy-config-example.json | 111 ++-- packages/contracts/package.json | 2 - .../tasks/deploy/maci/09-vkRegistry.ts | 2 - .../contracts/tasks/deploy/poll/01-poll.ts | 54 +- .../contracts/tasks/helpers/ProofGenerator.ts | 25 +- packages/contracts/tasks/helpers/Prover.ts | 74 +-- .../contracts/tasks/helpers/TreeMerger.ts | 75 +-- packages/contracts/tasks/helpers/types.ts | 20 +- packages/contracts/tasks/runner/merge.ts | 27 +- packages/contracts/tasks/runner/prove.ts | 65 +- .../contracts/tasks/runner/submitOnChain.ts | 43 +- packages/contracts/tests/AccQueue.test.ts | 496 -------------- .../contracts/tests/AccQueueBenchmark.test.ts | 290 -------- packages/contracts/tests/MACI.test.ts | 30 +- .../contracts/tests/MessageProcessor.test.ts | 65 +- packages/contracts/tests/Poll.test.ts | 88 +-- packages/contracts/tests/PollFactory.test.ts | 39 +- packages/contracts/tests/Tally.test.ts | 102 ++- packages/contracts/tests/TallyNonQv.test.ts | 51 +- packages/contracts/tests/VkRegistry.test.ts | 9 - packages/contracts/tests/constants.ts | 7 +- packages/contracts/tests/utils.ts | 434 ------------ packages/contracts/ts/genMaciState.ts | 64 +- packages/core/ts/MaciState.ts | 3 + packages/core/ts/Poll.ts | 209 +++--- packages/core/ts/__benchmarks__/index.ts | 2 + .../core/ts/__benchmarks__/utils/constants.ts | 9 +- packages/core/ts/__tests__/MaciState.test.ts | 52 +- packages/core/ts/__tests__/Poll.test.ts | 42 +- packages/core/ts/__tests__/e2e.test.ts | 56 +- packages/core/ts/__tests__/utils/constants.ts | 11 +- packages/core/ts/__tests__/utils/utils.ts | 3 +- packages/core/ts/index.ts | 2 +- packages/core/ts/utils/constants.ts | 2 +- packages/core/ts/utils/types.ts | 13 +- packages/core/ts/utils/utils.ts | 13 +- packages/crypto/package.json | 1 - packages/crypto/ts/AccQueue.ts | 627 ------------------ packages/crypto/ts/__tests__/AccQueue.test.ts | 319 --------- packages/crypto/ts/__tests__/utils.ts | 132 ---- packages/crypto/ts/index.ts | 2 - .../ts/__tests__/data/suites.json | 2 +- .../ts/__tests__/integration.test.ts | 38 +- .../ts/__tests__/maci-keys.test.ts | 6 +- .../ts/__tests__/utils/constants.ts | 6 +- 111 files changed, 1708 insertions(+), 4841 deletions(-) create mode 100644 packages/cli/ts/commands/joinPoll.ts delete mode 100644 packages/cli/ts/commands/mergeMessages.ts delete mode 100644 packages/contracts/contracts/trees/AccQueue.sol delete mode 100644 packages/contracts/contracts/trees/AccQueueBinary.sol delete mode 100644 packages/contracts/contracts/trees/AccQueueBinary0.sol delete mode 100644 packages/contracts/contracts/trees/AccQueueBinaryMaci.sol delete mode 100644 packages/contracts/contracts/trees/AccQueueQuinary.sol delete mode 100644 packages/contracts/contracts/trees/AccQueueQuinary0.sol delete mode 100644 packages/contracts/contracts/trees/AccQueueQuinaryBlankSl.sol delete mode 100644 packages/contracts/contracts/trees/AccQueueQuinaryMaci.sol create mode 100644 packages/contracts/contracts/trees/EmptyBallotRoots.sol delete mode 100644 packages/contracts/tests/AccQueue.test.ts delete mode 100644 packages/contracts/tests/AccQueueBenchmark.test.ts delete mode 100644 packages/crypto/ts/AccQueue.ts delete mode 100644 packages/crypto/ts/__tests__/AccQueue.test.ts delete mode 100644 packages/crypto/ts/__tests__/utils.ts diff --git a/apps/subgraph/schemas/schema.v1.graphql b/apps/subgraph/schemas/schema.v1.graphql index c018b98466..743b4898a6 100644 --- a/apps/subgraph/schemas/schema.v1.graphql +++ b/apps/subgraph/schemas/schema.v1.graphql @@ -32,7 +32,6 @@ type Poll @entity { pollId: BigInt! # uint256 duration: BigInt! # uint256 treeDepth: BigInt! # uint8 - maxMessages: BigInt! maxVoteOption: BigInt! messageProcessor: Bytes! # address tally: Bytes! # address @@ -43,10 +42,6 @@ type Poll @entity { stateRoot: BigInt # uint256 numSignups: BigInt! # uint256 numMessages: BigInt! # uint256 - "merge message tree after ended" - numSrQueueOps: BigInt # uint256 - messageRoot: BigInt - "relations" owner: Bytes! maci: MACI! diff --git a/apps/subgraph/src/maci.ts b/apps/subgraph/src/maci.ts index e2d4815f73..4400899927 100644 --- a/apps/subgraph/src/maci.ts +++ b/apps/subgraph/src/maci.ts @@ -1,31 +1,27 @@ /* eslint-disable no-underscore-dangle */ import { Address, BigInt as GraphBN } from "@graphprotocol/graph-ts"; -import { DeployPoll as DeployPollEvent, SignUp as SignUpEvent, MACI as MaciContract } from "../generated/MACI/MACI"; +import { DeployPoll as DeployPollEvent, SignUp as SignUpEvent } from "../generated/MACI/MACI"; import { Poll } from "../generated/schema"; import { Poll as PollTemplate } from "../generated/templates"; import { Poll as PollContract } from "../generated/templates/Poll/Poll"; -import { ONE_BIG_INT, MESSAGE_TREE_ARITY } from "./utils/constants"; +import { ONE_BIG_INT } from "./utils/constants"; import { createOrLoadMACI, createOrLoadUser, createOrLoadAccount } from "./utils/entity"; export function handleDeployPoll(event: DeployPollEvent): void { const maci = createOrLoadMACI(event); - const id = event.params._pollId; - - const maciContract = MaciContract.bind(Address.fromBytes(maci.id)); - const contracts = maciContract.getPoll(id); - const poll = new Poll(contracts.poll); - const contract = PollContract.bind(contracts.poll); + const poll = new Poll(event.params.pollAddr.poll); + const contract = PollContract.bind(event.params.pollAddr.poll); + const maxVoteOptions = contract.maxVoteOptions(); const treeDepths = contract.treeDepths(); const durations = contract.getDeployTimeAndDuration(); - poll.pollId = id; - poll.messageProcessor = contracts.messageProcessor; - poll.tally = contracts.tally; - poll.maxMessages = GraphBN.fromI32(MESSAGE_TREE_ARITY ** treeDepths.getMessageTreeDepth()); - poll.maxVoteOption = GraphBN.fromI32(MESSAGE_TREE_ARITY ** treeDepths.getVoteOptionTreeDepth()); + poll.pollId = event.params._pollId; + poll.messageProcessor = event.params.pollAddr.messageProcessor; + poll.tally = event.params.pollAddr.tally; + poll.maxVoteOption = maxVoteOptions; poll.treeDepth = GraphBN.fromI32(treeDepths.value0); poll.duration = durations.value1; poll.mode = GraphBN.fromI32(event.params._mode); diff --git a/apps/subgraph/src/poll.ts b/apps/subgraph/src/poll.ts index 7c9065d023..82117110c6 100644 --- a/apps/subgraph/src/poll.ts +++ b/apps/subgraph/src/poll.ts @@ -3,8 +3,6 @@ import { Poll, Vote, MACI } from "../generated/schema"; import { MergeMaciState as MergeMaciStateEvent, - MergeMessageAq as MergeMessageAqEvent, - MergeMessageAqSubRoots as MergeMessageAqSubRootsEvent, PublishMessage as PublishMessageEvent, } from "../generated/templates/Poll/Poll"; @@ -29,26 +27,6 @@ export function handleMergeMaciState(event: MergeMaciStateEvent): void { } } -export function handleMergeMessageAq(event: MergeMessageAqEvent): void { - const poll = Poll.load(event.address); - - if (poll) { - poll.messageRoot = event.params._messageRoot; - poll.updatedAt = event.block.timestamp; - poll.save(); - } -} - -export function handleMergeMessageAqSubRoots(event: MergeMessageAqSubRootsEvent): void { - const poll = Poll.load(event.address); - - if (poll) { - poll.numSrQueueOps = event.params._numSrQueueOps; - poll.updatedAt = event.block.timestamp; - poll.save(); - } -} - export function handlePublishMessage(event: PublishMessageEvent): void { const vote = new Vote(event.transaction.hash.concatI32(event.logIndex.toI32())); vote.data = event.params._message.data; diff --git a/apps/subgraph/templates/subgraph.template.yaml b/apps/subgraph/templates/subgraph.template.yaml index 015f1cf1cc..b62ff6afee 100644 --- a/apps/subgraph/templates/subgraph.template.yaml +++ b/apps/subgraph/templates/subgraph.template.yaml @@ -56,10 +56,6 @@ templates: eventHandlers: - event: MergeMaciState(indexed uint256,indexed uint256) handler: handleMergeMaciState - - event: MergeMessageAq(indexed uint256) - handler: handleMergeMessageAq - - event: MergeMessageAqSubRoots(indexed uint256) - handler: handleMergeMessageAqSubRoots - event: PublishMessage((uint256[10]),(uint256,uint256)) handler: handlePublishMessage file: ./src/poll.ts diff --git a/apps/subgraph/tests/common.ts b/apps/subgraph/tests/common.ts index be439e3d17..dc731d4ac2 100644 --- a/apps/subgraph/tests/common.ts +++ b/apps/subgraph/tests/common.ts @@ -9,11 +9,13 @@ export const DEFAULT_MESSAGE_PROCESSOR_ADDRESS = Address.fromString("0x000000000 export const DEFAULT_TALLY_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000003"); export function mockPollContract(): void { - createMockedFunction(DEFAULT_POLL_ADDRESS, "treeDepths", "treeDepths():(uint8,uint8,uint8,uint8)").returns([ + createMockedFunction(DEFAULT_POLL_ADDRESS, "maxVoteOptions", "maxVoteOptions():(uint256)").returns([ + ethereum.Value.fromI32(20), + ]); + + createMockedFunction(DEFAULT_POLL_ADDRESS, "treeDepths", "treeDepths():(uint8,uint8)").returns([ ethereum.Value.fromI32(1), ethereum.Value.fromI32(2), - ethereum.Value.fromI32(3), - ethereum.Value.fromI32(4), ]); createMockedFunction( diff --git a/apps/subgraph/tests/poll/poll.test.ts b/apps/subgraph/tests/poll/poll.test.ts index 88cef0207f..5caffbb228 100644 --- a/apps/subgraph/tests/poll/poll.test.ts +++ b/apps/subgraph/tests/poll/poll.test.ts @@ -4,27 +4,16 @@ import { test, describe, afterEach, clearStore, assert, beforeEach } from "match import { MACI, Poll } from "../../generated/schema"; import { handleDeployPoll } from "../../src/maci"; -import { - handleMergeMaciState, - handleMergeMessageAq, - handleMergeMessageAqSubRoots, - handlePublishMessage, -} from "../../src/poll"; -import { DEFAULT_POLL_ADDRESS, mockMaciContract, mockPollContract } from "../common"; +import { handleMergeMaciState, handlePublishMessage } from "../../src/poll"; +import { DEFAULT_POLL_ADDRESS, mockPollContract } from "../common"; import { createDeployPollEvent } from "../maci/utils"; -import { - createMergeMaciStateEvent, - createMergeMessageAqEvent, - createMergeMessageAqSubRootsEvent, - createPublishMessageEvent, -} from "./utils"; +import { createMergeMaciStateEvent, createPublishMessageEvent } from "./utils"; -export { handleMergeMaciState, handleMergeMessageAq, handleMergeMessageAqSubRoots, handlePublishMessage }; +export { handleMergeMaciState, handlePublishMessage }; describe("Poll", () => { beforeEach(() => { - mockMaciContract(); mockPollContract(); // mock the deploy poll event with non qv mode set @@ -53,26 +42,6 @@ describe("Poll", () => { assert.assertTrue(maci.polls.load().length === 1); }); - test("should handle merge message queue properly", () => { - const event = createMergeMessageAqEvent(DEFAULT_POLL_ADDRESS, BigInt.fromI32(1)); - - handleMergeMessageAq(event); - - const poll = Poll.load(DEFAULT_POLL_ADDRESS)!; - - assert.fieldEquals("Poll", poll.id.toHex(), "messageRoot", "1"); - }); - - test("should handle merge message queue subroots properly", () => { - const event = createMergeMessageAqSubRootsEvent(DEFAULT_POLL_ADDRESS, BigInt.fromI32(1)); - - handleMergeMessageAqSubRoots(event); - - const poll = Poll.load(event.address)!; - - assert.fieldEquals("Poll", poll.id.toHex(), "numSrQueueOps", "1"); - }); - test("should handle publish message properly", () => { const event = createPublishMessageEvent( DEFAULT_POLL_ADDRESS, diff --git a/apps/subgraph/tests/poll/utils.ts b/apps/subgraph/tests/poll/utils.ts index 35ea5845dd..118618c0d3 100644 --- a/apps/subgraph/tests/poll/utils.ts +++ b/apps/subgraph/tests/poll/utils.ts @@ -2,12 +2,7 @@ import { Address, BigInt as GraphBN, ethereum } from "@graphprotocol/graph-ts"; // eslint-disable-next-line import/no-extraneous-dependencies import { newMockEvent } from "matchstick-as"; -import { - MergeMaciState, - MergeMessageAq, - MergeMessageAqSubRoots, - PublishMessage, -} from "../../generated/templates/Poll/Poll"; +import { MergeMaciState, PublishMessage } from "../../generated/templates/Poll/Poll"; export function createMergeMaciStateEvent(address: Address, stateRoot: GraphBN, numSignups: GraphBN): MergeMaciState { const event = changetype(newMockEvent()); @@ -19,24 +14,6 @@ export function createMergeMaciStateEvent(address: Address, stateRoot: GraphBN, return event; } -export function createMergeMessageAqEvent(address: Address, messageRoot: GraphBN): MergeMessageAq { - const event = changetype(newMockEvent()); - - event.parameters.push(new ethereum.EventParam("_messageRoot", ethereum.Value.fromUnsignedBigInt(messageRoot))); - event.address = address; - - return event; -} - -export function createMergeMessageAqSubRootsEvent(address: Address, numSrQueueOps: GraphBN): MergeMessageAqSubRoots { - const event = changetype(newMockEvent()); - - event.parameters.push(new ethereum.EventParam("_numSrQueueOps", ethereum.Value.fromUnsignedBigInt(numSrQueueOps))); - event.address = address; - - return event; -} - export function createPublishMessageEvent( address: Address, data: GraphBN[], diff --git a/apps/website/static/img/completingAPoll.svg b/apps/website/static/img/completingAPoll.svg index d9e14f2707..acc935bf0c 100644 --- a/apps/website/static/img/completingAPoll.svg +++ b/apps/website/static/img/completingAPoll.svg @@ -1,4 +1,4 @@ -
Coordinator
Coord...
Merge message acc queue
Merge message acc qu...
Merge state acc queue
Merge state acc queue
MACI.mergeStateAqSubRoots()
MACI.mergeStateAqSubRoots()
MACI.mergeStateAq()
MACI.mergeStateAq()
These need to be merged from a Poll contract
These need to be merged from...
Poll.mergeMaciStateAq()
Poll.mergeMaciStateAq()
Poll.mergeMaciStateAqSubRoots()
Poll.mergeMaciStateAqSubRoots()
Generate the state and ballot roots commitment
Generate the state a...
hash([stateRootHash, emptyBallotRoot, 0])
hash([stateRootHash, emptyBallo...
Poll.mergeMessageAqSubRoots()
Poll.mergeMessageAqSubRoots()
Poll.mergeMessageAq()
Poll.mergeMessageAq()
Merging the stateAq also results in
Merging the stateA...
Text is not SVG - cannot display
\ No newline at end of file +
Coordinator
Coord...
Merge message acc queue
Merge message acc qu...
Merge state acc queue
Merge state acc queue
MACI.mergeStateAqSubRoots()
MACI.mergeStateAqSubRoots()
MACI.mergeStateAq()
MACI.mergeStateAq()
These need to be merged from a Poll contract
These need to be merged from...
Poll.mergeMaciStateAq()
Poll.mergeMaciStateAq()
Poll.mergeMaciStateAqSubRoots()
Poll.mergeMaciStateAqSubRoots()
Generate the state and ballot roots commitment
Generate the state a...
hash([stateRootHash, emptyBallotRoot, 0])
hash([stateRootHash, emptyBallo...
Poll.mergeMessageAq()
Poll.mergeMessageAq()
Merging the stateAq also results in
Merging the stateA...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/packages/circuits/circom/circuits.json b/packages/circuits/circom/circuits.json index 22650b1349..01e0d7b731 100644 --- a/packages/circuits/circom/circuits.json +++ b/packages/circuits/circom/circuits.json @@ -1,32 +1,30 @@ { - "ProcessMessages_10-2-1-2_test": { + "ProcessMessages_10-20-2_test": { "file": "./core/qv/processMessages", "template": "ProcessMessages", - "params": [10, 2, 1, 2], + "params": [10, 20, 2], "pubs": [ "numSignUps", "index", "batchEndIndex", - "msgRoot", "currentSbCommitment", "newSbCommitment", - "pollEndTimestamp", + "outputBatchHash", "actualStateTreeDepth", "coordinatorPublicKeyHash" ] }, - "ProcessMessagesNonQv_10-2-1-2_test": { + "ProcessMessagesNonQv_10-20-2_test": { "file": "./core/non-qv/processMessages", "template": "ProcessMessagesNonQv", - "params": [10, 2, 1, 2], + "params": [10, 20, 2], "pubs": [ "numSignUps", "index", "batchEndIndex", - "msgRoot", "currentSbCommitment", "newSbCommitment", - "pollEndTimestamp", + "outputBatchHash", "actualStateTreeDepth", "coordinatorPublicKeyHash" ] diff --git a/packages/circuits/circom/core/non-qv/processMessages.circom b/packages/circuits/circom/core/non-qv/processMessages.circom index 05dda47029..9dbede9d6d 100644 --- a/packages/circuits/circom/core/non-qv/processMessages.circom +++ b/packages/circuits/circom/core/non-qv/processMessages.circom @@ -14,28 +14,22 @@ include "../../trees/incrementalQuinaryTree.circom"; /** * Proves the correctness of processing a batch of MACI messages. - * The msgBatchDepth parameter is known as msgSubtreeDepth and indicates the depth - * of the shortest tree that can fit all the messages in a batch. * This template does not support Quadratic Voting (QV). */ template ProcessMessagesNonQv( stateTreeDepth, - msgTreeDepth, - msgBatchDepth, + batchSize, voteOptionTreeDepth ) { // Must ensure that the trees have a valid structure. assert(stateTreeDepth > 0); - assert(msgBatchDepth > 0); + assert(batchSize > 0); assert(voteOptionTreeDepth > 0); - assert(msgTreeDepth >= msgBatchDepth); // Default for IQT (quinary trees). - var MESSAGE_TREE_ARITY = 5; + var VOTE_OPTION_TREE_ARITY = 5; // Default for Binary trees. var STATE_TREE_ARITY = 2; - var batchSize = MESSAGE_TREE_ARITY ** msgBatchDepth; - var maxVoteOptions = MESSAGE_TREE_ARITY ** voteOptionTreeDepth; var MSG_LENGTH = 10; var PACKED_CMD_LENGTH = 4; var STATE_LEAF_LENGTH = 4; @@ -49,15 +43,15 @@ include "../../trees/incrementalQuinaryTree.circom"; var msgTreeZeroValue = 8370432830353022751713833565135785980866757267633941821328460903436894336785; // Number of users that have completed the sign up. - signal input numSignUps; - // Time when the poll ends. - signal input pollEndTimestamp; - // The existing message tree root. - signal input msgRoot; + signal numSignUps; + // Number of options for this poll. + signal maxVoteOptions; + // Value of chainHash at beginning of batch + signal input inputBatchHash; + // Value of chainHash at end of batch + signal input outputBatchHash; // The messages. signal input msgs[batchSize][MSG_LENGTH]; - // Sibling messages. - signal input msgSubrootPathElements[msgTreeDepth - msgBatchDepth][MESSAGE_TREE_ARITY - 1]; // The coordinator's private key. signal input coordPrivKey; // The ECDH public key per message. @@ -99,12 +93,20 @@ include "../../trees/incrementalQuinaryTree.circom"; signal input currentBallotsPathElements[batchSize][stateTreeDepth][STATE_TREE_ARITY - 1]; // Intermediate vote weights. signal input currentVoteWeights[batchSize]; - signal input currentVoteWeightsPathElements[batchSize][voteOptionTreeDepth][MESSAGE_TREE_ARITY - 1]; + signal input currentVoteWeightsPathElements[batchSize][voteOptionTreeDepth][VOTE_OPTION_TREE_ARITY - 1]; // nb. The messages are processed in REVERSE order. // Therefore, the index of the first message to process does not match the index of the // first message (e.g., [msg1, msg2, msg3] => first message to process has index 3). + // The index of the first message in the batch, inclusive. + signal batchStartIndex; + + // The index of the last message in the batch to process, exclusive. + // This value may be less than batchStartIndex + batchSize if this batch is + // the last batch and the total number of messages is not a multiple of the batch size. + signal batchEndIndex; + // The history of state and ballot roots and temporary intermediate // signals (for processing purposes). signal stateRoots[batchSize + 1]; @@ -121,7 +123,7 @@ include "../../trees/incrementalQuinaryTree.circom"; // ----------------------------------------------------------------------- // 0. Ensure that the maximum vote options signal is valid and if // the maximum users signal is valid. - var maxVoValid = LessEqThan(32)([maxVoteOptions, MESSAGE_TREE_ARITY ** voteOptionTreeDepth]); + var maxVoValid = LessEqThan(32)([maxVoteOptions, VOTE_OPTION_TREE_ARITY ** voteOptionTreeDepth]); maxVoValid === 1; // Check numSignUps <= the max number of users (i.e., number of state leaves @@ -129,52 +131,29 @@ include "../../trees/incrementalQuinaryTree.circom"; var numSignUpsValid = LessEqThan(32)([numSignUps, STATE_TREE_ARITY ** stateTreeDepth]); numSignUpsValid === 1; - // Hash each Message to check their existence in the Message tree. + // Hash each Message to check their existence in the Message chain hash. var computedMessageHashers[batchSize]; + var computedChainHashes[batchSize]; + var chainHash[batchSize + 1]; + chainHash[0] = inputBatchHash; for (var i = 0; i < batchSize; i++) { + // calculate message hash computedMessageHashers[i] = MessageHasher()(msgs[i], encPubKeys[i]); + // check if message is valid or not (if index of message is less than index of last valid message in batch) + var batchStartIndexValid = SafeLessThan(32)([batchStartIndex + i, batchEndIndex]); + // calculate chain hash if message is valid + computedChainHashes[i] = PoseidonHasher(2)([chainHash[i], computedMessageHashers[i]]); + // choose between old chain hash value and new chain hash value depending if message is valid or not + chainHash[i + 1] = Mux1()([chainHash[i], computedChainHashes[i]], batchStartIndexValid); } - // If endIndex - startIndex < batchSize, the remaining + // If batchEndIndex < batchStartIndex + i, the remaining // message hashes should be the zero value. // e.g. [m, z, z, z, z] if there is only 1 real message in the batch // This makes possible to have a batch of messages which is only partially full. - var computedLeaves[batchSize]; - var computedPathElements[msgTreeDepth - msgBatchDepth][MESSAGE_TREE_ARITY - 1]; - var computedPathIndex[msgTreeDepth - msgBatchDepth]; - - for (var i = 0; i < batchSize; i++) { - var batchStartIndexValid = SafeLessThan(32)([index + i, batchEndIndex]); - computedLeaves[i] = Mux1()([msgTreeZeroValue, computedMessageHashers[i]], batchStartIndexValid); - } - - for (var i = 0; i < msgTreeDepth - msgBatchDepth; i++) { - for (var j = 0; j < MESSAGE_TREE_ARITY - 1; j++) { - computedPathElements[i][j] = msgSubrootPathElements[i][j]; - } - } - // Computing the path_index values. Since msgBatchLeavesExists tests - // the existence of a subroot, the length of the proof correspond to the last - // n elements of a proof from the root to a leaf, where n = msgTreeDepth - msgBatchDepth. - // e.g. if startIndex = 25, msgTreeDepth = 4, msgBatchDepth = 2, then path_index = [1, 0]. - var computedMsgBatchPathIndices[msgTreeDepth] = QuinGeneratePathIndices(msgTreeDepth)(index); - - for (var i = msgBatchDepth; i < msgTreeDepth; i++) { - computedPathIndex[i - msgBatchDepth] = computedMsgBatchPathIndices[i]; - } - - // Check whether each message exists in the Message tree. - // Otherwise, throws (needs constraint to prevent such a proof). - // To save constraints, compute the subroot of the messages and check - // whether the subroot is a member of the message tree. This means that - // batchSize must be the message tree arity raised to some power (e.g. 5 ^ n). - QuinBatchLeavesExists(msgTreeDepth, msgBatchDepth)( - msgRoot, - computedLeaves, - computedPathIndex, - computedPathElements - ); + // Ensure that right output batch hash was sent to circuit + chainHash[batchSize] === outputBatchHash; // Decrypt each Message to a Command. // MessageToCommand derives the ECDH shared key from the coordinator's @@ -232,7 +211,7 @@ include "../../trees/incrementalQuinaryTree.circom"; // Process as vote type message. var currentStateLeavesPathElement[stateTreeDepth][STATE_TREE_ARITY - 1]; var currentBallotPathElement[stateTreeDepth][STATE_TREE_ARITY - 1]; - var currentVoteWeightsPathElement[voteOptionTreeDepth][MESSAGE_TREE_ARITY - 1]; + var currentVoteWeightsPathElement[voteOptionTreeDepth][VOTE_OPTION_TREE_ARITY - 1]; for (var j = 0; j < stateTreeDepth; j++) { for (var k = 0; k < STATE_TREE_ARITY - 1; k++) { @@ -242,14 +221,14 @@ include "../../trees/incrementalQuinaryTree.circom"; } for (var j = 0; j < voteOptionTreeDepth; j++) { - for (var k = 0; k < MESSAGE_TREE_ARITY - 1; k++) { + for (var k = 0; k < VOTE_OPTION_TREE_ARITY - 1; k++) { currentVoteWeightsPathElement[j][k] = currentVoteWeightsPathElements[i][j][k]; } } (computedNewVoteStateRoot[i], computedNewVoteBallotRoot[i]) = ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth)( numSignUps, - pollEndTimestamp, + maxVoteOptions, stateRoots[i + 1], ballotRoots[i + 1], actualStateTreeDepth, @@ -293,7 +272,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { var BALLOT_LENGTH = 2; var MSG_LENGTH = 10; var PACKED_CMD_LENGTH = 4; - var MESSAGE_TREE_ARITY = 5; + var VOTE_OPTION_TREE_ARITY = 5; var STATE_TREE_ARITY = 2; var BALLOT_NONCE_IDX = 0; // Ballot vote option (VO) root index. @@ -311,7 +290,8 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // Inputs representing the message and the current state. signal input numSignUps; - signal input pollEndTimestamp; + + signal input maxVoteOptions; // The current value of the state tree root. signal input currentStateRoot; @@ -331,7 +311,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // The current vote weight and related path elements. signal input currentVoteWeight; - signal input currentVoteWeightsPathElements[voteOptionTreeDepth][MESSAGE_TREE_ARITY - 1]; + signal input currentVoteWeightsPathElements[voteOptionTreeDepth][VOTE_OPTION_TREE_ARITY - 1]; // Inputs related to the command being processed. signal input cmdStateIndex; @@ -364,8 +344,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { maxVoteOptions, [stateLeaf[STATE_LEAF_PUB_X_IDX], stateLeaf[STATE_LEAF_PUB_Y_IDX]], stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - stateLeaf[STATE_LEAF_TIMESTAMP_IDX], - pollEndTimestamp, + // stateLeaf[STATE_LEAF_TIMESTAMP_IDX], ballot[BALLOT_NONCE_IDX], currentVoteWeight, cmdStateIndex, diff --git a/packages/circuits/circom/core/qv/processMessages.circom b/packages/circuits/circom/core/qv/processMessages.circom index f1ad356f36..15497bd2d6 100644 --- a/packages/circuits/circom/core/qv/processMessages.circom +++ b/packages/circuits/circom/core/qv/processMessages.circom @@ -14,28 +14,22 @@ include "../../trees/incrementalMerkleTree.circom"; /** * Proves the correctness of processing a batch of MACI messages. - * The msgBatchDepth parameter is known as msgSubtreeDepth and indicates the depth - * of the shortest tree that can fit all the messages in a batch. * This template supports the Quadratic Voting (QV). */ template ProcessMessages( stateTreeDepth, - msgTreeDepth, - msgBatchDepth, + batchSize, voteOptionTreeDepth ) { // Must ensure that the trees have a valid structure. assert(stateTreeDepth > 0); - assert(msgBatchDepth > 0); + assert(batchSize > 0); assert(voteOptionTreeDepth > 0); - assert(msgTreeDepth >= msgBatchDepth); // Default for IQT (quinary trees). - var MESSAGE_TREE_ARITY = 5; + var VOTE_OPTION_TREE_ARITY = 5; // Default for binary trees. var STATE_TREE_ARITY = 2; - var batchSize = MESSAGE_TREE_ARITY ** msgBatchDepth; - var maxVoteOptions = MESSAGE_TREE_ARITY ** voteOptionTreeDepth; var MSG_LENGTH = 10; var PACKED_CMD_LENGTH = 4; var STATE_LEAF_LENGTH = 4; @@ -47,17 +41,21 @@ template ProcessMessages( var STATE_LEAF_VOICE_CREDIT_BALANCE_IDX = 2; var STATE_LEAF_TIMESTAMP_IDX = 3; var msgTreeZeroValue = 8370432830353022751713833565135785980866757267633941821328460903436894336785; - - // Number of users that signup - signal input numSignUps; - // Time when the poll ends. - signal input pollEndTimestamp; - // The existing message tree root. - signal input msgRoot; + + // nb. The usage of SHA-256 hash is necessary to save some gas costs at verification time + // at the cost of more constraints for the prover. + // Basically, some values from the contract are passed as private inputs and the hash as a public input. + + // Number of users that have completed the sign up. + signal numSignUps; + // Number of options for this poll. + signal maxVoteOptions; + // Value of chainHash at beginning of batch + signal input inputBatchHash; + // Value of chainHash at end of batch + signal input outputBatchHash; // The messages. signal input msgs[batchSize][MSG_LENGTH]; - // Sibling messages. - signal input msgSubrootPathElements[msgTreeDepth - msgBatchDepth][MESSAGE_TREE_ARITY - 1]; // The coordinator's private key. signal input coordPrivKey; // The ECDH public key per message. @@ -99,12 +97,20 @@ template ProcessMessages( signal input currentBallotsPathElements[batchSize][stateTreeDepth][STATE_TREE_ARITY - 1]; // Intermediate vote weights. signal input currentVoteWeights[batchSize]; - signal input currentVoteWeightsPathElements[batchSize][voteOptionTreeDepth][MESSAGE_TREE_ARITY - 1]; + signal input currentVoteWeightsPathElements[batchSize][voteOptionTreeDepth][VOTE_OPTION_TREE_ARITY - 1]; // nb. The messages are processed in REVERSE order. // Therefore, the index of the first message to process does not match the index of the // first message (e.g., [msg1, msg2, msg3] => first message to process has index 3). + // The index of the first message in the batch, inclusive. + signal batchStartIndex; + + // The index of the last message in the batch to process, exclusive. + // This value may be less than batchSize if this batch is + // the last batch and the total number of messages is not a multiple of the batch size. + signal batchEndIndex; + // The history of state and ballot roots and temporary intermediate // signals (for processing purposes). signal stateRoots[batchSize + 1]; @@ -116,7 +122,7 @@ template ProcessMessages( // 0. Ensure that the maximum vote options signal is valid and if // the maximum users signal is valid. - var maxVoValid = LessEqThan(32)([maxVoteOptions, MESSAGE_TREE_ARITY ** voteOptionTreeDepth]); + var maxVoValid = LessEqThan(32)([maxVoteOptions, VOTE_OPTION_TREE_ARITY ** voteOptionTreeDepth]); maxVoValid === 1; // Check numSignUps <= the max number of users (i.e., number of state leaves @@ -126,50 +132,26 @@ template ProcessMessages( // Hash each Message to check their existence in the Message tree. var computedMessageHashers[batchSize]; + var computedChainHashes[batchSize]; + var chainHash[batchSize + 1]; + chainHash[0] = inputBatchHash; for (var i = 0; i < batchSize; i++) { + // calculate message hash computedMessageHashers[i] = MessageHasher()(msgs[i], encPubKeys[i]); + // check if message is valid or not (if index of message is less than index of last valid message in batch) + var batchStartIndexValid = SafeLessThan(32)([batchStartIndex + i, batchEndIndex]); + // calculate chain hash if message is valid + computedChainHashes[i] = PoseidonHasher(2)([chainHash[i], computedMessageHashers[i]]); + // choose between old chain hash value and new chain hash value depending if message is valid or not + chainHash[i + 1] = Mux1()([chainHash[i], computedChainHashes[i]], batchStartIndexValid); } - // If endIndex - startIndex < batchSize, the remaining + // If batchEndIndex < batchStartIndex + i, the remaining // message hashes should be the zero value. // e.g. [m, z, z, z, z] if there is only 1 real message in the batch // This makes possible to have a batch of messages which is only partially full. - var computedLeaves[batchSize]; - var computedPathElements[msgTreeDepth - msgBatchDepth][MESSAGE_TREE_ARITY - 1]; - var computedPathIndex[msgTreeDepth - msgBatchDepth]; - for (var i = 0; i < batchSize; i++) { - var batchStartIndexValid = SafeLessThan(32)([index + i, batchEndIndex]); - computedLeaves[i] = Mux1()([msgTreeZeroValue, computedMessageHashers[i]], batchStartIndexValid); - } - - for (var i = 0; i < msgTreeDepth - msgBatchDepth; i++) { - for (var j = 0; j < MESSAGE_TREE_ARITY - 1; j++) { - computedPathElements[i][j] = msgSubrootPathElements[i][j]; - } - } - - // Computing the path_index values. Since msgBatchLeavesExists tests - // the existence of a subroot, the length of the proof correspond to the last - // n elements of a proof from the root to a leaf, where n = msgTreeDepth - msgBatchDepth. - // e.g. if startIndex = 25, msgTreeDepth = 4, msgBatchDepth = 2, then path_index = [1, 0]. - var computedMsgBatchPathIndices[msgTreeDepth] = QuinGeneratePathIndices(msgTreeDepth)(index); - - for (var i = msgBatchDepth; i < msgTreeDepth; i++) { - computedPathIndex[i - msgBatchDepth] = computedMsgBatchPathIndices[i]; - } - - // Check whether each message exists in the Message tree. - // Otherwise, throws (needs constraint to prevent such a proof). - // To save constraints, compute the subroot of the messages and check - // whether the subroot is a member of the message tree. This means that - // batchSize must be the message tree arity raised to some power (e.g. 5 ^ n). - QuinBatchLeavesExists(msgTreeDepth, msgBatchDepth)( - msgRoot, - computedLeaves, - computedPathIndex, - computedPathElements - ); + chainHash[batchSize] === outputBatchHash; // Decrypt each Message to a Command. // MessageToCommand derives the ECDH shared key from the coordinator's @@ -227,7 +209,7 @@ template ProcessMessages( // Process as vote type message. var currentStateLeavesPathElement[stateTreeDepth][STATE_TREE_ARITY - 1]; var currentBallotPathElement[stateTreeDepth][STATE_TREE_ARITY - 1]; - var currentVoteWeightsPathElement[voteOptionTreeDepth][MESSAGE_TREE_ARITY - 1]; + var currentVoteWeightsPathElement[voteOptionTreeDepth][VOTE_OPTION_TREE_ARITY - 1]; for (var j = 0; j < stateTreeDepth; j++) { for (var k = 0; k < STATE_TREE_ARITY - 1; k++) { @@ -237,14 +219,14 @@ template ProcessMessages( } for (var j = 0; j < voteOptionTreeDepth; j++) { - for (var k = 0; k < MESSAGE_TREE_ARITY - 1; k++) { + for (var k = 0; k < VOTE_OPTION_TREE_ARITY - 1; k++) { currentVoteWeightsPathElement[j][k] = currentVoteWeightsPathElements[i][j][k]; } } (computedNewVoteStateRoot[i], computedNewVoteBallotRoot[i]) = ProcessOne(stateTreeDepth, voteOptionTreeDepth)( numSignUps, - pollEndTimestamp, + maxVoteOptions, stateRoots[i + 1], ballotRoots[i + 1], actualStateTreeDepth, @@ -288,7 +270,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { var BALLOT_LENGTH = 2; var MSG_LENGTH = 10; var PACKED_CMD_LENGTH = 4; - var MESSAGE_TREE_ARITY = 5; + var VOTE_OPTION_TREE_ARITY = 5; var STATE_TREE_ARITY = 2; var BALLOT_NONCE_IDX = 0; // Ballot vote option (VO) root index. @@ -308,8 +290,9 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // Number of users that have completed the sign up. signal input numSignUps; - // Time when the poll ends. - signal input pollEndTimestamp; + + signal input maxVoteOptions; + // The current value of the state tree root. signal input currentStateRoot; // The current value of the ballot tree root. @@ -328,7 +311,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // The current vote weight and related path elements. signal input currentVoteWeight; - signal input currentVoteWeightsPathElements[voteOptionTreeDepth][MESSAGE_TREE_ARITY - 1]; + signal input currentVoteWeightsPathElements[voteOptionTreeDepth][VOTE_OPTION_TREE_ARITY - 1]; // Inputs related to the command being processed. signal input cmdStateIndex; @@ -361,8 +344,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { maxVoteOptions, [stateLeaf[STATE_LEAF_PUB_X_IDX], stateLeaf[STATE_LEAF_PUB_Y_IDX]], stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - stateLeaf[STATE_LEAF_TIMESTAMP_IDX], - pollEndTimestamp, + // stateLeaf[STATE_LEAF_TIMESTAMP_IDX], ballot[BALLOT_NONCE_IDX], currentVoteWeight, cmdStateIndex, diff --git a/packages/circuits/circom/utils/non-qv/messageValidator.circom b/packages/circuits/circom/utils/non-qv/messageValidator.circom index 990e4f68d6..e5cad3aaec 100644 --- a/packages/circuits/circom/utils/non-qv/messageValidator.circom +++ b/packages/circuits/circom/utils/non-qv/messageValidator.circom @@ -33,10 +33,6 @@ template MessageValidatorNonQv() { signal input sigR8[2]; // ECDSA signature of the command (S part). signal input sigS; - // State leaf signup timestamp. - signal input slTimestamp; - // Timestamp indicating when the poll ends. - signal input pollEndTimestamp; // State leaf current voice credit balance. signal input currentVoiceCreditBalance; // Current number of votes for specific option. @@ -65,11 +61,8 @@ template MessageValidatorNonQv() { // Check (4) - The signature must be correct. var computedIsSignatureValid = VerifySignature()(pubKey, sigR8, sigS, cmd); - - // Check (5) - The state leaf must be inserted before the Poll period end. - var computedIsTimestampValid = SafeLessEqThan(252)([slTimestamp, pollEndTimestamp]); - // Check (6) - There must be sufficient voice credits. + // Check (5) - There must be sufficient voice credits. // The check ensure that currentVoiceCreditBalance + (currentVotesForOption) >= (voteWeight). var computedAreVoiceCreditsSufficient = SafeGreaterEqThan(252)( [ @@ -78,15 +71,14 @@ template MessageValidatorNonQv() { ] ); - // When all six checks are correct, then isValid = 1. + // When all five checks are correct, then isValid = 1. var computedIsUpdateValid = IsEqual()( [ - 6, + 5, computedIsSignatureValid + computedAreVoiceCreditsSufficient + computedIsNonceValid + computedIsStateLeafIndexValid + - computedIsTimestampValid + computedIsVoteOptionIndexValid ] ); diff --git a/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom b/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom index b4d487a357..28426f5290 100644 --- a/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom +++ b/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom @@ -25,10 +25,6 @@ template StateLeafAndBallotTransformerNonQv() { signal input slPubKey[2]; // Current voice credit balance. signal input slVoiceCreditBalance; - // Signup timestamp. - signal input slTimestamp; - // Timestamp indicating when the poll ends. - signal input pollEndTimestamp; // The following signals represents a ballot. // Nonce. @@ -83,8 +79,6 @@ template StateLeafAndBallotTransformerNonQv() { slPubKey, cmdSigR8, cmdSigS, - slTimestamp, - pollEndTimestamp, slVoiceCreditBalance, ballotCurrentVotesForOption, cmdNewVoteWeight diff --git a/packages/circuits/circom/utils/qv/messageValidator.circom b/packages/circuits/circom/utils/qv/messageValidator.circom index e2b0d46eff..bee3af3994 100644 --- a/packages/circuits/circom/utils/qv/messageValidator.circom +++ b/packages/circuits/circom/utils/qv/messageValidator.circom @@ -33,10 +33,6 @@ template MessageValidator() { signal input sigR8[2]; // ECDSA signature of the command (S part). signal input sigS; - // State leaf signup timestamp. - signal input slTimestamp; - // Timestamp indicating when the poll ends. - signal input pollEndTimestamp; // State leaf current voice credit balance. signal input currentVoiceCreditBalance; // Current number of votes for specific option. @@ -66,15 +62,12 @@ template MessageValidator() { // Check (4) - The signature must be correct. var computedIsSignatureValid = VerifySignature()(pubKey, sigR8, sigS, cmd); - // Check (5) - The state leaf must be inserted before the Poll period end. - var computedIsTimestampValid = SafeLessEqThan(252)([slTimestamp, pollEndTimestamp]); - - // Check (6) - There must be sufficient voice credits. + // Check (5) - There must be sufficient voice credits. // The check ensure that the voteWeight is < sqrt(field size) // so that voteWeight ^ 2 will not overflow. var computedIsVoteWeightValid = SafeLessEqThan(252)([voteWeight, 147946756881789319005730692170996259609]); - // Check (7) - Check the current voice credit balance. + // Check (6) - Check the current voice credit balance. // The check ensure that currentVoiceCreditBalance + (currentVotesForOption ** 2) >= (voteWeight ** 2) var computedAreVoiceCreditsSufficient = SafeGreaterEqThan(252)( [ @@ -83,16 +76,15 @@ template MessageValidator() { ] ); - // When all seven checks are correct, then isValid = 1. + // When all six checks are correct, then isValid = 1. var computedIsUpdateValid = IsEqual()( [ - 7, + 6, computedIsSignatureValid + computedAreVoiceCreditsSufficient + computedIsVoteWeightValid + computedIsNonceValid + computedIsStateLeafIndexValid + - computedIsTimestampValid + computedIsVoteOptionIndexValid ] ); diff --git a/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom b/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom index acf7bc421d..17886de928 100644 --- a/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom +++ b/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom @@ -25,10 +25,6 @@ template StateLeafAndBallotTransformer() { signal input slPubKey[2]; // Current voice credit balance. signal input slVoiceCreditBalance; - // Signup timestamp. - signal input slTimestamp; - // Timestamp indicating when the poll ends. - signal input pollEndTimestamp; // The following signals represents a ballot. // Nonce. @@ -83,8 +79,6 @@ template StateLeafAndBallotTransformer() { slPubKey, cmdSigR8, cmdSigS, - slTimestamp, - pollEndTimestamp, slVoiceCreditBalance, ballotCurrentVotesForOption, cmdNewVoteWeight diff --git a/packages/circuits/package.json b/packages/circuits/package.json index 2c9d3755fe..79974158f7 100644 --- a/packages/circuits/package.json +++ b/packages/circuits/package.json @@ -29,7 +29,6 @@ "test:messageToCommand": "pnpm run mocha-test ts/__tests__/MessageToCommand.test.ts", "test:messageValidator": "pnpm run mocha-test ts/__tests__/MessageValidator.test.ts", "test:verifySignature": "pnpm run mocha-test ts/__tests__/VerifySignature.test.ts", - "test:splicer": "pnpm run mocha-test ts/__tests__/Splicer.test.ts", "test:privToPubKey": "pnpm run mocha-test ts/__tests__/PrivToPubKey.test.ts", "test:calculateTotal": "pnpm run mocha-test ts/__tests__/CalculateTotal.test.ts", "test:processMessages": "pnpm run mocha-test ts/__tests__/ProcessMessages.test.ts", diff --git a/packages/circuits/ts/__tests__/CeremonyParams.test.ts b/packages/circuits/ts/__tests__/CeremonyParams.test.ts index a73d8d4bf4..a85b942352 100644 --- a/packages/circuits/ts/__tests__/CeremonyParams.test.ts +++ b/packages/circuits/ts/__tests__/CeremonyParams.test.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { type WitnessTester } from "circomkit"; -import { MaciState, Poll, STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "maci-core"; +import { MaciState, Poll, STATE_TREE_ARITY, VOTE_OPTION_TREE_ARITY, MESSAGE_BATCH_SIZE } from "maci-core"; import { hash5, IncrementalQuinTree } from "maci-crypto"; import { PrivKey, Keypair, PCommand, Message, Ballot } from "maci-domainobjs"; @@ -12,25 +12,23 @@ describe("Ceremony param tests", () => { const params = { // processMessages and Tally stateTreeDepth: 14, - // processMessages - messageTreeDepth: 9, - // processMessages - messageBatchTreeDepth: 2, // processMessages and Tally voteOptionTreeDepth: 3, // Tally - stateLeafBatchDepth: 5, + stateLeafBatchDepth: 2, + }; + + const maxValues = { + maxUsers: STATE_TREE_ARITY ** params.stateTreeDepth, + maxVoteOptions: VOTE_OPTION_TREE_ARITY ** params.voteOptionTreeDepth, }; const treeDepths = { + // can be 1 intStateTreeDepth: params.stateLeafBatchDepth, - messageTreeDepth: params.messageTreeDepth, - messageTreeSubDepth: params.messageBatchTreeDepth, voteOptionTreeDepth: params.voteOptionTreeDepth, }; - const messageBatchSize = MESSAGE_TREE_ARITY ** params.messageBatchTreeDepth; - const voiceCreditBalance = BigInt(100); const duration = 30; @@ -45,10 +43,9 @@ describe("Ceremony param tests", () => { "batchEndIndex", "index", "maxVoteOptions", - "pollEndTimestamp", - "msgRoot", + "inputBatchHash", + "outputBatchHash", "msgs", - "msgSubrootPathElements", "coordPrivKey", "coordinatorPublicKeyHash", "encPubKeys", @@ -71,7 +68,7 @@ describe("Ceremony param tests", () => { circuit = await circomkitInstance.WitnessTester("processMessages", { file: "./core/qv/processMessages", template: "ProcessMessages", - params: [14, 9, 2, 3], + params: [14, MESSAGE_BATCH_SIZE, 3], }); }); @@ -94,8 +91,9 @@ describe("Ceremony param tests", () => { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, - messageBatchSize, + MESSAGE_BATCH_SIZE, coordinatorKeypair, ); @@ -145,10 +143,7 @@ describe("Ceremony param tests", () => { it("should produce the correct state root and ballot root", async () => { // The current roots - const emptyBallot = new Ballot( - MESSAGE_TREE_ARITY ** poll.treeDepths.voteOptionTreeDepth, - poll.treeDepths.voteOptionTreeDepth, - ); + const emptyBallot = new Ballot(poll.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); const ballotTree = new IncrementalQuinTree(params.stateTreeDepth, emptyBallot.hash(), STATE_TREE_ARITY, hash5); ballotTree.insert(emptyBallot.hash()); @@ -209,7 +204,7 @@ describe("Ceremony param tests", () => { testCircuit = await circomkitInstance.WitnessTester("tallyVotes", { file: "./core/qv/tallyVotes", template: "TallyVotes", - params: [14, 5, 3], + params: [14, 1, 3], }); }); @@ -233,8 +228,9 @@ describe("Ceremony param tests", () => { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, - messageBatchSize, + MESSAGE_BATCH_SIZE, coordinatorKeypair, ); diff --git a/packages/circuits/ts/__tests__/MessageValidator.test.ts b/packages/circuits/ts/__tests__/MessageValidator.test.ts index 39797a1bc2..05e16392a8 100644 --- a/packages/circuits/ts/__tests__/MessageValidator.test.ts +++ b/packages/circuits/ts/__tests__/MessageValidator.test.ts @@ -27,8 +27,6 @@ describe("MessageValidator circuit", function test() { "currentVoiceCreditBalance", "currentVotesForOption", "voteWeight", - "slTimestamp", - "pollEndTimestamp", ], ["isValid", "isStateLeafIndexValid", "isVoteOptionIndexValid"] >; @@ -70,8 +68,6 @@ describe("MessageValidator circuit", function test() { currentVoiceCreditBalance: 100n, currentVotesForOption: 0n, voteWeight: 9n, - slTimestamp: 1n, - pollEndTimestamp: 2n, }; }); @@ -176,19 +172,6 @@ describe("MessageValidator circuit", function test() { const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); expect(isVoteOptionIndexValid.toString()).to.be.eq("0"); }); - - it("should be invalid if the state leaf timestamp is too high", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.slTimestamp = 3n; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - const isStateLeafIndexValid = await getSignal(circuit, witness, "isStateLeafIndexValid"); - expect(isStateLeafIndexValid.toString()).to.be.eq("0"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("0"); - }); }); describe("MessageValidatorNonQV", () => { @@ -209,8 +192,6 @@ describe("MessageValidator circuit", function test() { "currentVoiceCreditBalance", "currentVotesForOption", "voteWeight", - "slTimestamp", - "pollEndTimestamp", ], ["isValid", "isStateLeafIndexValid", "isVoteOptionIndexValid"] >; @@ -252,8 +233,6 @@ describe("MessageValidator circuit", function test() { currentVoiceCreditBalance: 100n, currentVotesForOption: 0n, voteWeight: 9n, - slTimestamp: 1n, - pollEndTimestamp: 2n, }; }); @@ -358,19 +337,5 @@ describe("MessageValidator circuit", function test() { const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); expect(isVoteOptionIndexValid.toString()).to.be.eq("0"); }); - - it("should be invalid if the state leaf timestamp is too high", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.slTimestamp = 3n; - - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - const isStateLeafIndexValid = await getSignal(circuit, witness, "isStateLeafIndexValid"); - expect(isStateLeafIndexValid.toString()).to.be.eq("0"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("0"); - }); }); }); diff --git a/packages/circuits/ts/__tests__/ProcessMessages.test.ts b/packages/circuits/ts/__tests__/ProcessMessages.test.ts index 05d8e77912..d95ad85799 100644 --- a/packages/circuits/ts/__tests__/ProcessMessages.test.ts +++ b/packages/circuits/ts/__tests__/ProcessMessages.test.ts @@ -6,11 +6,11 @@ import { PrivKey, Keypair, PCommand, Message, Ballot, PubKey } from "maci-domain import { IProcessMessagesInputs } from "../types"; -import { STATE_TREE_DEPTH, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; +import { STATE_TREE_DEPTH, duration, maxValues, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; import { circomkitInstance } from "./utils/utils"; describe("ProcessMessage circuit", function test() { - this.timeout(900000); + this.timeout(9000000); const coordinatorKeypair = new Keypair(); @@ -19,10 +19,9 @@ describe("ProcessMessage circuit", function test() { "batchEndIndex", "index", "maxVoteOptions", - "pollEndTimestamp", - "msgRoot", + "inputBatchHash", + "outputBatchHash", "msgs", - "msgSubrootPathElements", "coordPrivKey", "coordinatorPublicKeyHash", "encPubKeys", @@ -48,17 +47,17 @@ describe("ProcessMessage circuit", function test() { circuit = await circomkitInstance.WitnessTester("processMessages", { file: "./core/qv/processMessages", template: "ProcessMessages", - params: [10, 2, 1, 2], + params: [10, 20, 2], }); circuitNonQv = await circomkitInstance.WitnessTester("processMessagesNonQv", { file: "./core/non-qv/processMessages", template: "ProcessMessagesNonQv", - params: [10, 2, 1, 2], + params: [10, 20, 2], }); }); - describe("5 users, 1 messages", () => { + describe("1) 5 users, 1 messages", () => { const maciState = new MaciState(STATE_TREE_DEPTH); const voteWeight = BigInt(9); const voteOptionIndex = BigInt(1); @@ -77,6 +76,7 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -135,7 +135,7 @@ describe("ProcessMessage circuit", function test() { }); }); - describe("1 user, 2 messages (non-quadratic voting)", () => { + describe("2) 1 user, 2 messages (non-quadratic voting)", () => { const maciState = new MaciState(STATE_TREE_DEPTH); const voteWeight = BigInt(9); const voteOptionIndex = BigInt(0); @@ -154,6 +154,7 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -203,10 +204,7 @@ describe("ProcessMessage circuit", function test() { it("should produce the correct state root and ballot root", async () => { // The current roots - const emptyBallot = new Ballot( - MESSAGE_TREE_ARITY ** poll.treeDepths.voteOptionTreeDepth, - poll.treeDepths.voteOptionTreeDepth, - ); + const emptyBallot = new Ballot(poll.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash2); @@ -235,7 +233,7 @@ describe("ProcessMessage circuit", function test() { }); }); - describe("2 users, 1 message", () => { + describe("3) 2 users, 1 message", () => { const maciState = new MaciState(STATE_TREE_DEPTH); let pollId: bigint; let poll: Poll; @@ -260,6 +258,7 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(2 + duration), // BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -291,10 +290,7 @@ describe("ProcessMessage circuit", function test() { it("should produce the correct state root and ballot root", async () => { // The current roots - const emptyBallot = new Ballot( - MESSAGE_TREE_ARITY ** poll.treeDepths.voteOptionTreeDepth, - poll.treeDepths.voteOptionTreeDepth, - ); + const emptyBallot = new Ballot(poll.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash2); @@ -322,7 +318,7 @@ describe("ProcessMessage circuit", function test() { }); }); - describe("1 user, key-change", () => { + describe("4) 1 user, key-change", () => { const maciState = new MaciState(STATE_TREE_DEPTH); const voteWeight = BigInt(9); let stateIndex: number; @@ -331,8 +327,6 @@ describe("ProcessMessage circuit", function test() { const messages: Message[] = []; const commands: PCommand[] = []; - const NUM_BATCHES = 2; - before(() => { // Sign up and publish const userKeypair = new Keypair(new PrivKey(BigInt(123))); @@ -346,6 +340,7 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(2 + duration), // BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -413,56 +408,93 @@ describe("ProcessMessage circuit", function test() { poll.publishMessage(message3, ecdhKeypair3.pubKey); }); - describe(`1 user, ${messageBatchSize * NUM_BATCHES} messages`, () => { - it("should produce the correct state root and ballot root", async () => { - const state = new MaciState(STATE_TREE_DEPTH); - const userKeypair = new Keypair(); - const index = state.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); - - // Sign up and publish - const id = state.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); + it("should produce the correct state root and ballot root", async () => { + // The current roots + const emptyBallot = new Ballot(poll.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); + const emptyBallotHash = emptyBallot.hash(); + const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash2); - const selectedPoll = state.polls.get(id); - - selectedPoll?.updatePoll(BigInt(state.stateLeaves.length)); - - // Second batch is not a full batch - const numMessages = messageBatchSize * NUM_BATCHES - 1; - for (let i = 0; i < numMessages; i += 1) { - const command = new PCommand( - BigInt(index), - userKeypair.pubKey, - BigInt(i), // vote option index - BigInt(1), // vote weight - BigInt(numMessages - i), // nonce - BigInt(id), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - selectedPoll?.publishMessage(message, ecdhKeypair.pubKey); - } - - for (let i = 0; i < 2; i += 1) { - const inputs = selectedPoll?.processMessages(id) as unknown as IProcessMessagesInputs; - // eslint-disable-next-line no-await-in-loop - const witness = await circuit.calculateWitness(inputs); - // eslint-disable-next-line no-await-in-loop - await circuit.expectConstraintPass(witness); - } + ballotTree.insert(emptyBallot.hash()); + + poll.stateLeaves.forEach(() => { + ballotTree.insert(emptyBallotHash); }); + + const currentStateRoot = poll.stateTree?.root; + const currentBallotRoot = ballotTree.root; + + const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; + // Calculate the witness + const witness = await circuit.calculateWitness(inputs); + await circuit.expectConstraintPass(witness); + + // The new roots, which should differ, since at least one of the + // messages modified a Ballot or State Leaf + const newStateRoot = poll.stateTree?.root; + const newBallotRoot = poll.ballotTree?.root; + + expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); + expect(newBallotRoot?.toString()).not.to.be.eq(currentBallotRoot.toString()); }); }); - describe("1 user, 2 messages", () => { + const NUM_BATCHES = 2; + describe(`5) 1 user, ${messageBatchSize * NUM_BATCHES - 1} messages`, () => { + const maciState = new MaciState(STATE_TREE_DEPTH); + let stateIndex: number; + let pollId: bigint; + let poll: Poll; + + before(() => { + const userKeypair = new Keypair(new PrivKey(BigInt(1))); + stateIndex = maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); + + // Sign up and publish + pollId = maciState.deployPoll( + BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, + treeDepths, + messageBatchSize, + coordinatorKeypair, + ); + + poll = maciState.polls.get(pollId)!; + + poll.updatePoll(BigInt(maciState.stateLeaves.length)); + + // Second batch is not a full batch + const numMessages = messageBatchSize * NUM_BATCHES - 1; + for (let i = 0; i < numMessages; i += 1) { + const command = new PCommand( + BigInt(stateIndex), + userKeypair.pubKey, + BigInt(i), // vote option index + BigInt(1), // vote weight + BigInt(numMessages - i), // nonce + BigInt(pollId), + ); + + const signature = command.sign(userKeypair.privKey); + + const ecdhKeypair = new Keypair(); + const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); + const message = command.encrypt(signature, sharedKey); + poll.publishMessage(message, ecdhKeypair.pubKey); + } + }); + + it("should produce a proof", async () => { + for (let i = 0; i < NUM_BATCHES; i += 1) { + const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; + // eslint-disable-next-line no-await-in-loop + const witness = await circuit.calculateWitness(inputs); + // eslint-disable-next-line no-await-in-loop + await circuit.expectConstraintPass(witness); + } + }); + }); + + describe("6) 1 user, 2 messages", () => { const maciState = new MaciState(STATE_TREE_DEPTH); const voteOptionIndex = 1n; let stateIndex: bigint; @@ -480,6 +512,7 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -549,10 +582,7 @@ describe("ProcessMessage circuit", function test() { it("should produce the correct state root and ballot root", async () => { // The current roots - const emptyBallot = new Ballot( - MESSAGE_TREE_ARITY ** poll.treeDepths.voteOptionTreeDepth, - poll.treeDepths.voteOptionTreeDepth, - ); + const emptyBallot = new Ballot(poll.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash2); @@ -581,7 +611,7 @@ describe("ProcessMessage circuit", function test() { }); }); - describe("1 user, 2 messages in different batches", () => { + describe("7) 1 user, 2 messages in different batches", () => { const maciState = new MaciState(STATE_TREE_DEPTH); const voteOptionIndex = 1n; let stateIndex: bigint; @@ -599,6 +629,7 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -673,10 +704,7 @@ describe("ProcessMessage circuit", function test() { it("should produce the correct state root and ballot root", async () => { // The current roots - const emptyBallot = new Ballot( - MESSAGE_TREE_ARITY ** poll.treeDepths.voteOptionTreeDepth, - poll.treeDepths.voteOptionTreeDepth, - ); + const emptyBallot = new Ballot(poll.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash2); @@ -708,7 +736,7 @@ describe("ProcessMessage circuit", function test() { }); }); - describe("1 user, 3 messages in different batches", () => { + describe("8) 1 user, 3 messages in different batches", () => { const maciState = new MaciState(STATE_TREE_DEPTH); const voteOptionIndex = 1n; let stateIndex: bigint; @@ -726,6 +754,7 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -819,10 +848,7 @@ describe("ProcessMessage circuit", function test() { it("should produce the correct state root and ballot root", async () => { // The current roots - const emptyBallot = new Ballot( - MESSAGE_TREE_ARITY ** poll.treeDepths.voteOptionTreeDepth, - poll.treeDepths.voteOptionTreeDepth, - ); + const emptyBallot = new Ballot(poll.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); const emptyBallotHash = emptyBallot.hash(); const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash2); diff --git a/packages/circuits/ts/__tests__/StateLeafAndBallotTransformer.test.ts b/packages/circuits/ts/__tests__/StateLeafAndBallotTransformer.test.ts index cdfefbfbf5..ef43d5e76a 100644 --- a/packages/circuits/ts/__tests__/StateLeafAndBallotTransformer.test.ts +++ b/packages/circuits/ts/__tests__/StateLeafAndBallotTransformer.test.ts @@ -26,8 +26,6 @@ describe("StateLeafAndBallotTransformer circuit", function test() { const slVoiceCreditBalance = BigInt(100); const ballotNonce = BigInt(0); const ballotCurrentVotesForOption = BigInt(0); - const slTimestamp = 1n; - const pollEndTimestamp = 2n; const command: PCommand = new PCommand(stateIndex, newPubKey, voteOptionIndex, newVoteWeight, nonce, pollId, salt); @@ -39,8 +37,6 @@ describe("StateLeafAndBallotTransformer circuit", function test() { "maxVoteOptions", "slPubKey", "slVoiceCreditBalance", - "slTimestamp", - "pollEndTimestamp", "ballotNonce", "ballotCurrentVotesForOption", "cmdStateIndex", @@ -63,8 +59,6 @@ describe("StateLeafAndBallotTransformer circuit", function test() { "maxVoteOptions", "slPubKey", "slVoiceCreditBalance", - "slTimestamp", - "pollEndTimestamp", "ballotNonce", "ballotCurrentVotesForOption", "cmdStateIndex", @@ -99,8 +93,6 @@ describe("StateLeafAndBallotTransformer circuit", function test() { maxVoteOptions, slPubKey: slPubKey.asCircuitInputs() as unknown as [bigint, bigint], slVoiceCreditBalance, - slTimestamp, - pollEndTimestamp, ballotNonce, ballotCurrentVotesForOption, cmdStateIndex: command.stateIndex, @@ -136,8 +128,6 @@ describe("StateLeafAndBallotTransformer circuit", function test() { maxVoteOptions, slPubKey: slPubKey.asCircuitInputs() as unknown as [bigint, bigint], slVoiceCreditBalance, - slTimestamp, - pollEndTimestamp, ballotNonce, ballotCurrentVotesForOption, cmdStateIndex: command.stateIndex, @@ -173,8 +163,6 @@ describe("StateLeafAndBallotTransformer circuit", function test() { maxVoteOptions, slPubKey: slPubKey.asCircuitInputs() as unknown as [bigint, bigint], slVoiceCreditBalance, - slTimestamp, - pollEndTimestamp, ballotNonce, ballotCurrentVotesForOption, cmdStateIndex: command.stateIndex, @@ -210,8 +198,6 @@ describe("StateLeafAndBallotTransformer circuit", function test() { maxVoteOptions, slPubKey: slPubKey.asCircuitInputs() as unknown as [bigint, bigint], slVoiceCreditBalance, - slTimestamp, - pollEndTimestamp, ballotNonce, ballotCurrentVotesForOption, cmdStateIndex: command.stateIndex, diff --git a/packages/circuits/ts/__tests__/TallyVotes.test.ts b/packages/circuits/ts/__tests__/TallyVotes.test.ts index 6d2568b39a..1ee38363ac 100644 --- a/packages/circuits/ts/__tests__/TallyVotes.test.ts +++ b/packages/circuits/ts/__tests__/TallyVotes.test.ts @@ -4,7 +4,7 @@ import { Keypair, PCommand, Message } from "maci-domainobjs"; import { ITallyVotesInputs } from "../types"; -import { STATE_TREE_DEPTH, duration, messageBatchSize, voiceCreditBalance } from "./utils/constants"; +import { STATE_TREE_DEPTH, duration, maxValues, messageBatchSize, voiceCreditBalance } from "./utils/constants"; import { generateRandomIndex, circomkitInstance } from "./utils/utils"; describe("TallyVotes circuit", function test() { @@ -12,8 +12,6 @@ describe("TallyVotes circuit", function test() { const treeDepths = { intStateTreeDepth: 1, - messageTreeDepth: 2, - messageTreeSubDepth: 1, voteOptionTreeDepth: 2, }; @@ -80,6 +78,7 @@ describe("TallyVotes circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -153,6 +152,7 @@ describe("TallyVotes circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -222,6 +222,7 @@ describe("TallyVotes circuit", function test() { const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, diff --git a/packages/circuits/ts/__tests__/utils/constants.ts b/packages/circuits/ts/__tests__/utils/constants.ts index 815aa371b9..e6a4574763 100644 --- a/packages/circuits/ts/__tests__/utils/constants.ts +++ b/packages/circuits/ts/__tests__/utils/constants.ts @@ -1,12 +1,17 @@ export const STATE_TREE_DEPTH = 10; export const voiceCreditBalance = BigInt(100); export const duration = 30; + +export const maxValues = { + maxUsers: 25, + maxVoteOptions: 25, +}; + export const treeDepths = { intStateTreeDepth: 5, - messageTreeDepth: 2, - messageTreeSubDepth: 1, voteOptionTreeDepth: 2, }; -export const messageBatchSize = 5; + +export const messageBatchSize = 20; export const L = 2736030358979909402780800718157159386076813972158567259200215660948447373041n; diff --git a/packages/circuits/ts/__tests__/utils/types.ts b/packages/circuits/ts/__tests__/utils/types.ts index 934108a0e9..776078a927 100644 --- a/packages/circuits/ts/__tests__/utils/types.ts +++ b/packages/circuits/ts/__tests__/utils/types.ts @@ -21,6 +21,4 @@ export interface IMessageValidatorCircuitInputs { currentVoiceCreditBalance: SignalValueType; currentVotesForOption: SignalValueType; voteWeight: SignalValueType; - slTimestamp: SignalValueType; - pollEndTimestamp: SignalValueType; } diff --git a/packages/circuits/ts/types.ts b/packages/circuits/ts/types.ts index 78dd1a54ce..de265390eb 100644 --- a/packages/circuits/ts/types.ts +++ b/packages/circuits/ts/types.ts @@ -50,9 +50,9 @@ export interface IProcessMessagesInputs { batchEndIndex: bigint; index: bigint; maxVoteOptions: bigint; - msgRoot: bigint; + inputBatchHash: bigint; + outputBatchHash: bigint; msgs: bigint[]; - msgSubrootPathElements: bigint[][]; coordPrivKey: bigint; coordinatorPublicKeyHash: bigint; encPubKeys: bigint[]; diff --git a/packages/cli/testScript.sh b/packages/cli/testScript.sh index d8bfc6b372..73a20e2c3b 100755 --- a/packages/cli/testScript.sh +++ b/packages/cli/testScript.sh @@ -8,7 +8,7 @@ node build/ts/index.js setVerifyingKeys \ --msg-tree-depth 2 \ --vote-option-tree-depth 2 \ --msg-batch-depth 1 \ - --process-messages-zkey-qv ./zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey \ + --process-messages-zkey-qv ./zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey \ --tally-votes-zkey-qv ./zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey node build/ts/index.js create -s 10 node build/ts/index.js deployPoll \ @@ -39,16 +39,15 @@ node build/ts/index.js publish \ --poll-id 0 node build/ts/index.js timeTravel -s 100 node build/ts/index.js mergeSignups --poll-id 0 -node build/ts/index.js mergeMessages --poll-id 0 node build/ts/index.js genProofs \ --privkey macisk.bf92af7614b07e2ba19dce65bb7fef2b93d83b84da2cf2e3af690104fbc52511 \ --poll-id 0 \ - --process-zkey ./zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey \ + --process-zkey ./zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey \ --tally-zkey ./zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey \ --tally-file tally.json \ --output proofs/ \ -tw ./zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm \ - -pw ./zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm \ + -pw ./zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm \ -w true \ -q false node build/ts/index.js proveOnChain \ diff --git a/packages/cli/tests/ceremony-params/ceremonyParams.test.ts b/packages/cli/tests/ceremony-params/ceremonyParams.test.ts index d1a590ba84..c5398cdd87 100644 --- a/packages/cli/tests/ceremony-params/ceremonyParams.test.ts +++ b/packages/cli/tests/ceremony-params/ceremonyParams.test.ts @@ -9,7 +9,6 @@ import { deployPoll, deployVkRegistryContract, genProofs, - mergeMessages, mergeSignups, proveOnChain, publish, @@ -31,7 +30,6 @@ import { testTallyFilePath, ceremonyTallyVotesWasmPath, ceremonyTallyVotesWitnessPath, - mergeMessagesArgs, mergeSignupsArgs, proveOnChainArgs, verifyArgs, @@ -48,12 +46,11 @@ import { } from "../constants"; import { clean, isArm } from "../utils"; -describe("Stress tests with ceremony params (14,9,2,3)", function test() { - const messageTreeDepth = 9; - const stateTreeDepth = 14; +describe("Stress tests with ceremony params (6,3,2,20)", function test() { + const stateTreeDepth = 6; const voteOptionTreeDepth = 3; - const messageBatchDepth = 2; - const intStateTreeDepth = 5; + const intStateTreeDepth = 2; + const messageBatchSize = 20; const pollDuration = 60000; @@ -67,9 +64,8 @@ describe("Stress tests with ceremony params (14,9,2,3)", function test() { quiet: true, stateTreeDepth, intStateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, - messageBatchDepth, + messageBatchSize, processMessagesZkeyPathQv: ceremonyProcessMessagesZkeyPath, tallyVotesZkeyPathQv: ceremonyTallyVotesZkeyPath, processMessagesZkeyPathNonQv: ceremonyProcessMessagesNonQvZkeyPath, @@ -84,8 +80,7 @@ describe("Stress tests with ceremony params (14,9,2,3)", function test() { const deployPollArgs: Omit = { pollDuration, intStateTreeDepth, - messageTreeSubDepth: messageBatchDepth, - messageTreeDepth, + messageBatchSize, voteOptionTreeDepth, coordinatorPubkey: coordinatorPubKey, useQuadraticVoting: true, @@ -161,7 +156,6 @@ describe("Stress tests with ceremony params (14,9,2,3)", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ seconds: pollDuration, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genProofs({ ...genProofsCeremonyArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -212,7 +206,6 @@ describe("Stress tests with ceremony params (14,9,2,3)", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ seconds: pollDuration, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genProofs({ ...genProofsCeremonyArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -283,7 +276,6 @@ describe("Stress tests with ceremony params (14,9,2,3)", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ seconds: pollDuration, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer, useQuadraticVoting: false }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -339,7 +331,6 @@ describe("Stress tests with ceremony params (14,9,2,3)", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ seconds: pollDuration, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer, useQuadraticVoting: false }); await proveOnChain({ ...proveOnChainArgs, signer }); diff --git a/packages/cli/tests/constants.ts b/packages/cli/tests/constants.ts index a0100fbe0a..acd1a29b1e 100644 --- a/packages/cli/tests/constants.ts +++ b/packages/cli/tests/constants.ts @@ -6,7 +6,6 @@ import { CheckVerifyingKeysArgs, DeployArgs, DeployPollArgs, - MergeMessagesArgs, MergeSignupsArgs, ProveOnChainArgs, SetVerifyingKeysArgs, @@ -18,29 +17,28 @@ import { export const STATE_TREE_DEPTH = 10; export const INT_STATE_TREE_DEPTH = 1; -export const MSG_TREE_DEPTH = 2; export const VOTE_OPTION_TREE_DEPTH = 2; -export const MSG_BATCH_DEPTH = 1; +export const MESSAGE_BATCH_SIZE = 20; const coordinatorKeypair = new Keypair(); export const coordinatorPubKey = coordinatorKeypair.pubKey.serialize(); export const coordinatorPrivKey = coordinatorKeypair.privKey.serialize(); -export const processMessageTestZkeyPath = "./zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey"; +export const processMessageTestZkeyPath = "./zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey"; export const tallyVotesTestZkeyPath = "./zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey"; export const processMessageTestNonQvZkeyPath = - "./zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey"; + "./zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey"; export const tallyVotesTestNonQvZkeyPath = "./zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey"; export const testTallyFilePath = "./tally.json"; export const testProofsDirPath = "./proofs"; export const testProcessMessagesWitnessPath = - "./zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_cpp/ProcessMessages_10-2-1-2_test"; + "./zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_cpp/ProcessMessages_10-20-2_test"; export const testProcessMessagesWitnessDatPath = - "./zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_cpp/ProcessMessages_10-2-1-2_test.dat"; + "./zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_cpp/ProcessMessages_10-20-2_test.dat"; export const testTallyVotesWitnessPath = "./zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_cpp/TallyVotes_10-1-2_test"; export const testTallyVotesWitnessDatPath = "./zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_cpp/TallyVotes_10-1-2_test.dat"; export const testProcessMessagesWasmPath = - "./zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm"; + "./zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm"; export const testTallyVotesWasmPath = "./zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm"; export const testRapidsnarkPath = `${homedir()}/rapidsnark/build/prover`; @@ -71,15 +69,15 @@ export const ceremonyTallyVotesWasmPath = "./zkeys/TallyVotes_14-5-3/TallyVotes_ export const ceremonyTallyVotesNonQvWasmPath = "./zkeys/TallyVotesNonQv_14-5-3/TallyVotesNonQv_14-5-3_js/TallyVotesNonQv_14-5-3.wasm"; export const testProcessMessagesNonQvWitnessPath = - "./zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_cpp/ProcessMessagesNonQv_10-2-1-2_test"; + "./zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_cpp/ProcessMessagesNonQv_10-20-2_test"; export const testProcessMessagesNonQvWitnessDatPath = - "./zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_cpp/ProcessMessagesNonQv_10-2-1-2_test.dat"; + "./zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_cpp/ProcessMessagesNonQv_10-20-2_test.dat"; export const testTallyVotesNonQvWitnessPath = "./zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_cpp/TallyVotesNonQv_10-1-2_test"; export const testTallyVotesNonQvWitnessDatPath = "./zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_cpp/TallyVotesNonQv_10-1-2_test.dat"; export const testProcessMessagesNonQvWasmPath = - "./zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm"; + "./zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm"; export const testTallyVotesNonQvWasmPath = "./zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm"; @@ -89,9 +87,8 @@ export const setVerifyingKeysArgs: Omit = { quiet: true, stateTreeDepth: STATE_TREE_DEPTH, intStateTreeDepth: INT_STATE_TREE_DEPTH, - messageTreeDepth: MSG_TREE_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, - messageBatchDepth: MSG_BATCH_DEPTH, + messageBatchSize: MESSAGE_BATCH_SIZE, processMessagesZkeyPathQv: processMessageTestZkeyPath, tallyVotesZkeyPathQv: tallyVotesTestZkeyPath, }; @@ -100,9 +97,8 @@ export const setVerifyingKeysNonQvArgs: Omit = { quiet: true, stateTreeDepth: STATE_TREE_DEPTH, intStateTreeDepth: INT_STATE_TREE_DEPTH, - messageTreeDepth: MSG_TREE_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, - messageBatchDepth: MSG_BATCH_DEPTH, + messageBatchSize: MESSAGE_BATCH_SIZE, processMessagesZkeyPathNonQv: processMessageTestNonQvZkeyPath, tallyVotesZkeyPathNonQv: tallyVotesTestNonQvZkeyPath, }; @@ -110,9 +106,8 @@ export const setVerifyingKeysNonQvArgs: Omit = { export const checkVerifyingKeysArgs: Omit = { stateTreeDepth: STATE_TREE_DEPTH, intStateTreeDepth: INT_STATE_TREE_DEPTH, - messageTreeDepth: MSG_TREE_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, - messageBatchDepth: MSG_BATCH_DEPTH, + messageBatchSize: MESSAGE_BATCH_SIZE, processMessagesZkeyPath: processMessageTestZkeyPath, tallyVotesZkeyPath: tallyVotesTestZkeyPath, }; @@ -121,10 +116,6 @@ export const timeTravelArgs: Omit = { seconds: pollDuration, }; -export const mergeMessagesArgs: Omit = { - pollId: 0n, -}; - export const mergeSignupsArgs: Omit = { pollId: 0n, }; @@ -152,8 +143,7 @@ export const deployArgs: Omit = { export const deployPollArgs: Omit = { pollDuration, intStateTreeDepth: INT_STATE_TREE_DEPTH, - messageTreeSubDepth: MSG_BATCH_DEPTH, - messageTreeDepth: MSG_TREE_DEPTH, + messageBatchSize: MESSAGE_BATCH_SIZE, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, coordinatorPubkey: coordinatorPubKey, useQuadraticVoting: true, diff --git a/packages/cli/tests/e2e/e2e.nonQv.test.ts b/packages/cli/tests/e2e/e2e.nonQv.test.ts index 63a14fa32e..3335bd99b9 100644 --- a/packages/cli/tests/e2e/e2e.nonQv.test.ts +++ b/packages/cli/tests/e2e/e2e.nonQv.test.ts @@ -9,7 +9,6 @@ import { deployPoll, deployVkRegistryContract, genProofs, - mergeMessages, mergeSignups, proveOnChain, publish, @@ -25,7 +24,6 @@ import { pollDuration, proveOnChainArgs, verifyArgs, - mergeMessagesArgs, mergeSignupsArgs, testProofsDirPath, testRapidsnarkPath, @@ -117,7 +115,6 @@ describe("e2e tests with non quadratic voting", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ seconds: pollDuration, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer, useQuadraticVoting: false }); await proveOnChain({ ...proveOnChainArgs, signer }); diff --git a/packages/cli/tests/e2e/e2e.test.ts b/packages/cli/tests/e2e/e2e.test.ts index 8d9e690275..47a91099a4 100644 --- a/packages/cli/tests/e2e/e2e.test.ts +++ b/packages/cli/tests/e2e/e2e.test.ts @@ -14,7 +14,6 @@ import { deployVkRegistryContract, genLocalState, genProofs, - mergeMessages, mergeSignups, proveOnChain, publish, @@ -33,7 +32,6 @@ import { pollDuration, proveOnChainArgs, verifyArgs, - mergeMessagesArgs, mergeSignupsArgs, processMessageTestZkeyPath, setVerifyingKeysArgs, @@ -138,7 +136,6 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ seconds: pollDuration, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -185,7 +182,6 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer }); await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }); @@ -322,7 +318,6 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -379,7 +374,6 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -428,7 +422,6 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -515,7 +508,6 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -563,7 +555,6 @@ describe("e2e tests", function test() { // time travel await timeTravel({ ...timeTravelArgs, signer }); // generate proofs - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -592,7 +583,6 @@ describe("e2e tests", function test() { it("should generate proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ pollId: 1n, signer }); await mergeSignups({ pollId: 1n, signer }); await genProofs({ ...genProofsArgs, pollId: 1n, signer }); await proveOnChain({ ...proveOnChainArgs, pollId: 1n, signer }); @@ -633,7 +623,6 @@ describe("e2e tests", function test() { // time travel await timeTravel({ ...timeTravelArgs, signer }); // generate proofs - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -684,7 +673,6 @@ describe("e2e tests", function test() { it("should generate proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ pollId: 1n, signer }); await mergeSignups({ pollId: 1n, signer }); await genProofs({ ...genProofsArgs, pollId: 1n, signer }); await proveOnChain({ ...proveOnChainArgs, pollId: 1n, signer }); @@ -750,7 +738,6 @@ describe("e2e tests", function test() { // time travel await timeTravel({ ...timeTravelArgs, signer }); // generate proofs - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -848,7 +835,6 @@ describe("e2e tests", function test() { it("should complete the second poll", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ pollId: 1n, signer }); await mergeSignups({ pollId: 1n, signer }); const tallyData = await genProofs({ ...genProofsArgs, pollId: 1n, signer }); await proveOnChain({ @@ -868,7 +854,6 @@ describe("e2e tests", function test() { }); it("should complete the third poll", async () => { - await mergeMessages({ pollId: 2n, signer }); await mergeSignups({ pollId: 2n, signer }); const tallyData = await genProofs({ ...genProofsArgs, pollId: 2n, signer }); await proveOnChain({ @@ -928,7 +913,6 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genLocalState({ outputPath: stateOutPath, diff --git a/packages/cli/tests/e2e/keyChange.test.ts b/packages/cli/tests/e2e/keyChange.test.ts index bb96f59fb0..729e3dc6fd 100644 --- a/packages/cli/tests/e2e/keyChange.test.ts +++ b/packages/cli/tests/e2e/keyChange.test.ts @@ -13,7 +13,6 @@ import { deployPoll, deployVkRegistryContract, genProofs, - mergeMessages, mergeSignups, proveOnChain, publish, @@ -28,7 +27,6 @@ import { deployArgs, deployPollArgs, processMessageTestZkeyPath, - mergeMessagesArgs, mergeSignupsArgs, setVerifyingKeysArgs, tallyVotesTestZkeyPath, @@ -139,7 +137,6 @@ describe("keyChange tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -211,7 +208,6 @@ describe("keyChange tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); @@ -283,7 +279,6 @@ describe("keyChange tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeMessages({ ...mergeMessagesArgs, signer }); await mergeSignups({ ...mergeSignupsArgs, signer }); await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); diff --git a/packages/cli/tests/unit/poll.test.ts b/packages/cli/tests/unit/poll.test.ts index 0e8c560af2..bf5fc70d91 100644 --- a/packages/cli/tests/unit/poll.test.ts +++ b/packages/cli/tests/unit/poll.test.ts @@ -10,7 +10,6 @@ import { setVerifyingKeys, getPoll, timeTravel, - mergeMessages, mergeSignups, } from "../../ts/commands"; import { DeployedContracts, PollContracts } from "../../ts/utils"; @@ -56,7 +55,6 @@ describe("poll", () => { const pollData = await getPoll({ maciAddress: maciAddresses.maciAddress, provider: signer.provider! }); await timeTravel({ seconds: Number(pollData.duration), signer }); - await mergeMessages({ pollId: BigInt(pollData.id), signer }); await mergeSignups({ pollId: BigInt(pollData.id), signer }); const finishedPollData = await getPoll({ maciAddress: maciAddresses.maciAddress, signer }); diff --git a/packages/cli/ts/commands/checkVerifyingKeys.ts b/packages/cli/ts/commands/checkVerifyingKeys.ts index 856f74368d..bb42ccca8a 100644 --- a/packages/cli/ts/commands/checkVerifyingKeys.ts +++ b/packages/cli/ts/commands/checkVerifyingKeys.ts @@ -27,9 +27,8 @@ import { export const checkVerifyingKeys = async ({ stateTreeDepth, intStateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, - messageBatchDepth, + messageBatchSize, processMessagesZkeyPath, tallyVotesZkeyPath, vkRegistry, @@ -73,11 +72,9 @@ export const checkVerifyingKeys = async ({ try { logYellow(quiet, info("Retrieving verifying keys from the contract...")); // retrieve the verifying keys from the contract - const messageBatchSize = 5 ** messageBatchDepth; const processVkOnChain = await vkRegistryContractInstance.getProcessVk( stateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, messageBatchSize, useQuadraticVoting ? EMode.QV : EMode.NON_QV, diff --git a/packages/cli/ts/commands/deploy.ts b/packages/cli/ts/commands/deploy.ts index 7efb996307..1ae2983b3e 100644 --- a/packages/cli/ts/commands/deploy.ts +++ b/packages/cli/ts/commands/deploy.ts @@ -75,7 +75,7 @@ export const deploy = async ({ const verifierContractAddress = await verifierContract.getAddress(); - // deploy MACI, stateAq, PollFactory and poseidon + // deploy MACI, PollFactory and poseidon const { maciContract, pollFactoryContract, poseidonAddrs } = await deployMaci({ signUpTokenGatekeeperContractAddress: signupGatekeeperContractAddress, initialVoiceCreditBalanceAddress: initialVoiceCreditProxyContractAddress, diff --git a/packages/cli/ts/commands/deployPoll.ts b/packages/cli/ts/commands/deployPoll.ts index 9ef0a17093..bff6d257f2 100644 --- a/packages/cli/ts/commands/deployPoll.ts +++ b/packages/cli/ts/commands/deployPoll.ts @@ -21,8 +21,7 @@ import { export const deployPoll = async ({ pollDuration, intStateTreeDepth, - messageTreeSubDepth, - messageTreeDepth, + messageBatchSize, voteOptionTreeDepth, coordinatorPubkey, maciAddress, @@ -59,13 +58,10 @@ export const deployPoll = async ({ if (intStateTreeDepth <= 0) { logError("Int state tree depth cannot be <= 0"); } - // required arg -> message tree sub depth - if (messageTreeSubDepth <= 0) { - logError("Message tree sub depth cannot be <= 0"); - } + // required arg -> message tree depth - if (messageTreeDepth <= 0) { - logError("Message tree depth cannot be <= 0"); + if (messageBatchSize <= 0) { + logError("Message batch size cannot be <= 0"); } // required arg -> vote option tree depth if (voteOptionTreeDepth <= 0) { @@ -100,10 +96,9 @@ export const deployPoll = async ({ pollDuration, { intStateTreeDepth, - messageTreeSubDepth, - messageTreeDepth, voteOptionTreeDepth, }, + messageBatchSize, unserializedKey.asContractParam(), verifierContractAddress, vkRegistry, diff --git a/packages/cli/ts/commands/genLocalState.ts b/packages/cli/ts/commands/genLocalState.ts index 2c5e5bdae6..219f234087 100644 --- a/packages/cli/ts/commands/genLocalState.ts +++ b/packages/cli/ts/commands/genLocalState.ts @@ -1,10 +1,5 @@ import { JsonRpcProvider } from "ethers"; -import { - MACI__factory as MACIFactory, - AccQueue__factory as AccQueueFactory, - Poll__factory as PollFactory, - genMaciStateFromContract, -} from "maci-contracts"; +import { MACI__factory as MACIFactory, Poll__factory as PollFactory, genMaciStateFromContract } from "maci-contracts"; import { Keypair, PrivKey } from "maci-domainobjs"; import fs from "fs"; @@ -72,28 +67,18 @@ export const genLocalState = async ({ } const pollContract = PollFactory.connect(pollContracts.poll, signer); - const [{ messageAq }, { messageTreeDepth }] = await Promise.all([ - pollContract.extContracts(), - pollContract.treeDepths(), - ]); - const messageAqContract = AccQueueFactory.connect(messageAq, signer); - - const [defaultStartBlockSignup, defaultStartBlockPoll, stateRoot, numSignups, messageRoot] = await Promise.all([ + const [defaultStartBlockSignup, defaultStartBlockPoll, stateRoot, numSignups] = await Promise.all([ maciContract.queryFilter(maciContract.filters.SignUp(), startBlock).then((events) => events[0]?.blockNumber ?? 0), maciContract .queryFilter(maciContract.filters.DeployPoll(), startBlock) .then((events) => events[0]?.blockNumber ?? 0), maciContract.getStateTreeRoot(), maciContract.numSignUps(), - messageAqContract.getMainRoot(messageTreeDepth), ]); const defaultStartBlock = Math.min(defaultStartBlockPoll, defaultStartBlockSignup); let fromBlock = startBlock ? Number(startBlock) : defaultStartBlock; const defaultEndBlock = await Promise.all([ - pollContract - .queryFilter(pollContract.filters.MergeMessageAq(messageRoot), fromBlock) - .then((events) => events[events.length - 1]?.blockNumber), pollContract .queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock) .then((events) => events[events.length - 1]?.blockNumber), diff --git a/packages/cli/ts/commands/genProofs.ts b/packages/cli/ts/commands/genProofs.ts index 8fa29413be..400a66f43d 100644 --- a/packages/cli/ts/commands/genProofs.ts +++ b/packages/cli/ts/commands/genProofs.ts @@ -1,10 +1,5 @@ import { extractVk, genProof, verifyProof } from "maci-circuits"; -import { - MACI__factory as MACIFactory, - AccQueue__factory as AccQueueFactory, - Poll__factory as PollFactory, - genMaciStateFromContract, -} from "maci-contracts"; +import { MACI__factory as MACIFactory, Poll__factory as PollFactory, genMaciStateFromContract } from "maci-contracts"; import { type CircuitInputs, type IJsonMaciState, MaciState } from "maci-core"; import { hash3, hashLeftRight, genTreeCommitment } from "maci-crypto"; import { Keypair, PrivKey } from "maci-domainobjs"; @@ -153,23 +148,11 @@ export const genProofs = async ({ } const pollContract = PollFactory.connect(pollContracts.poll, signer); - const extContracts = await pollContract.extContracts(); - const messageAqContractAddr = extContracts.messageAq; - const messageAqContract = AccQueueFactory.connect(messageAqContractAddr, signer); - // Check that the state and message trees have been merged if (!(await pollContract.stateMerged())) { logError("The state tree has not been merged yet. Please use the mergeSignups subcommand to do so."); } - const messageTreeDepth = Number((await pollContract.treeDepths()).messageTreeDepth); - - // check that the main root is set - const mainRoot = (await messageAqContract.getMainRoot(messageTreeDepth.toString())).toString(); - if (mainRoot === "0") { - logError("The message tree has not been merged yet. Please use the mergeMessages subcommand to do so."); - } - let maciState: MaciState | undefined; if (stateFile) { const content = JSON.parse( @@ -188,22 +171,18 @@ export const genProofs = async ({ } } else { // build an off-chain representation of the MACI contract using data in the contract storage - const [defaultStartBlockSignup, defaultStartBlockPoll, stateRoot, numSignups, messageRoot] = await Promise.all([ + const [defaultStartBlockSignup, defaultStartBlockPoll, stateRoot, numSignups] = await Promise.all([ maciContract.queryFilter(maciContract.filters.SignUp(), startBlock).then((events) => events[0]?.blockNumber ?? 0), maciContract .queryFilter(maciContract.filters.DeployPoll(), startBlock) .then((events) => events[0]?.blockNumber ?? 0), maciContract.getStateTreeRoot(), maciContract.numSignUps(), - messageAqContract.getMainRoot(messageTreeDepth), ]); const defaultStartBlock = Math.min(defaultStartBlockPoll, defaultStartBlockSignup); let fromBlock = startBlock ? Number(startBlock) : defaultStartBlock; const defaultEndBlock = await Promise.all([ - pollContract - .queryFilter(pollContract.filters.MergeMessageAq(messageRoot), fromBlock) - .then((events) => events[events.length - 1]?.blockNumber), pollContract .queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock) .then((events) => events[events.length - 1]?.blockNumber), diff --git a/packages/cli/ts/commands/index.ts b/packages/cli/ts/commands/index.ts index 13877710dc..7099e6c610 100644 --- a/packages/cli/ts/commands/index.ts +++ b/packages/cli/ts/commands/index.ts @@ -4,7 +4,6 @@ export { getPoll } from "./poll"; export { deployVkRegistryContract } from "./deployVkRegistry"; export { genKeyPair } from "./genKeyPair"; export { genMaciPubKey } from "./genPubKey"; -export { mergeMessages } from "./mergeMessages"; export { mergeSignups } from "./mergeSignups"; export { publish, publishBatch } from "./publish"; export { setVerifyingKeys } from "./setVerifyingKeys"; diff --git a/packages/cli/ts/commands/joinPoll.ts b/packages/cli/ts/commands/joinPoll.ts new file mode 100644 index 0000000000..c359f37f6d --- /dev/null +++ b/packages/cli/ts/commands/joinPoll.ts @@ -0,0 +1,461 @@ +import { type ContractTransactionReceipt } from "ethers"; +import { extractVk, genProof, verifyProof } from "maci-circuits"; +import { formatProofForVerifierContract, genSignUpTree, IGenSignUpTree } from "maci-contracts"; +import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts/typechain-types"; +import { CircuitInputs, IJsonMaciState, MaciState, IPollJoiningCircuitInputs } from "maci-core"; +import { poseidon, sha256Hash, stringifyBigInts } from "maci-crypto"; +import { IVkObjectParams, Keypair, PrivKey, PubKey, StateLeaf } from "maci-domainobjs"; + +import assert from "assert"; +import fs from "fs"; + +import type { IJoinPollArgs, IJoinedUserArgs, IParsePollJoinEventsArgs, IJoinPollData } from "../utils"; + +import { contractExists, logError, logYellow, info, logGreen, success, BLOCKS_STEP } from "../utils"; +import { banner } from "../utils/banner"; + +/** + * Get state index and credit balance + * either from command line or + * from maci state leaves or from sign up leaves + * @param stateIndex State index from the command + * @param newVoiceCreditBalance Credit balance from the command + * @param stateLeaves State leaves from maci state or sign up tree + * @param userMaciPubKey Public key of the maci user + * @returns State index and credit balance + */ +const getStateIndexAndCreditBalance = ( + stateIndex: bigint | null, + newVoiceCreditBalance: bigint | null, + stateLeaves: StateLeaf[], + userMaciPubKey: PubKey, +) => { + let loadedStateIndex = stateIndex; + let loadedCreditBalance = newVoiceCreditBalance; + + if (!stateIndex) { + const index = stateLeaves.findIndex((leaf) => leaf.pubKey.equals(userMaciPubKey)); + if (index > 0) { + loadedStateIndex = BigInt(index); + } else { + logError("State leaf not found"); + process.exit(); + } + } + if (!newVoiceCreditBalance) { + const balance = stateLeaves[Number(loadedStateIndex!)].voiceCreditBalance; + if (balance) { + loadedCreditBalance = balance; + } else { + logError("Voice credit balance not found"); + process.exit(); + } + } + + return [loadedStateIndex, loadedCreditBalance]; +}; + +/** + * Generate and verify poll proof + * @param inputs - the inputs to the circuit + * @param zkeyPath - the path to the zkey + * @param useWasm - whether we want to use the wasm witness or not + * @param rapidsnarkExePath - the path to the rapidnsark binary + * @param witnessExePath - the path to the compiled witness binary + * @param wasmPath - the path to the wasm witness + * @param pollVk - Poll verifying key + * @returns proof - an array of strings + */ +const generateAndVerifyProof = async ( + inputs: CircuitInputs, + zkeyPath: string, + useWasm: boolean | undefined, + rapidsnarkExePath: string | undefined, + witnessExePath: string | undefined, + wasmPath: string | undefined, + pollVk: IVkObjectParams, +) => { + const r = await genProof({ + inputs, + zkeyPath, + useWasm, + rapidsnarkExePath, + witnessExePath, + wasmPath, + }); + + // verify it + const isValid = await verifyProof(r.publicSignals, r.proof, pollVk); + if (!isValid) { + throw new Error("Generated an invalid proof"); + } + + return formatProofForVerifierContract(r.proof); +}; + +/** + * Create circuit input for pollJoining + * @param signUpData Sign up tree and state leaves + * @param stateTreeDepth Maci state tree depth + * @param maciPrivKey User's private key for signing up + * @param stateLeafIndex Index where the user is stored in the state leaves + * @param credits Credits for voting + * @param pollPrivKey Poll's private key for the poll joining + * @param pollPubKey Poll's public key for the poll joining + * @returns stringified circuit inputs + */ +const joiningCircuitInputs = ( + signUpData: IGenSignUpTree, + stateTreeDepth: bigint, + maciPrivKey: PrivKey, + stateLeafIndex: bigint, + credits: bigint, + pollPrivKey: PrivKey, + pollPubKey: PubKey, +): IPollJoiningCircuitInputs => { + // Get the state leaf on the index position + const { signUpTree: stateTree, stateLeaves } = signUpData; + const stateLeaf = stateLeaves[Number(stateLeafIndex)]; + const { pubKey, voiceCreditBalance, timestamp } = stateLeaf; + const pubKeyX = pubKey.asArray()[0]; + const pubKeyY = pubKey.asArray()[1]; + const stateLeafArray = [pubKeyX, pubKeyY, voiceCreditBalance, timestamp]; + const pollPubKeyArray = pollPubKey.asArray(); + + assert(credits <= voiceCreditBalance, "Credits must be lower than signed up credits"); + + // calculate the path elements for the state tree given the original state tree + const { siblings, index } = stateTree.generateProof(Number(stateLeafIndex)); + const siblingsLength = siblings.length; + + // The index must be converted to a list of indices, 1 for each tree level. + // The circuit tree depth is this.stateTreeDepth, so the number of siblings must be this.stateTreeDepth, + // even if the tree depth is actually 3. The missing siblings can be set to 0, as they + // won't be used to calculate the root in the circuit. + const indices: bigint[] = []; + + for (let i = 0; i < stateTreeDepth; i += 1) { + // eslint-disable-next-line no-bitwise + indices.push(BigInt((index >> i) & 1)); + + if (i >= siblingsLength) { + siblings[i] = BigInt(0); + } + } + + const siblingsArray = siblings.map((sibling) => [sibling]); + + // Create nullifier from private key + const inputNullifier = BigInt(maciPrivKey.asCircuitInputs()); + const nullifier = poseidon([inputNullifier]); + + // Get pll state tree's root + const stateRoot = stateTree.root; + + // Set actualStateTreeDepth as number of initial siblings length + const actualStateTreeDepth = BigInt(siblingsLength); + + // Calculate public input hash from nullifier, credits and current root + const inputHash = sha256Hash([nullifier, credits, stateRoot, pollPubKeyArray[0], pollPubKeyArray[1]]); + + const circuitInputs = { + privKey: maciPrivKey.asCircuitInputs(), + pollPrivKey: pollPrivKey.asCircuitInputs(), + pollPubKey: pollPubKey.asCircuitInputs(), + stateLeaf: stateLeafArray, + siblings: siblingsArray, + indices, + nullifier, + credits, + stateRoot, + actualStateTreeDepth, + inputHash, + }; + + return stringifyBigInts(circuitInputs) as unknown as IPollJoiningCircuitInputs; +}; + +/** + * Join Poll user to the Poll contract + * @param {IJoinPollArgs} args - The arguments for the join poll command + * @returns {IJoinPollData} The poll state index of the joined user and transaction hash + */ +export const joinPoll = async ({ + maciAddress, + privateKey, + pollPrivKey, + stateIndex, + newVoiceCreditBalance, + stateFile, + pollId, + signer, + startBlock, + endBlock, + blocksPerBatch, + transactionHash, + pollJoiningZkey, + useWasm, + rapidsnark, + pollWitgen, + pollWasm, + quiet = true, +}: IJoinPollArgs): Promise => { + banner(quiet); + + if (!(await contractExists(signer.provider!, maciAddress))) { + logError("MACI contract does not exist"); + } + + if (!PrivKey.isValidSerializedPrivKey(privateKey)) { + logError("Invalid MACI private key"); + } + + const userMaciPrivKey = PrivKey.deserialize(privateKey); + const userMaciPubKey = new Keypair(userMaciPrivKey).pubKey; + const nullifier = poseidon([BigInt(userMaciPrivKey.asCircuitInputs())]); + + // Create poll public key from poll private key + const pollPrivKeyDeserialized = PrivKey.deserialize(pollPrivKey); + const pollKeyPair = new Keypair(pollPrivKeyDeserialized); + const pollPubKey = pollKeyPair.pubKey; + + if (pollId < 0) { + logError("Invalid poll id"); + } + + const maciContract = MACIFactory.connect(maciAddress, signer); + const pollAddress = await maciContract.getPoll(pollId); + + if (!(await contractExists(signer.provider!, pollAddress))) { + logError("Poll contract does not exist"); + } + + const pollContract = PollFactory.connect(pollAddress, signer); + + let loadedStateIndex: bigint | null; + let loadedCreditBalance: bigint | null; + let maciState: MaciState | undefined; + let signUpData: IGenSignUpTree | undefined; + let currentStateRootIndex: number; + let circuitInputs: CircuitInputs; + if (stateFile) { + try { + const file = await fs.promises.readFile(stateFile); + const content = JSON.parse(file.toString()) as unknown as IJsonMaciState; + maciState = MaciState.fromJSON(content); + } catch (error) { + logError((error as Error).message); + } + const poll = maciState!.polls.get(pollId)!; + + if (poll.hasJoined(nullifier)) { + throw new Error("User the given nullifier has already joined"); + } + + [loadedStateIndex, loadedCreditBalance] = getStateIndexAndCreditBalance( + stateIndex, + newVoiceCreditBalance, + maciState!.stateLeaves, + userMaciPubKey, + ); + + // check < 1 cause index zero is a blank state leaf + if (loadedStateIndex! < 1) { + logError("Invalid state index"); + } + + currentStateRootIndex = poll.maciStateRef.numSignUps - 1; + + poll.updatePoll(BigInt(maciState!.stateLeaves.length)); + + circuitInputs = poll.joiningCircuitInputs({ + maciPrivKey: userMaciPrivKey, + stateLeafIndex: loadedStateIndex!, + credits: loadedCreditBalance!, + pollPrivKey: pollPrivKeyDeserialized, + pollPubKey, + }) as unknown as CircuitInputs; + } else { + // build an off-chain representation of the MACI contract using data in the contract storage + const [defaultStartBlockSignup, defaultStartBlockPoll, stateTreeDepth, numSignups] = await Promise.all([ + maciContract.queryFilter(maciContract.filters.SignUp(), startBlock).then((events) => events[0]?.blockNumber ?? 0), + maciContract + .queryFilter(maciContract.filters.DeployPoll(), startBlock) + .then((events) => events[0]?.blockNumber ?? 0), + maciContract.stateTreeDepth(), + maciContract.numSignUps(), + ]); + const defaultStartBlock = Math.min(defaultStartBlockPoll, defaultStartBlockSignup); + let fromBlock = startBlock ? Number(startBlock) : defaultStartBlock; + + if (transactionHash) { + const tx = await signer.provider!.getTransaction(transactionHash); + fromBlock = tx?.blockNumber ?? defaultStartBlock; + } + + logYellow(quiet, info(`starting to fetch logs from block ${fromBlock}`)); + + signUpData = await genSignUpTree({ + provider: signer.provider!, + address: await maciContract.getAddress(), + blocksPerRequest: blocksPerBatch || 50, + fromBlock, + endBlock, + sleepAmount: 0, + }); + + currentStateRootIndex = Number(numSignups) - 1; + + [loadedStateIndex, loadedCreditBalance] = getStateIndexAndCreditBalance( + stateIndex, + newVoiceCreditBalance, + signUpData.stateLeaves, + userMaciPubKey, + ); + + // check < 1 cause index zero is a blank state leaf + if (loadedStateIndex! < 1) { + logError("Invalid state index"); + } + + circuitInputs = joiningCircuitInputs( + signUpData, + stateTreeDepth, + userMaciPrivKey, + loadedStateIndex!, + loadedCreditBalance!, + pollPrivKeyDeserialized, + pollPubKey, + ) as unknown as CircuitInputs; + } + + const pollVk = await extractVk(pollJoiningZkey); + + let pollStateIndex = ""; + let receipt: ContractTransactionReceipt | null = null; + + try { + // generate the proof for this batch + const proof = await generateAndVerifyProof( + circuitInputs, + pollJoiningZkey, + useWasm, + rapidsnark, + pollWitgen, + pollWasm, + pollVk, + ); + + // submit the message onchain as well as the encryption public key + const tx = await pollContract.joinPoll( + nullifier, + pollPubKey.asContractParam(), + loadedCreditBalance!, + currentStateRootIndex, + proof, + ); + receipt = await tx.wait(); + logYellow(quiet, info(`Transaction hash: ${receipt!.hash}`)); + + if (receipt?.status !== 1) { + logError("Transaction failed"); + } + + const iface = pollContract.interface; + + // get state index from the event + if (receipt?.logs) { + const [log] = receipt.logs; + const { args } = iface.parseLog(log as unknown as { topics: string[]; data: string }) || { args: [] }; + [, , , , , pollStateIndex] = args; + logGreen(quiet, success(`State index: ${pollStateIndex.toString()}`)); + } else { + logError("Unable to retrieve the transaction receipt"); + } + } catch (error) { + logError((error as Error).message); + } + + return { + pollStateIndex: pollStateIndex ? pollStateIndex.toString() : "", + hash: receipt!.hash, + }; +}; + +/** + * Parse the poll joining events from the Poll contract + */ +const parsePollJoinEvents = async ({ + pollContract, + startBlock, + currentBlock, + pollPublicKey, +}: IParsePollJoinEventsArgs) => { + // 1000 blocks at a time + for (let block = startBlock; block <= currentBlock; block += BLOCKS_STEP) { + const toBlock = Math.min(block + BLOCKS_STEP - 1, currentBlock); + const pubKey = pollPublicKey.asArray(); + // eslint-disable-next-line no-await-in-loop + const newEvents = await pollContract.queryFilter( + pollContract.filters.PollJoined(pubKey[0], pubKey[1], undefined, undefined, undefined, undefined), + block, + toBlock, + ); + + if (newEvents.length > 0) { + const [event] = newEvents; + + return { + pollStateIndex: event.args[5].toString(), + voiceCredits: event.args[2].toString(), + }; + } + } + + return { + pollStateIndex: undefined, + voiceCredits: undefined, + }; +}; + +/** + * Checks if user is joined with the public key + * @param {IJoinedUserArgs} - The arguments for the join check command + * @returns user joined or not and poll state index, voice credit balance + */ +export const isJoinedUser = async ({ + maciAddress, + pollId, + pollPubKey, + signer, + startBlock, + quiet = true, +}: IJoinedUserArgs): Promise<{ isJoined: boolean; pollStateIndex?: string; voiceCredits?: string }> => { + banner(quiet); + + const maciContract = MACIFactory.connect(maciAddress, signer); + const pollAddress = await maciContract.getPoll(pollId); + const pollContract = PollFactory.connect(pollAddress, signer); + + const pollPublicKey = PubKey.deserialize(pollPubKey); + const startBlockNumber = startBlock || 0; + const currentBlock = await signer.provider!.getBlockNumber(); + + const { pollStateIndex, voiceCredits } = await parsePollJoinEvents({ + pollContract, + startBlock: startBlockNumber, + currentBlock, + pollPublicKey, + }); + + logGreen( + quiet, + success(`Poll state index: ${pollStateIndex?.toString()}, registered: ${pollStateIndex !== undefined}`), + ); + + return { + isJoined: pollStateIndex !== undefined, + pollStateIndex, + voiceCredits, + }; +}; diff --git a/packages/cli/ts/commands/mergeMessages.ts b/packages/cli/ts/commands/mergeMessages.ts deleted file mode 100644 index 4ed876be14..0000000000 --- a/packages/cli/ts/commands/mergeMessages.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { - MACI__factory as MACIFactory, - Poll__factory as PollFactory, - AccQueue__factory as AccQueueFactory, -} from "maci-contracts/typechain-types"; - -import type { MergeMessagesArgs } from "../utils/interfaces"; - -import { banner } from "../utils/banner"; -import { contractExists, currentBlockTimestamp } from "../utils/contracts"; -import { DEFAULT_SR_QUEUE_OPS } from "../utils/defaults"; -import { readContractAddress } from "../utils/storage"; -import { info, logError, logGreen, logYellow, success } from "../utils/theme"; - -/** - * Merge the message queue on chain - * @param MergeMessagesArgs - The arguments for the mergeMessages command - */ -export const mergeMessages = async ({ - pollId, - quiet = true, - maciAddress, - numQueueOps, - signer, -}: MergeMessagesArgs): Promise => { - banner(quiet); - const network = await signer.provider?.getNetwork(); - - // maci contract validation - const maciContractAddress = maciAddress || (await readContractAddress("MACI", network?.name)); - - if (!maciContractAddress) { - logError("Could not read contracts"); - } - - if (!(await contractExists(signer.provider!, maciContractAddress))) { - logError("MACI contract does not exist"); - } - - if (pollId < 0) { - logError("Invalid poll id"); - } - - const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollContracts = await maciContract.polls(pollId); - - if (!(await contractExists(signer.provider!, pollContracts.poll))) { - logError("Poll contract does not exist"); - } - - const pollContract = PollFactory.connect(pollContracts.poll, signer); - const extContracts = await pollContract.extContracts(); - const messageAqContractAddr = extContracts.messageAq; - - const accQueueContract = AccQueueFactory.connect(messageAqContractAddr, signer); - - // check if it's time to merge the message AQ - const dd = await pollContract.getDeployTimeAndDuration(); - const deadline = Number(dd[0]) + Number(dd[1]); - const now = await currentBlockTimestamp(signer.provider!); - - if (now < deadline) { - logError("The voting period is not over yet"); - } - - let subTreesMerged = false; - - // infinite loop to merge the sub trees - while (!subTreesMerged) { - // eslint-disable-next-line no-await-in-loop - subTreesMerged = await accQueueContract.subTreesMerged(); - - if (subTreesMerged) { - logGreen(quiet, success("All message subtrees have been merged.")); - } else { - // eslint-disable-next-line no-await-in-loop - await accQueueContract - .getSrIndices() - .then((data) => data.map((x) => Number(x))) - .then((indices) => { - logYellow(quiet, info(`Merging message subroots ${indices[0] + 1} / ${indices[1] + 1}`)); - }); - - // eslint-disable-next-line no-await-in-loop - const tx = await pollContract.mergeMessageAqSubRoots(numQueueOps || DEFAULT_SR_QUEUE_OPS); - // eslint-disable-next-line no-await-in-loop - const receipt = await tx.wait(); - - if (receipt?.status !== 1) { - logError("Transaction failed"); - } - - logGreen(quiet, success(`Executed mergeMessageAqSubRoots(); gas used: ${receipt!.gasUsed.toString()}`)); - - logYellow(quiet, info(`Transaction hash: ${receipt!.hash}`)); - } - } - - // check if the message AQ has been fully merged - const messageTreeDepth = Number((await pollContract.treeDepths()).messageTreeDepth); - - // check if the main root was not already computed - const mainRoot = (await accQueueContract.getMainRoot(messageTreeDepth.toString())).toString(); - if (mainRoot === "0") { - // go and merge the message tree - - logYellow(quiet, info("Merging subroots to a main message root...")); - const tx = await pollContract.mergeMessageAq(); - const receipt = await tx.wait(); - - if (receipt?.status !== 1) { - logError("Transaction failed"); - } - - logGreen(quiet, success(`Executed mergeMessageAq(); gas used: ${receipt!.gasUsed.toString()}`)); - logYellow(quiet, info(`Transaction hash: ${receipt!.hash}`)); - logGreen(quiet, success("The message tree has been merged.")); - } else { - logYellow(quiet, info("The message tree has already been merged.")); - } -}; diff --git a/packages/cli/ts/commands/proveOnChain.ts b/packages/cli/ts/commands/proveOnChain.ts index 2fde7b89f1..c3374c940e 100644 --- a/packages/cli/ts/commands/proveOnChain.ts +++ b/packages/cli/ts/commands/proveOnChain.ts @@ -1,17 +1,17 @@ /* eslint-disable no-await-in-loop */ import { type BigNumberish } from "ethers"; -import { type IVerifyingKeyStruct, TallyData, formatProofForVerifierContract } from "maci-contracts"; import { MACI__factory as MACIFactory, - AccQueue__factory as AccQueueFactory, Tally__factory as TallyFactory, MessageProcessor__factory as MessageProcessorFactory, Poll__factory as PollFactory, VkRegistry__factory as VkRegistryFactory, Verifier__factory as VerifierFactory, -} from "maci-contracts/typechain-types"; -import { MESSAGE_TREE_ARITY, STATE_TREE_ARITY } from "maci-core"; -import { G1Point, G2Point, genTreeProof } from "maci-crypto"; + formatProofForVerifierContract, + type IVerifyingKeyStruct, +} from "maci-contracts"; +import { STATE_TREE_ARITY } from "maci-core"; +import { G1Point, G2Point, hashLeftRight } from "maci-crypto"; import { VerifyingKey } from "maci-domainobjs"; import fs from "fs"; @@ -72,13 +72,6 @@ export const proveOnChain = async ({ const mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); const tallyContract = TallyFactory.connect(pollContracts.tally, signer); - const messageAqContractAddress = (await pollContract.extContracts()).messageAq; - - if (!(await contractExists(signer.provider!, messageAqContractAddress))) { - logError("There is no MessageAq contract linked to the specified MACI contract."); - } - - const messageAqContract = AccQueueFactory.connect(messageAqContractAddress, signer); const vkRegistryContractAddress = await tallyContract.vkRegistry(); if (!(await contractExists(signer.provider!, vkRegistryContractAddress))) { @@ -129,13 +122,12 @@ export const proveOnChain = async ({ const numSignUpsAndMessages = await pollContract.numSignUpsAndMessages(); const numSignUps = Number(numSignUpsAndMessages[0]); const numMessages = Number(numSignUpsAndMessages[1]); - const messageBatchSize = MESSAGE_TREE_ARITY ** Number(treeDepths.messageTreeSubDepth); + const messageBatchSize = Number(await pollContract.messageBatchSize()); const tallyBatchSize = STATE_TREE_ARITY ** Number(treeDepths.intStateTreeDepth); - let totalMessageBatches = numMessages <= messageBatchSize ? 1 : Math.floor(numMessages / messageBatchSize); - - if (numMessages > messageBatchSize && numMessages % messageBatchSize > 0) { - totalMessageBatches += 1; - } + const pollBatchHashes = await pollContract.getBatchHashes(); + const batchHashes = [...pollBatchHashes]; + const totalMessageBatches = batchHashes.length; + const lastChainHash = await pollContract.chainHash(); // perform validation if (numProcessProofs !== totalMessageBatches) { @@ -156,55 +148,45 @@ export const proveOnChain = async ({ logError("Tally and MessageProcessor modes are not compatible"); } - const messageRootOnChain = await messageAqContract.getMainRoot(Number(treeDepths.messageTreeDepth)); - const stateTreeDepth = Number(await maciContract.stateTreeDepth()); const onChainProcessVk = await vkRegistryContract.getProcessVk( stateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, mpMode, ); - const dd = await pollContract.getDeployTimeAndDuration(); - const pollEndTimestampOnChain = BigInt(dd[0]) + BigInt(dd[1]); - if (numberBatchesProcessed < totalMessageBatches) { logYellow(quiet, info("Submitting proofs of message processing...")); } // process all batches left for (let i = numberBatchesProcessed; i < totalMessageBatches; i += 1) { - let currentMessageBatchIndex: number; + let currentMessageBatchIndex = totalMessageBatches; if (numberBatchesProcessed === 0) { - const r = numMessages % messageBatchSize; - - currentMessageBatchIndex = numMessages; + const chainHash = lastChainHash; + if (numMessages % messageBatchSize !== 0) { + batchHashes.push(chainHash); + } + currentMessageBatchIndex = batchHashes.length; if (currentMessageBatchIndex > 0) { - if (r === 0) { - currentMessageBatchIndex -= messageBatchSize; - } else { - currentMessageBatchIndex -= r; - } + currentMessageBatchIndex -= 1; } - } else { - currentMessageBatchIndex = (totalMessageBatches - numberBatchesProcessed) * messageBatchSize; - } - - if (numberBatchesProcessed > 0 && currentMessageBatchIndex > 0) { - currentMessageBatchIndex -= messageBatchSize; } const { proof, circuitInputs, publicInputs } = data.processProofs[i]; // validation - if (circuitInputs.pollEndTimestamp !== pollEndTimestampOnChain.toString()) { - logError("pollEndTimestamp mismatch."); + + const inputBatchHash = batchHashes[currentMessageBatchIndex - 1]; + if (BigInt(circuitInputs.inputBatchHash as BigNumberish).toString() !== inputBatchHash.toString()) { + logError("input batch hash mismatch."); } - if (BigInt(circuitInputs.msgRoot as BigNumberish).toString() !== messageRootOnChain.toString()) { - logError("message root mismatch."); + + const outputBatchHash = batchHashes[currentMessageBatchIndex]; + if (BigInt(circuitInputs.outputBatchHash as BigNumberish).toString() !== outputBatchHash.toString()) { + logError("output batch hash mismatch."); } let currentSbCommitmentOnChain: bigint; @@ -225,7 +207,11 @@ export const proveOnChain = async ({ } const publicInputsOnChain = await mpContract - .getPublicCircuitInputs(currentMessageBatchIndex, asHex(circuitInputs.newSbCommitment as BigNumberish)) + .getPublicCircuitInputs( + currentMessageBatchIndex, + asHex(circuitInputs.newSbCommitment as BigNumberish), + outputBatchHash.toString(), + ) .then((value) => [...value]); if (!publicInputsOnChain.every((value, index) => value.toString() === publicInputs[index].toString())) { diff --git a/packages/cli/ts/commands/publish.ts b/packages/cli/ts/commands/publish.ts index 5b48285949..197624ba55 100644 --- a/packages/cli/ts/commands/publish.ts +++ b/packages/cli/ts/commands/publish.ts @@ -1,5 +1,4 @@ import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts/typechain-types"; -import { MESSAGE_TREE_ARITY } from "maci-core"; import { genRandomSalt } from "maci-crypto"; import { type IG1ContractParams, @@ -87,9 +86,8 @@ export const publish = async ({ const pollContract = PollFactory.connect(pollContracts.poll, signer); - const treeDepths = await pollContract.treeDepths(); + const maxVoteOptions = Number(await pollContract.maxVoteOptions()); const coordinatorPubKeyResult = await pollContract.coordinatorPubKey(); - const maxVoteOptions = Number(BigInt(MESSAGE_TREE_ARITY) ** treeDepths.voteOptionTreeDepth); // validate the vote options index against the max leaf index on-chain if (maxVoteOptions < voteOptionIndex) { @@ -173,11 +171,10 @@ export const publishBatch = async ({ const pollContract = PollFactory.connect(pollContracts.poll, signer); - const [treeDepths, coordinatorPubKeyResult] = await Promise.all([ - pollContract.treeDepths(), + const [maxVoteOptions, coordinatorPubKeyResult] = await Promise.all([ + pollContract.maxVoteOptions().then(Number), pollContract.coordinatorPubKey(), ]); - const maxVoteOptions = Number(BigInt(MESSAGE_TREE_ARITY) ** treeDepths.voteOptionTreeDepth); // validate the vote options index against the max leaf index on-chain messages.forEach(({ stateIndex, voteOptionIndex, salt, nonce }) => { diff --git a/packages/cli/ts/commands/setVerifyingKeys.ts b/packages/cli/ts/commands/setVerifyingKeys.ts index 60120a81bf..7decc3a622 100644 --- a/packages/cli/ts/commands/setVerifyingKeys.ts +++ b/packages/cli/ts/commands/setVerifyingKeys.ts @@ -1,7 +1,6 @@ import { extractVk } from "maci-circuits"; -import { type IVerifyingKeyStruct, EMode } from "maci-contracts"; -import { VkRegistry__factory as VkRegistryFactory } from "maci-contracts/typechain-types"; -import { genProcessVkSig, genTallyVkSig, MESSAGE_TREE_ARITY } from "maci-core"; +import { type IVerifyingKeyStruct, VkRegistry__factory as VkRegistryFactory, EMode } from "maci-contracts"; +import { genProcessVkSig, genTallyVkSig } from "maci-core"; import { VerifyingKey } from "maci-domainobjs"; import fs from "fs"; @@ -27,9 +26,8 @@ import { export const setVerifyingKeys = async ({ stateTreeDepth, intStateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, - messageBatchDepth, + messageBatchSize, processMessagesZkeyPathQv, tallyVotesZkeyPathQv, processMessagesZkeyPathNonQv, @@ -87,13 +85,7 @@ export const setVerifyingKeys = async ({ const tallyVkNonQv = tallyVotesZkeyPathNonQv && VerifyingKey.fromObj(await extractVk(tallyVotesZkeyPathNonQv)); // validate args - if ( - stateTreeDepth < 1 || - intStateTreeDepth < 1 || - messageTreeDepth < 1 || - voteOptionTreeDepth < 1 || - messageBatchDepth < 1 - ) { + if (stateTreeDepth < 1 || intStateTreeDepth < 1 || voteOptionTreeDepth < 1 || messageBatchSize < 1) { logError("Invalid depth or batch size parameters"); } @@ -105,8 +97,7 @@ export const setVerifyingKeys = async ({ processMessagesZkeyPath: processMessagesZkeyPathQv!, tallyVotesZkeyPath: tallyVotesZkeyPathQv!, stateTreeDepth, - messageTreeDepth, - messageBatchDepth, + messageBatchSize, voteOptionTreeDepth, intStateTreeDepth, }); @@ -115,8 +106,7 @@ export const setVerifyingKeys = async ({ processMessagesZkeyPath: processMessagesZkeyPathNonQv!, tallyVotesZkeyPath: tallyVotesZkeyPathNonQv!, stateTreeDepth, - messageTreeDepth, - messageBatchDepth, + messageBatchSize, voteOptionTreeDepth, intStateTreeDepth, }); @@ -129,10 +119,8 @@ export const setVerifyingKeys = async ({ // connect to VkRegistry contract const vkRegistryContract = VkRegistryFactory.connect(vkRegistryAddress, signer); - const messageBatchSize = MESSAGE_TREE_ARITY ** messageBatchDepth; - // check if the process messages vk was already set - const processVkSig = genProcessVkSig(stateTreeDepth, messageTreeDepth, voteOptionTreeDepth, messageBatchSize); + const processVkSig = genProcessVkSig(stateTreeDepth, voteOptionTreeDepth, messageBatchSize); if (useQuadraticVoting && (await vkRegistryContract.isProcessVkSet(processVkSig, EMode.QV))) { logError("This process verifying key is already set in the contract"); @@ -177,7 +165,6 @@ export const setVerifyingKeys = async ({ const tx = await vkRegistryContract.setVerifyingKeysBatch( stateTreeDepth, intStateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, messageBatchSize, modes, @@ -197,7 +184,6 @@ export const setVerifyingKeys = async ({ if (useQuadraticVoting) { const processVkOnChain = await vkRegistryContract.getProcessVk( stateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -220,7 +206,6 @@ export const setVerifyingKeys = async ({ } else { const processVkOnChain = await vkRegistryContract.getProcessVk( stateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, messageBatchSize, EMode.NON_QV, @@ -250,8 +235,7 @@ export const setVerifyingKeys = async ({ interface ICheckZkeyFilepathsArgs { stateTreeDepth: number; - messageTreeDepth: number; - messageBatchDepth: number; + messageBatchSize: number; voteOptionTreeDepth: number; intStateTreeDepth: number; processMessagesZkeyPath?: string; @@ -262,8 +246,7 @@ function checkZkeyFilepaths({ processMessagesZkeyPath, tallyVotesZkeyPath, stateTreeDepth, - messageTreeDepth, - messageBatchDepth, + messageBatchSize, voteOptionTreeDepth, intStateTreeDepth, }: ICheckZkeyFilepathsArgs): void { @@ -272,7 +255,7 @@ function checkZkeyFilepaths({ } // Check the pm zkey filename against specified params - const pmMatch = processMessagesZkeyPath.match(/.+_(\d+)-(\d+)-(\d+)-(\d+)/); + const pmMatch = processMessagesZkeyPath.match(/.+_(\d+)-(\d+)-(\d+)/); if (!pmMatch) { logError(`${processMessagesZkeyPath} has an invalid filename`); @@ -280,9 +263,8 @@ function checkZkeyFilepaths({ } const pmStateTreeDepth = Number(pmMatch[1]); - const pmMsgTreeDepth = Number(pmMatch[2]); - const pmMsgBatchDepth = Number(pmMatch[3]); - const pmVoteOptionTreeDepth = Number(pmMatch[4]); + const pmMsgBatchSize = Number(pmMatch[2]); + const pmVoteOptionTreeDepth = Number(pmMatch[3]); const tvMatch = tallyVotesZkeyPath.match(/.+_(\d+)-(\d+)-(\d+)/); @@ -297,8 +279,7 @@ function checkZkeyFilepaths({ if ( stateTreeDepth !== pmStateTreeDepth || - messageTreeDepth !== pmMsgTreeDepth || - messageBatchDepth !== pmMsgBatchDepth || + messageBatchSize !== pmMsgBatchSize || voteOptionTreeDepth !== pmVoteOptionTreeDepth || stateTreeDepth !== tvStateTreeDepth || intStateTreeDepth !== tvIntStateTreeDepth || diff --git a/packages/cli/ts/index.ts b/packages/cli/ts/index.ts index d9aba453b2..388830eeb7 100644 --- a/packages/cli/ts/index.ts +++ b/packages/cli/ts/index.ts @@ -16,7 +16,6 @@ import { showContracts, deployPoll, getPoll, - mergeMessages, publish, setVerifyingKeys, mergeSignups, @@ -93,9 +92,8 @@ program .option("-vk, --vk-contract ", "the VkRegistry contract address") .requiredOption("-s, --state-tree-depth ", "the state tree depth", parseInt) .requiredOption("-i, --int-state-tree-depth ", "the intermediate state tree depth", parseInt) - .requiredOption("-m, --msg-tree-depth ", "the message tree depth", parseInt) .requiredOption("-v, --vote-option-tree-depth ", "the vote option tree depth", parseInt) - .requiredOption("-b, --msg-batch-depth ", "the message batch depth", parseInt) + .requiredOption("-b, --msg-batch-size ", "the message batch size", parseInt) .requiredOption( "-p, --process-messages-zkey ", "the process messages zkey path (see different options for zkey files to use specific circuits https://maci.pse.dev/docs/trusted-setup, https://maci.pse.dev/docs/testing/#pre-compiled-artifacts-for-testing)", @@ -111,9 +109,8 @@ program await checkVerifyingKeys({ stateTreeDepth: cmdOptions.stateTreeDepth, intStateTreeDepth: cmdOptions.intStateTreeDepth, - messageTreeDepth: cmdOptions.msgTreeDepth, voteOptionTreeDepth: cmdOptions.voteOptionTreeDepth, - messageBatchDepth: cmdOptions.msgBatchDepth, + messageBatchSize: cmdOptions.msgBatchSize, processMessagesZkeyPath: cmdOptions.processMessagesZkey, tallyVotesZkeyPath: cmdOptions.tallyVotesZkey, vkRegistry: cmdOptions.vkContract, @@ -175,8 +172,7 @@ program .option("-vk, --vkRegistryAddress ", "the vk registry contract address") .requiredOption("-t, --duration ", "the poll duration", parseInt) .requiredOption("-i, --int-state-tree-depth ", "the int state tree depth", parseInt) - .requiredOption("-b, --msg-batch-depth ", "the message tree sub depth", parseInt) - .requiredOption("-m, --msg-tree-depth ", "the message tree depth", parseInt) + .requiredOption("-b, --msg-batch-size ", "the message batch size", parseInt) .requiredOption("-v, --vote-option-tree-depth ", "the vote option tree depth", parseInt) .requiredOption("-pk, --pubkey ", "the coordinator public key") .option( @@ -195,8 +191,7 @@ program await deployPoll({ pollDuration: cmdObj.duration, intStateTreeDepth: cmdObj.intStateTreeDepth, - messageTreeSubDepth: cmdObj.msgBatchDepth, - messageTreeDepth: cmdObj.msgTreeDepth, + messageBatchSize: cmdObj.msgBatchSize, voteOptionTreeDepth: cmdObj.voteOptionTreeDepth, coordinatorPubkey: cmdObj.pubkey, maciAddress: cmdObj.maciAddress, @@ -214,9 +209,8 @@ program .description("set the verifying keys") .requiredOption("-s, --state-tree-depth ", "the state tree depth", parseInt) .requiredOption("-i, --int-state-tree-depth ", "the intermediate state tree depth", parseInt) - .requiredOption("-m, --msg-tree-depth ", "the message tree depth", parseInt) .requiredOption("-v, --vote-option-tree-depth ", "the vote option tree depth", parseInt) - .requiredOption("-b, --msg-batch-depth ", "the message batch depth", parseInt) + .requiredOption("-b, --msg-batch-size ", "the message batch size", parseInt) .option( "-pqv, --process-messages-zkey-qv ", "the process messages qv zkey path (see different options for zkey files to use specific circuits https://maci.pse.dev/docs/trusted-setup, https://maci.pse.dev/docs/testing/#pre-compiled-artifacts-for-testing)", @@ -249,9 +243,8 @@ program await setVerifyingKeys({ stateTreeDepth: cmdObj.stateTreeDepth, intStateTreeDepth: cmdObj.intStateTreeDepth, - messageTreeDepth: cmdObj.msgTreeDepth, voteOptionTreeDepth: cmdObj.voteOptionTreeDepth, - messageBatchDepth: cmdObj.msgBatchDepth, + messageBatchSize: cmdObj.msgBatchSize, processMessagesZkeyPathQv: cmdObj.processMessagesZkeyQv, tallyVotesZkeyPathQv: cmdObj.tallyVotesZkeyQv, processMessagesZkeyPathNonQv: cmdObj.processMessagesZkeyNonQv, @@ -307,29 +300,7 @@ program program.error((error as Error).message, { exitCode: 1 }); } }); -program - .command("mergeMessages") - .description("merge the message accumulator queue") - .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) - .option("-r, --rpc-provider ", "the rpc provider URL") - .option("-x, --maci-address ", "the MACI contract address") - .requiredOption("-o, --poll-id ", "the poll id", BigInt) - .option("-n, --num-queue-ops ", "the number of queue operations", parseInt) - .action(async (cmdObj) => { - try { - const signer = await getSigner(); - await mergeMessages({ - pollId: cmdObj.pollId, - maciAddress: cmdObj.maciAddress, - numQueueOps: cmdObj.numQueueOps?.toString(), - quiet: cmdObj.quiet, - signer, - }); - } catch (error) { - program.error((error as Error).message, { exitCode: 1 }); - } - }); program .command("mergeSignups") .description("merge the signups accumulator queue") @@ -676,7 +647,6 @@ export { genKeyPair, genMaciPubKey, genProofs, - mergeMessages, mergeSignups, publish, publishBatch, @@ -697,7 +667,6 @@ export type { GenProofsArgs, PublishArgs, SignupArgs, - MergeMessagesArgs, MergeSignupsArgs, VerifyArgs, ProveOnChainArgs, diff --git a/packages/cli/ts/sdk/index.ts b/packages/cli/ts/sdk/index.ts index 35c495e736..83cf2720a8 100644 --- a/packages/cli/ts/sdk/index.ts +++ b/packages/cli/ts/sdk/index.ts @@ -1,7 +1,6 @@ import { extractVkToFile } from "../commands/extractVkToFile"; import { genKeyPair } from "../commands/genKeyPair"; import { genMaciPubKey } from "../commands/genPubKey"; -import { mergeMessages } from "../commands/mergeMessages"; import { mergeSignups } from "../commands/mergeSignups"; import { getPoll } from "../commands/poll"; import { publish, publishBatch } from "../commands/publish"; @@ -28,7 +27,6 @@ export { getPoll, extractVkToFile, mergeSignups, - mergeMessages, getGatekeeperTrait, getSemaphoreGatekeeperData, getZupassGatekeeperData, diff --git a/packages/cli/ts/utils/index.ts b/packages/cli/ts/utils/index.ts index d7c67531a9..7f854dbfbc 100644 --- a/packages/cli/ts/utils/index.ts +++ b/packages/cli/ts/utils/index.ts @@ -28,7 +28,6 @@ export type { SignupArgs, ISignupData, SetVerifyingKeysArgs, - MergeMessagesArgs, MergeSignupsArgs, ProveOnChainArgs, PublishArgs, diff --git a/packages/cli/ts/utils/interfaces.ts b/packages/cli/ts/utils/interfaces.ts index 57ca72524a..eaf9d883b1 100644 --- a/packages/cli/ts/utils/interfaces.ts +++ b/packages/cli/ts/utils/interfaces.ts @@ -164,20 +164,15 @@ export interface CheckVerifyingKeysArgs { */ intStateTreeDepth: number; - /** - * The depth of the message tree - */ - messageTreeDepth: number; - /** * The depth of the vote option tree */ voteOptionTreeDepth: number; /** - * The depth of the message batch tree + * The size of the message batch */ - messageBatchDepth: number; + messageBatchSize: number; /** * The path to the process messages zkey @@ -280,14 +275,9 @@ export interface DeployPollArgs { intStateTreeDepth: number; /** - * The depth of the message tree sublevels + * The size of the message batch */ - messageTreeSubDepth: number; - - /** - * The depth of the message tree - */ - messageTreeDepth: number; + messageBatchSize: number; /** * The depth of the vote option tree @@ -511,36 +501,6 @@ export interface GenProofsArgs { useQuadraticVoting?: boolean; } -/** - * Interface for the arguments to the mergeMessages command - */ -export interface MergeMessagesArgs { - /** - * The id of the poll - */ - pollId: bigint; - - /** - * A signer object - */ - signer: Signer; - - /** - * Whether to log the output - */ - quiet?: boolean; - - /** - * The address of the MACI contract - */ - maciAddress?: string; - - /** - * The number of queue operations to merge - */ - numQueueOps?: string; -} - /** * Interface for the arguments to the mergeSignups command */ @@ -745,20 +705,15 @@ export interface SetVerifyingKeysArgs { */ intStateTreeDepth: number; - /** - * The depth of the message tree - */ - messageTreeDepth: number; - /** * The depth of the vote option tree */ voteOptionTreeDepth: number; /** - * The depth of the message batch tree + * The size of message batch */ - messageBatchDepth: number; + messageBatchSize: number; /** * The path to the process messages qv zkey diff --git a/packages/contracts/contracts/MACI.sol b/packages/contracts/contracts/MACI.sol index b0cbcec39f..ce295847d2 100644 --- a/packages/contracts/contracts/MACI.sol +++ b/packages/contracts/contracts/MACI.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.20; import { IPollFactory } from "./interfaces/IPollFactory.sol"; import { IMessageProcessorFactory } from "./interfaces/IMPFactory.sol"; import { ITallyFactory } from "./interfaces/ITallyFactory.sol"; +import { IVerifier } from "./interfaces/IVerifier.sol"; +import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; import { InitialVoiceCreditProxy } from "./initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; import { SignUpGatekeeper } from "./gatekeepers/SignUpGatekeeper.sol"; import { IMACI } from "./interfaces/IMACI.sol"; @@ -26,7 +28,9 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { uint256 public immutable maxSignups; - uint8 internal constant TREE_ARITY = 2; + uint8 internal constant STATE_TREE_ARITY = 2; + + uint8 internal constant VOTE_TREE_ARITY = 5; /// @notice The hash of a blank state leaf uint256 internal constant BLANK_STATE_LEAF_HASH = @@ -39,7 +43,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { uint256 public nextPollId; /// @notice A mapping of poll IDs to Poll contracts. - mapping(uint256 => PollContracts) public polls; + mapping(uint256 => address) public polls; /// @notice Factory contract that deploy a Poll contract IPollFactory public immutable pollFactory; @@ -81,6 +85,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { uint256 _pollId, uint256 indexed _coordinatorPubKeyX, uint256 indexed _coordinatorPubKeyY, + PollContracts pollAddr, Mode _mode ); @@ -117,7 +122,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { signUpGatekeeper = _signUpGatekeeper; initialVoiceCreditProxy = _initialVoiceCreditProxy; stateTreeDepth = _stateTreeDepth; - maxSignups = uint256(TREE_ARITY) ** uint256(_stateTreeDepth); + maxSignups = uint256(STATE_TREE_ARITY) ** uint256(_stateTreeDepth); emptyBallotRoots = _emptyBallotRoots; // Verify linked poseidon libraries @@ -126,8 +131,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @notice Allows any eligible user sign up. The sign-up gatekeeper should prevent /// double sign-ups or ineligible users from doing so. This function will - /// only succeed if the sign-up deadline has not passed. It also enqueues a - /// fresh state leaf into the state AccQueue. + /// only succeed if the sign-up deadline has not passed. /// @param _pubKey The user's desired public key. /// @param _signUpGatekeeperData Data to pass to the sign-up gatekeeper's /// register() function. For instance, the POAPGatekeeper or @@ -168,6 +172,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @notice Deploy a new Poll contract. /// @param _duration How long should the Poll last for /// @param _treeDepths The depth of the Merkle trees + /// @param _messageBatchSize The message batch size /// @param _coordinatorPubKey The coordinator's public key /// @param _verifier The Verifier Contract /// @param _vkRegistry The VkRegistry Contract @@ -175,11 +180,12 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { function deployPoll( uint256 _duration, TreeDepths memory _treeDepths, + uint8 _messageBatchSize, PubKey memory _coordinatorPubKey, address _verifier, address _vkRegistry, Mode _mode - ) public virtual { + ) public virtual returns (PollContracts memory pollAddr) { // cache the poll to a local variable so we can increment it uint256 pollId = nextPollId; @@ -195,24 +201,32 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { } uint256 voteOptionTreeDepth = _treeDepths.voteOptionTreeDepth; + uint256 maxVoteOptions = uint256(VOTE_TREE_ARITY) ** voteOptionTreeDepth; + + ExtContracts memory extContracts = ExtContracts({ + maci: IMACI(address(this)), + verifier: IVerifier(_verifier), + vkRegistry: IVkRegistry(_vkRegistry) + }); address p = pollFactory.deploy( _duration, + maxVoteOptions, _treeDepths, + _messageBatchSize, _coordinatorPubKey, - address(this), - emptyBallotRoots[voteOptionTreeDepth - 1] + extContracts ); address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, msg.sender, _mode); address tally = tallyFactory.deploy(_verifier, _vkRegistry, p, mp, msg.sender, _mode); - // store the addresses in a struct so they can be returned - PollContracts memory pollAddr = PollContracts({ poll: p, messageProcessor: mp, tally: tally }); + polls[pollId] = p; - polls[pollId] = pollAddr; + // store the addresses in a struct so they can be returned + pollAddr = PollContracts({ poll: p, messageProcessor: mp, tally: tally }); - emit DeployPoll(pollId, _coordinatorPubKey.x, _coordinatorPubKey.y, _mode); + emit DeployPoll(pollId, _coordinatorPubKey.x, _coordinatorPubKey.y, pollAddr, _mode); } /// @inheritdoc IMACI @@ -222,10 +236,10 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @notice Get the Poll details /// @param _pollId The identifier of the Poll to retrieve - /// @return pollContracts The Poll contract object - function getPoll(uint256 _pollId) public view returns (PollContracts memory pollContracts) { + /// @return poll The Poll contract object + function getPoll(uint256 _pollId) public view returns (address poll) { if (_pollId >= nextPollId) revert PollDoesNotExist(_pollId); - pollContracts = polls[_pollId]; + poll = polls[_pollId]; } /// @inheritdoc IMACI diff --git a/packages/contracts/contracts/MessageProcessor.sol b/packages/contracts/contracts/MessageProcessor.sol index 5acfbd7627..bc6e8d23dd 100644 --- a/packages/contracts/contracts/MessageProcessor.sol +++ b/packages/contracts/contracts/MessageProcessor.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import { AccQueue } from "./trees/AccQueue.sol"; import { IMACI } from "./interfaces/IMACI.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { IPoll } from "./interfaces/IPoll.sol"; @@ -21,7 +20,6 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes /// @notice custom errors error NoMoreMessages(); error StateNotMerged(); - error MessageAqNotMerged(); error InvalidProcessMessageProof(); error MaxVoteOptionsTooLarge(); error NumSignUpsTooLarge(); @@ -37,10 +35,8 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes /// @notice The number of batches processed uint256 public numBatchesProcessed; - /// @notice The current message batch index. When the coordinator runs - /// processMessages(), this action relates to messages - /// currentMessageBatchIndex to currentMessageBatchIndex + messageBatchSize. - uint256 public currentMessageBatchIndex; + /// @notice The current message batch index + uint256 public currentBatchIndex; /// @inheritdoc IMessageProcessor uint256 public sbCommitment; @@ -67,6 +63,7 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes vkRegistry = IVkRegistry(_vkRegistry); poll = IPoll(_poll); mode = _mode; + currentBatchIndex = 1; } /// @notice Update the Poll's currentSbCommitment if the proof is valid. @@ -82,72 +79,56 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes revert NoMoreMessages(); } - // The state AccQueue must be merged - if (!poll.stateMerged()) { - revert StateNotMerged(); - } - - // Retrieve stored vals - (, uint8 messageTreeSubDepth, uint8 messageTreeDepth, ) = poll.treeDepths(); - // calculate the message batch size from the message tree subdepth - uint256 messageBatchSize = TREE_ARITY ** messageTreeSubDepth; - - (, AccQueue messageAq) = poll.extContracts(); - - // Require that the message queue has been merged - uint256 messageRoot = messageAq.getMainRoot(messageTreeDepth); - if (messageRoot == 0) { - revert MessageAqNotMerged(); - } + (, uint8 voteOptionTreeDepth) = poll.treeDepths(); + uint8 messageBatchSize = poll.messageBatchSize(); + uint256[] memory batchHashes; // Copy the state and ballot commitment and set the batch index if this // is the first batch to process if (numBatchesProcessed == 0) { uint256 currentSbCommitment = poll.currentSbCommitment(); sbCommitment = currentSbCommitment; - (, uint256 numMessages) = poll.numSignUpsAndMessages(); - uint256 r = numMessages % messageBatchSize; - currentMessageBatchIndex = numMessages; + poll.padLastBatch(); + batchHashes = poll.getBatchHashes(); + currentBatchIndex = batchHashes.length; - if (currentMessageBatchIndex > 0) { - if (r == 0) { - currentMessageBatchIndex -= messageBatchSize; - } else { - currentMessageBatchIndex -= r; - } + if (currentBatchIndex > 0) { + currentBatchIndex -= 1; } + } else { + batchHashes = poll.getBatchHashes(); } - if (!verifyProcessProof(currentMessageBatchIndex, _newSbCommitment, _proof)) { + uint256 outputBatchHash = batchHashes[currentBatchIndex]; + + if ( + !verifyProcessProof( + currentBatchIndex, + outputBatchHash, + sbCommitment, + _newSbCommitment, + messageBatchSize, + voteOptionTreeDepth, + _proof + ) + ) { revert InvalidProcessMessageProof(); } - { - (, uint256 numMessages) = poll.numSignUpsAndMessages(); - // Decrease the message batch start index to ensure that each - // message batch is processed in order - if (currentMessageBatchIndex > 0) { - currentMessageBatchIndex -= messageBatchSize; - } + (, uint256 numMessages) = poll.numSignUpsAndMessages(); - updateMessageProcessingData( - _newSbCommitment, - currentMessageBatchIndex, - numMessages <= messageBatchSize * (numBatchesProcessed + 1) - ); - } + updateMessageProcessingData(_newSbCommitment, numMessages <= messageBatchSize * (numBatchesProcessed + 1)); } /// @inheritdoc IMessageProcessor function getPublicCircuitInputs( uint256 _currentMessageBatchIndex, - uint256 _newSbCommitment + uint256 _newSbCommitment, + uint256 _outputBatchHash ) public view override returns (uint256[] memory publicInputs) { - (, uint8 messageTreeSubDepth, uint8 messageTreeDepth, ) = poll.treeDepths(); - (, AccQueue messageAq) = poll.extContracts(); uint256 coordinatorPubKeyHash = poll.coordinatorPubKeyHash(); - uint256 messageBatchSize = TREE_ARITY ** messageTreeSubDepth; + uint8 messageBatchSize = poll.messageBatchSize(); (uint256 numSignUps, uint256 numMessages) = poll.numSignUpsAndMessages(); (uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration(); uint256 batchEndIndex = _currentMessageBatchIndex + messageBatchSize; @@ -159,7 +140,7 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes publicInputs = new uint256[](9); publicInputs[0] = numSignUps; publicInputs[1] = deployTime + duration; - publicInputs[2] = messageAq.getMainRoot(messageTreeDepth); + publicInputs[2] = _outputBatchHash; publicInputs[3] = poll.actualStateTreeDepth(); publicInputs[4] = batchEndIndex; publicInputs[5] = _currentMessageBatchIndex; @@ -170,27 +151,36 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes /// @notice Verify the proof for processMessage /// @dev used to update the sbCommitment - /// @param _currentMessageBatchIndex The current message batch index - /// @param _newSbCommitment The new state root and ballot root commitment - /// after all messages are processed + /// @param _currentBatchIndex The batch index of current message batch + /// @param _outputBatchHash The output batch hash + /// @param _currentSbCommitment The current sbCommitment (state and ballot) + /// @param _newSbCommitment The new sbCommitment after we update this message batch + /// @param _messageBatchSize The message batch size + /// @param _voteOptionTreeDepth The vote option tree depth /// @param _proof The zk-SNARK proof /// @return isValid Whether the proof is valid function verifyProcessProof( - uint256 _currentMessageBatchIndex, + uint256 _currentBatchIndex, + uint256 _outputBatchHash, + uint256 _currentSbCommitment, uint256 _newSbCommitment, - uint256[8] calldata _proof - ) public view returns (bool isValid) { + uint8 _messageBatchSize, + uint8 _voteOptionTreeDepth, + uint256[8] memory _proof + ) internal view returns (bool isValid) { // get the tree depths - (, uint8 messageTreeSubDepth, uint8 messageTreeDepth, uint8 voteOptionTreeDepth) = poll.treeDepths(); - (IMACI maci, ) = poll.extContracts(); - uint256[] memory publicCircuitInputs = getPublicCircuitInputs(_currentMessageBatchIndex, _newSbCommitment); + // get the message batch size from the message tree subdepth + // get the number of signups + (uint256 numSignUps, uint256 numMessages) = poll.numSignUpsAndMessages(); + IMACI maci = poll.getMaciContract(); + + uint256[] memory publicCircuitInputs = getPublicCircuitInputs(_currentBatchIndex, _newSbCommitment, _outputBatchHash); // Get the verifying key from the VkRegistry VerifyingKey memory vk = vkRegistry.getProcessVk( maci.stateTreeDepth(), - messageTreeDepth, - voteOptionTreeDepth, - TREE_ARITY ** messageTreeSubDepth, + _voteOptionTreeDepth, + _messageBatchSize, mode ); @@ -199,16 +189,11 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes /// @notice update message processing state variables /// @param _newSbCommitment sbCommitment to be updated - /// @param _currentMessageBatchIndex currentMessageBatchIndex to be updated /// @param _processingComplete update flag that indicate processing is finished or not - function updateMessageProcessingData( - uint256 _newSbCommitment, - uint256 _currentMessageBatchIndex, - bool _processingComplete - ) internal { + function updateMessageProcessingData(uint256 _newSbCommitment, bool _processingComplete) internal { sbCommitment = _newSbCommitment; processingComplete = _processingComplete; - currentMessageBatchIndex = _currentMessageBatchIndex; + currentBatchIndex -= 1; numBatchesProcessed++; } } diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index ba276915a0..d3ef94cede 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Params } from "./utilities/Params.sol"; +import { EmptyBallotRoots } from "./trees/EmptyBallotRoots.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { IPoll } from "./interfaces/IPoll.sol"; +import { IMACI } from "./interfaces/IMACI.sol"; import { Utilities } from "./utilities/Utilities.sol"; import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol"; @@ -12,7 +15,7 @@ import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol"; /// which can be either votes or key change messages. /// @dev Do not deploy this directly. Use PollFactory.deploy() which performs some /// checks on the Poll constructor arguments. -contract Poll is Params, Utilities, SnarkCommon, IPoll { +contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { /// @notice Whether the Poll has been initialized bool internal isInit; @@ -31,9 +34,6 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { // The duration of the polling period, in seconds uint256 internal immutable duration; - /// @notice The root of the empty ballot tree at a given voteOptionTree depth - uint256 public immutable emptyBallotRoot; - /// @notice Whether the MACI contract's stateAq has been merged by this contract bool public stateMerged; @@ -57,17 +57,26 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { /// to be used as public input for the circuit uint8 public actualStateTreeDepth; + /// @notice Max vote options for the poll + uint256 public immutable maxVoteOptions; + /// @notice Depths of the merkle trees TreeDepths public treeDepths; + /// @notice Message batch size for the poll + uint8 public immutable messageBatchSize; + /// @notice The contracts used by the Poll ExtContracts public extContracts; - /// @notice The max number of messages - uint256 public immutable maxMessages; + /// @notice The array for chain hash checkpoints + uint256[] public batchHashes; + + /// @notice Current chain hash + uint256 public chainHash; - /// @notice The number of children per node in the merkle trees - uint256 internal constant TREE_ARITY = 5; + /// @notice flag for batch padding + bool public isBatchHashesPadded; error VotingPeriodOver(); error VotingPeriodNotOver(); @@ -76,24 +85,27 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { error InvalidPubKey(); error StateAlreadyMerged(); error InvalidBatchLength(); + error BatchHashesAlreadyPadded(); event PublishMessage(Message _message, PubKey _encPubKey); event MergeMaciState(uint256 indexed _stateRoot, uint256 indexed _numSignups); - event MergeMessageAqSubRoots(uint256 indexed _numSrQueueOps); - event MergeMessageAq(uint256 indexed _messageRoot); /// @notice Each MACI instance can have multiple Polls. /// When a Poll is deployed, its voting period starts immediately. /// @param _duration The duration of the voting period, in seconds + /// @param _maxVoteOptions The maximum number of vote options /// @param _treeDepths The depths of the merkle trees + /// @param _messageBatchSize The message batch size /// @param _coordinatorPubKey The coordinator's public key /// @param _extContracts The external contracts + constructor( uint256 _duration, + uint256 _maxVoteOptions, TreeDepths memory _treeDepths, + uint8 _messageBatchSize, PubKey memory _coordinatorPubKey, - ExtContracts memory _extContracts, - uint256 _emptyBallotRoot + ExtContracts memory _extContracts ) payable { // check that the coordinator public key is valid if (!CurveBabyJubJub.isOnCurve(_coordinatorPubKey.x, _coordinatorPubKey.y)) { @@ -108,14 +120,14 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { extContracts = _extContracts; // store duration of the poll duration = _duration; + // store max vote options + maxVoteOptions = _maxVoteOptions; + // store message batch size + messageBatchSize = _messageBatchSize; // store tree depth treeDepths = _treeDepths; // Record the current timestamp deployTime = block.timestamp; - // store the empty ballot root - emptyBallotRoot = _emptyBallotRoot; - // store max messages - maxMessages = TREE_ARITY ** _treeDepths.messageTreeDepth; } /// @notice A modifier that causes the function to revert if the voting period is @@ -126,6 +138,11 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { _; } + modifier isNotPadded() { + if (isBatchHashesPadded) revert BatchHashesAlreadyPadded(); + _; + } + /// @notice A modifier that causes the function to revert if the voting period is /// over modifier isWithinVotingDeadline() virtual { @@ -136,7 +153,6 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { /// @notice The initialization function. /// @dev Should be called immediately after Poll creation - /// and messageAq ownership transferred function init() public virtual { if (isInit) revert PollAlreadyInit(); // set to true so it cannot be called again @@ -146,22 +162,26 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { numMessages++; } - // init messageAq here by inserting placeholderLeaf + // init chainHash here by inserting placeholderLeaf uint256[2] memory dat; dat[0] = NOTHING_UP_MY_SLEEVE; dat[1] = 0; (Message memory _message, PubKey memory _padKey, uint256 placeholderLeaf) = padAndHashMessage(dat); - extContracts.messageAq.enqueue(placeholderLeaf); + chainHash = NOTHING_UP_MY_SLEEVE; + batchHashes.push(NOTHING_UP_MY_SLEEVE); + updateChainHash(placeholderLeaf); emit PublishMessage(_message, _padKey); } + // get all batch hash array elements + function getBatchHashes() external view returns (uint256[] memory) { + return batchHashes; + } + /// @inheritdoc IPoll function publishMessage(Message memory _message, PubKey calldata _encPubKey) public virtual isWithinVotingDeadline { - // we check that we do not exceed the max number of messages - if (numMessages >= maxMessages) revert TooManyMessages(); - // check if the public key is on the curve if (!CurveBabyJubJub.isOnCurve(_encPubKey.x, _encPubKey.y)) { revert InvalidPubKey(); @@ -172,12 +192,33 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { numMessages++; } - uint256 messageLeaf = hashMessageAndEncPubKey(_message, _encPubKey); - extContracts.messageAq.enqueue(messageLeaf); + // compute current message hash + uint256 messageHash = hashMessageAndEncPubKey(_message, _encPubKey); + + // update current message chain hash + updateChainHash(messageHash); emit PublishMessage(_message, _encPubKey); } + /// @notice compute and update current message chain hash + /// @param messageHash hash of the current message + function updateChainHash(uint256 messageHash) internal { + uint256 newChainHash = hash2([chainHash, messageHash]); + if (numMessages % messageBatchSize == 0) { + batchHashes.push(newChainHash); + } + chainHash = newChainHash; + } + + /// @notice pad last unclosed batch + function padLastBatch() external isAfterVotingDeadline isNotPadded { + if (numMessages % messageBatchSize != 0) { + batchHashes.push(chainHash); + } + isBatchHashesPadded = true; + } + /// @notice submit a message batch /// @dev Can only be submitted before the voting deadline /// @param _messages the messages @@ -216,7 +257,7 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { // Set currentSbCommitment uint256[3] memory sb; sb[0] = _mergedStateRoot; - sb[1] = emptyBallotRoot; + sb[1] = emptyBallotRoots[treeDepths.voteOptionTreeDepth - 1]; sb[2] = uint256(0); currentSbCommitment = hash3(sb); @@ -236,18 +277,6 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { emit MergeMaciState(_mergedStateRoot, _numSignups); } - /// @inheritdoc IPoll - function mergeMessageAqSubRoots(uint256 _numSrQueueOps) public isAfterVotingDeadline { - extContracts.messageAq.mergeSubRoots(_numSrQueueOps); - emit MergeMessageAqSubRoots(_numSrQueueOps); - } - - /// @inheritdoc IPoll - function mergeMessageAq() public isAfterVotingDeadline { - uint256 root = extContracts.messageAq.merge(treeDepths.messageTreeDepth); - emit MergeMessageAq(root); - } - /// @inheritdoc IPoll function getDeployTimeAndDuration() public view virtual returns (uint256 pollDeployTime, uint256 pollDuration) { pollDeployTime = deployTime; @@ -259,4 +288,9 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { numSUps = numSignups; numMsgs = numMessages; } + + /// @inheritdoc IPoll + function getMaciContract() public view returns (IMACI maci) { + return extContracts.maci; + } } diff --git a/packages/contracts/contracts/PollFactory.sol b/packages/contracts/contracts/PollFactory.sol index bca7b98f8c..1d0c46a27e 100644 --- a/packages/contracts/contracts/PollFactory.sol +++ b/packages/contracts/contracts/PollFactory.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.20; import { IMACI } from "./interfaces/IMACI.sol"; -import { AccQueue } from "./trees/AccQueue.sol"; -import { AccQueueQuinaryMaci } from "./trees/AccQueueQuinaryMaci.sol"; import { Params } from "./utilities/Params.sol"; import { DomainObjs } from "./utilities/DomainObjs.sol"; import { Poll } from "./Poll.sol"; @@ -20,23 +18,14 @@ contract PollFactory is Params, DomainObjs, IPollFactory { /// @inheritdoc IPollFactory function deploy( uint256 _duration, - TreeDepths calldata _treeDepths, - PubKey calldata _coordinatorPubKey, - address _maci, - uint256 _emptyBallotRoot + uint256 _maxVoteOptions, + Params.TreeDepths calldata _treeDepths, + uint8 _messageBatchSize, + DomainObjs.PubKey calldata _coordinatorPubKey, + Params.ExtContracts calldata _extContracts ) public virtual returns (address pollAddr) { - /// @notice deploy a new AccQueue contract to store messages - AccQueue messageAq = new AccQueueQuinaryMaci(_treeDepths.messageTreeSubDepth); - - /// @notice the smart contracts that a Poll would interact with - ExtContracts memory extContracts = ExtContracts({ maci: IMACI(_maci), messageAq: messageAq }); - // deploy the poll - Poll poll = new Poll(_duration, _treeDepths, _coordinatorPubKey, extContracts, _emptyBallotRoot); - - // Make the Poll contract own the messageAq contract, so only it can - // run enqueue/merge - messageAq.transferOwnership(address(poll)); + Poll poll = new Poll(_duration, _maxVoteOptions, _treeDepths, _messageBatchSize, _coordinatorPubKey, _extContracts); // init Poll poll.init(); diff --git a/packages/contracts/contracts/Tally.sol b/packages/contracts/contracts/Tally.sol index e92ce42973..06673822be 100644 --- a/packages/contracts/contracts/Tally.sol +++ b/packages/contracts/contracts/Tally.sol @@ -105,7 +105,7 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs, ITa /// @notice Check if all ballots are tallied /// @return tallied whether all ballots are tallied function isTallied() public view returns (bool tallied) { - (uint8 intStateTreeDepth, , , ) = poll.treeDepths(); + (uint8 intStateTreeDepth, ) = poll.treeDepths(); (uint256 numSignUps, ) = poll.numSignUpsAndMessages(); // Require that there are untallied ballots left @@ -132,7 +132,7 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs, ITa updateSbCommitment(); // get the batch size and start index - (uint8 intStateTreeDepth, , , ) = poll.treeDepths(); + (uint8 intStateTreeDepth, ) = poll.treeDepths(); uint256 tallyBatchSize = TREE_ARITY ** intStateTreeDepth; uint256 batchStartIndex = tallyBatchNum * tallyBatchSize; @@ -184,9 +184,9 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs, ITa uint256 _newTallyCommitment, uint256[8] calldata _proof ) public view returns (bool isValid) { - (uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = poll.treeDepths(); - (IMACI maci, ) = poll.extContracts(); + (uint8 intStateTreeDepth, uint8 voteOptionTreeDepth) = poll.treeDepths(); uint256[] memory circuitPublicInputs = getPublicCircuitInputs(_batchStartIndex, _newTallyCommitment); + IMACI maci = poll.getMaciContract(); // Get the verifying key VerifyingKey memory vk = vkRegistry.getTallyVk(maci.stateTreeDepth(), intStateTreeDepth, voteOptionTreeDepth, mode); @@ -373,7 +373,7 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs, ITa revert VotesNotTallied(); } - (, , , uint8 voteOptionTreeDepth) = poll.treeDepths(); + (, uint8 voteOptionTreeDepth) = poll.treeDepths(); uint256 voteOptionsLength = args.voteOptionIndices.length; for (uint256 i = 0; i < voteOptionsLength; ) { diff --git a/packages/contracts/contracts/VkRegistry.sol b/packages/contracts/contracts/VkRegistry.sol index f0b26d9e78..d69c17eb6c 100644 --- a/packages/contracts/contracts/VkRegistry.sol +++ b/packages/contracts/contracts/VkRegistry.sol @@ -49,16 +49,14 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry /// @notice generate the signature for the process verifying key /// @param _stateTreeDepth The state tree depth - /// @param _messageTreeDepth The message tree depth /// @param _voteOptionTreeDepth The vote option tree depth /// @param _messageBatchSize The message batch size function genProcessVkSig( uint256 _stateTreeDepth, - uint256 _messageTreeDepth, uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize + uint8 _messageBatchSize ) public pure returns (uint256 sig) { - sig = (_messageBatchSize << 192) + (_stateTreeDepth << 128) + (_messageTreeDepth << 64) + _voteOptionTreeDepth; + sig = (_messageBatchSize << 128) + (_stateTreeDepth << 64) + _voteOptionTreeDepth; } /// @notice generate the signature for the tally verifying key @@ -78,7 +76,6 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry /// of parameters and modes /// @param _stateTreeDepth The state tree depth /// @param _intStateTreeDepth The intermediate state tree depth - /// @param _messageTreeDepth The message tree depth /// @param _voteOptionTreeDepth The vote option tree depth /// @param _messageBatchSize The message batch size /// @param _modes Array of QV or Non-QV modes (must have the same length as process and tally keys) @@ -87,9 +84,8 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry function setVerifyingKeysBatch( uint256 _stateTreeDepth, uint256 _intStateTreeDepth, - uint256 _messageTreeDepth, uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, + uint8 _messageBatchSize, Mode[] calldata _modes, VerifyingKey[] calldata _processVks, VerifyingKey[] calldata _tallyVks @@ -104,7 +100,6 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry setVerifyingKeys( _stateTreeDepth, _intStateTreeDepth, - _messageTreeDepth, _voteOptionTreeDepth, _messageBatchSize, _modes[index], @@ -122,7 +117,6 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry /// of parameters /// @param _stateTreeDepth The state tree depth /// @param _intStateTreeDepth The intermediate state tree depth - /// @param _messageTreeDepth The message tree depth /// @param _voteOptionTreeDepth The vote option tree depth /// @param _messageBatchSize The message batch size /// @param _mode QV or Non-QV @@ -131,14 +125,13 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry function setVerifyingKeys( uint256 _stateTreeDepth, uint256 _intStateTreeDepth, - uint256 _messageTreeDepth, uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, + uint8 _messageBatchSize, Mode _mode, VerifyingKey calldata _processVk, VerifyingKey calldata _tallyVk ) public onlyOwner { - uint256 processVkSig = genProcessVkSig(_stateTreeDepth, _messageTreeDepth, _voteOptionTreeDepth, _messageBatchSize); + uint256 processVkSig = genProcessVkSig(_stateTreeDepth, _voteOptionTreeDepth, _messageBatchSize); if (processVkSet[_mode][processVkSig]) revert ProcessVkAlreadySet(); @@ -186,19 +179,17 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry /// @notice Check if the process verifying key is set /// @param _stateTreeDepth The state tree depth - /// @param _messageTreeDepth The message tree depth /// @param _voteOptionTreeDepth The vote option tree depth /// @param _messageBatchSize The message batch size /// @param _mode QV or Non-QV /// @return isSet whether the verifying key is set function hasProcessVk( uint256 _stateTreeDepth, - uint256 _messageTreeDepth, uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, + uint8 _messageBatchSize, Mode _mode ) public view returns (bool isSet) { - uint256 sig = genProcessVkSig(_stateTreeDepth, _messageTreeDepth, _voteOptionTreeDepth, _messageBatchSize); + uint256 sig = genProcessVkSig(_stateTreeDepth, _voteOptionTreeDepth, _messageBatchSize); isSet = processVkSet[_mode][sig]; } @@ -215,12 +206,11 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry /// @inheritdoc IVkRegistry function getProcessVk( uint256 _stateTreeDepth, - uint256 _messageTreeDepth, uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, + uint8 _messageBatchSize, Mode _mode ) public view returns (VerifyingKey memory vk) { - uint256 sig = genProcessVkSig(_stateTreeDepth, _messageTreeDepth, _voteOptionTreeDepth, _messageBatchSize); + uint256 sig = genProcessVkSig(_stateTreeDepth, _voteOptionTreeDepth, _messageBatchSize); vk = getProcessVkBySig(sig, _mode); } diff --git a/packages/contracts/contracts/interfaces/IMessageProcessor.sol b/packages/contracts/contracts/interfaces/IMessageProcessor.sol index f3eeb89a66..710aaed73c 100644 --- a/packages/contracts/contracts/interfaces/IMessageProcessor.sol +++ b/packages/contracts/contracts/interfaces/IMessageProcessor.sol @@ -16,9 +16,11 @@ interface IMessageProcessor { /// @param _currentMessageBatchIndex The current processed batch index /// @param _newSbCommitment The new state root and ballot root commitment /// after all messages are processed + /// @param _outputBatchHash The output batch hash /// @return publicInputs public circuit inputs function getPublicCircuitInputs( uint256 _currentMessageBatchIndex, - uint256 _newSbCommitment + uint256 _newSbCommitment, + uint256 _outputBatchHash ) external view returns (uint256[] memory); } diff --git a/packages/contracts/contracts/interfaces/IPoll.sol b/packages/contracts/contracts/interfaces/IPoll.sol index df15584d3e..00a7942ed3 100644 --- a/packages/contracts/contracts/interfaces/IPoll.sol +++ b/packages/contracts/contracts/interfaces/IPoll.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.10; import { DomainObjs } from "../utilities/DomainObjs.sol"; import { IMACI } from "./IMACI.sol"; -import { AccQueue } from "../trees/AccQueue.sol"; /// @title IPoll /// @notice Poll interface @@ -13,6 +12,13 @@ interface IPoll { /// @return numMsgs The number of messages sent by voters function numSignUpsAndMessages() external view returns (uint256 numSignups, uint256 numMsgs); + /// @notice Get all message batch hashes + /// @return betchHashes array containing all batch hashes + function getBatchHashes() external view returns (uint256[] memory); + + /// @notice Pad last unclosed batch + function padLastBatch() external; + /// @notice Allows anyone to publish a message (an encrypted command and signature). /// This function also enqueues the message. /// @param _message The message to publish @@ -21,20 +27,11 @@ interface IPoll { /// to encrypt the message. function publishMessage(DomainObjs.Message memory _message, DomainObjs.PubKey calldata _encPubKey) external; - /// @notice The second step of merging the MACI state AccQueue. This allows the + /// @notice The second step of merging the MACI state. This allows the /// ProcessMessages circuit to access the latest state tree and ballots via /// currentSbCommitment. function mergeMaciState() external; - /// @notice The first step in merging the message AccQueue so that the - /// ProcessMessages circuit can access the message root. - /// @param _numSrQueueOps The number of subroot queue operations to perform - function mergeMessageAqSubRoots(uint256 _numSrQueueOps) external; - - /// @notice The second step in merging the message AccQueue so that the - /// ProcessMessages circuit can access the message root. - function mergeMessageAq() external; - /// @notice Returns the Poll's deploy time and duration /// @return _deployTime The deployment timestamp /// @return _duration The duration of the poll @@ -46,18 +43,16 @@ interface IPoll { /// @notice Get the depths of the merkle trees /// @return intStateTreeDepth The depth of the state tree - /// @return messageTreeSubDepth The subdepth of the message tree - /// @return messageTreeDepth The depth of the message tree /// @return voteOptionTreeDepth The subdepth of the vote option tree - function treeDepths() - external - view - returns (uint8 intStateTreeDepth, uint8 messageTreeSubDepth, uint8 messageTreeDepth, uint8 voteOptionTreeDepth); + function treeDepths() external view returns (uint8 intStateTreeDepth, uint8 voteOptionTreeDepth); - /// @notice Get the external contracts - /// @return maci The IMACI contract - /// @return messageAq The AccQueue contract - function extContracts() external view returns (IMACI maci, AccQueue messageAq); + /// @notice Get the max vote options for the poll + /// @return maxVoteOptions The maximum number of vote options + function maxVoteOptions() external view returns (uint256); + + /// @notice Get message batch size for the poll + /// @return The message batch size + function messageBatchSize() external view returns (uint8); /// @notice Get the hash of coordinator's public key /// @return _coordinatorPubKeyHash the hash of coordinator's public key @@ -76,4 +71,8 @@ interface IPoll { /// @notice Get the dynamic depth of the state tree at the time of poll /// finalization (based on the number of leaves inserted) function actualStateTreeDepth() external view returns (uint8); + + /// @notice Get the external contracts + /// @return maci The IMACI contract + function getMaciContract() external view returns (IMACI maci); } diff --git a/packages/contracts/contracts/interfaces/IPollFactory.sol b/packages/contracts/contracts/interfaces/IPollFactory.sol index 79b3663aac..8d8351af23 100644 --- a/packages/contracts/contracts/interfaces/IPollFactory.sol +++ b/packages/contracts/contracts/interfaces/IPollFactory.sol @@ -7,18 +7,20 @@ import { DomainObjs } from "../utilities/DomainObjs.sol"; /// @title IPollFactory /// @notice PollFactory interface interface IPollFactory { - /// @notice Deploy a new Poll contract and AccQueue contract for messages. + /// @notice Deploy a new Poll contract /// @param _duration The duration of the poll + /// @param _maxVoteOptions The max vote options for the poll /// @param _treeDepths The depths of the merkle trees + /// @param _messageBatchSize The size of message batch /// @param _coordinatorPubKey The coordinator's public key - /// @param _maci The MACI contract interface reference - /// @param _emptyBallotRoot The root of the empty ballot tree + /// @param _extContracts The external contract interface references /// @return The deployed Poll contract function deploy( uint256 _duration, - Params.TreeDepths memory _treeDepths, - DomainObjs.PubKey memory _coordinatorPubKey, - address _maci, - uint256 _emptyBallotRoot + uint256 _maxVoteOptions, + Params.TreeDepths calldata _treeDepths, + uint8 _messageBatchSize, + DomainObjs.PubKey calldata _coordinatorPubKey, + Params.ExtContracts calldata _extContracts ) external returns (address); } diff --git a/packages/contracts/contracts/interfaces/IVkRegistry.sol b/packages/contracts/contracts/interfaces/IVkRegistry.sol index 3c2d22ec00..5d0437d972 100644 --- a/packages/contracts/contracts/interfaces/IVkRegistry.sol +++ b/packages/contracts/contracts/interfaces/IVkRegistry.sol @@ -22,16 +22,14 @@ interface IVkRegistry { /// @notice Get the process verifying key /// @param _stateTreeDepth The state tree depth - /// @param _messageTreeDepth The message tree depth /// @param _voteOptionTreeDepth The vote option tree depth /// @param _messageBatchSize The message batch size /// @param _mode QV or Non-QV /// @return The verifying key function getProcessVk( uint256 _stateTreeDepth, - uint256 _messageTreeDepth, uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, + uint8 _messageBatchSize, DomainObjs.Mode _mode ) external view returns (SnarkCommon.VerifyingKey memory); } diff --git a/packages/contracts/contracts/trees/AccQueue.sol b/packages/contracts/contracts/trees/AccQueue.sol deleted file mode 100644 index 829b9319c6..0000000000 --- a/packages/contracts/contracts/trees/AccQueue.sol +++ /dev/null @@ -1,467 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { Hasher } from "../crypto/Hasher.sol"; - -/// @title AccQueue -/// @notice This contract defines a Merkle tree where each leaf insertion only updates a -/// subtree. To obtain the main tree root, the contract owner must merge the -/// subtrees together. Merging subtrees requires at least 2 operations: -/// mergeSubRoots(), and merge(). To get around the gas limit, -/// the mergeSubRoots() can be performed in multiple transactions. -abstract contract AccQueue is Ownable(msg.sender), Hasher { - // The maximum tree depth - uint256 public constant MAX_DEPTH = 32; - - /// @notice A Queue is a 2D array of Merkle roots and indices which represents nodes - /// in a Merkle tree while it is progressively updated. - struct Queue { - /// @notice IMPORTANT: the following declares an array of b elements of type T: T[b] - /// And the following declares an array of b elements of type T[a]: T[a][b] - /// As such, the following declares an array of MAX_DEPTH+1 arrays of - /// uint256[4] arrays, **not the other way round**: - uint256[4][MAX_DEPTH + 1] levels; - uint256[MAX_DEPTH + 1] indices; - } - - // The depth of each subtree - uint256 internal immutable subDepth; - - // The number of elements per hash operation. Should be either 2 (for - // binary trees) or 5 (quinary trees). The limit is 5 because that is the - // maximum supported number of inputs for the EVM implementation of the - // Poseidon hash function - uint256 internal immutable hashLength; - - // hashLength ** subDepth - uint256 internal immutable subTreeCapacity; - - // True hashLength == 2, false if hashLength == 5 - bool internal immutable isBinary; - - // The index of the current subtree. e.g. the first subtree has index 0, the - // second has 1, and so on - uint256 internal currentSubtreeIndex; - - // Tracks the current subtree. - Queue internal leafQueue; - - // Tracks the smallest tree of subroots - Queue internal subRootQueue; - - // Subtree roots - mapping(uint256 => uint256) internal subRoots; - - // Merged roots - uint256[MAX_DEPTH + 1] internal mainRoots; - - // Whether the subtrees have been merged - bool public subTreesMerged; - - // Whether entire merkle tree has been merged - bool public treeMerged; - - // The root of the shortest possible tree which fits all current subtree - // roots - uint256 internal smallSRTroot; - - // Tracks the next subroot to queue - uint256 internal nextSubRootIndex; - - // The number of leaves inserted across all subtrees so far - uint256 public numLeaves; - - /// @notice custom errors - error SubDepthCannotBeZero(); - error SubdepthTooLarge(uint256 _subDepth, uint256 max); - error InvalidHashLength(); - error DepthCannotBeZero(); - error SubTreesAlreadyMerged(); - error NothingToMerge(); - error SubTreesNotMerged(); - error DepthTooLarge(uint256 _depth, uint256 max); - error DepthTooSmall(uint256 _depth, uint256 min); - error InvalidIndex(uint256 _index); - error InvalidLevel(); - - /// @notice Create a new AccQueue - /// @param _subDepth The depth of each subtree. - /// @param _hashLength The number of leaves per node (2 or 5). - constructor(uint256 _subDepth, uint256 _hashLength) payable { - /// validation - if (_subDepth == 0) revert SubDepthCannotBeZero(); - if (_subDepth > MAX_DEPTH) revert SubdepthTooLarge(_subDepth, MAX_DEPTH); - if (_hashLength != 2 && _hashLength != 5) revert InvalidHashLength(); - - isBinary = _hashLength == 2; - subDepth = _subDepth; - hashLength = _hashLength; - subTreeCapacity = _hashLength ** _subDepth; - } - - /// @notice Hash the contents of the specified level and the specified leaf. - /// This is a virtual function as the hash function which the overriding - /// contract uses will be either hashLeftRight or hash5, which require - /// different input array lengths. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return _hash The hash of the level and leaf. - // solhint-disable-next-line no-empty-blocks - function hashLevel(uint256 _level, uint256 _leaf) internal virtual returns (uint256 _hash) {} - - /// @notice Hash the contents of the specified level and the specified leaf. - /// This is a virtual function as the hash function which the overriding - /// contract uses will be either hashLeftRight or hash5, which require - /// different input array lengths. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return _hash The hash of the level and leaf. - // solhint-disable-next-line no-empty-blocks - function hashLevelLeaf(uint256 _level, uint256 _leaf) public view virtual returns (uint256 _hash) {} - - /// @notice Returns the zero leaf at a specified level. - /// This is a virtual function as the hash function which the overriding - /// contract uses will be either hashLeftRight or hash5, which will produce - /// different zero values (e.g. hashLeftRight(0, 0) vs - /// hash5([0, 0, 0, 0, 0]). Moreover, the zero value may be a - /// nothing-up-my-sleeve value. - /// @param _level The level at which to return the zero leaf. - /// @return zero The zero leaf at the specified level. - // solhint-disable-next-line no-empty-blocks - function getZero(uint256 _level) internal virtual returns (uint256 zero) {} - - /// @notice Add a leaf to the queue for the current subtree. - /// @param _leaf The leaf to add. - /// @return leafIndex The index of the leaf in the queue. - function enqueue(uint256 _leaf) public onlyOwner returns (uint256 leafIndex) { - leafIndex = numLeaves; - // Recursively queue the leaf - _enqueue(_leaf, 0); - - // Update the leaf counter - numLeaves = leafIndex + 1; - - // Now that a new leaf has been added, mainRoots and smallSRTroot are - // obsolete - delete mainRoots; - delete smallSRTroot; - subTreesMerged = false; - - // If a subtree is full - if (numLeaves % subTreeCapacity == 0) { - // Store the subroot - subRoots[currentSubtreeIndex] = leafQueue.levels[subDepth][0]; - - // Increment the index - currentSubtreeIndex++; - - // Delete ancillary data - delete leafQueue.levels[subDepth][0]; - delete leafQueue.indices; - } - } - - /// @notice Updates the queue at a given level and hashes any subroots - /// that need to be hashed. - /// @param _leaf The leaf to add. - /// @param _level The level at which to queue the leaf. - function _enqueue(uint256 _leaf, uint256 _level) internal { - if (_level > subDepth) { - revert InvalidLevel(); - } - - while (true) { - uint256 n = leafQueue.indices[_level]; - - if (n != hashLength - 1) { - // Just store the leaf - leafQueue.levels[_level][n] = _leaf; - - if (_level != subDepth) { - // Update the index - leafQueue.indices[_level]++; - } - - return; - } - - // Hash the leaves to next level - _leaf = hashLevel(_level, _leaf); - - // Reset the index for this level - delete leafQueue.indices[_level]; - - // Queue the hash of the leaves into to the next level - _level++; - } - } - - /// @notice Fill any empty leaves of the current subtree with zeros and store the - /// resulting subroot. - function fill() public onlyOwner { - if (numLeaves % subTreeCapacity == 0) { - // If the subtree is completely empty, then the subroot is a - // precalculated zero value - subRoots[currentSubtreeIndex] = getZero(subDepth); - } else { - // Otherwise, fill the rest of the subtree with zeros - _fill(0); - - // Store the subroot - subRoots[currentSubtreeIndex] = leafQueue.levels[subDepth][0]; - - // Reset the subtree data - delete leafQueue.levels; - - // Reset the merged roots - delete mainRoots; - } - - // Increment the subtree index - uint256 curr = currentSubtreeIndex + 1; - currentSubtreeIndex = curr; - - // Update the number of leaves - numLeaves = curr * subTreeCapacity; - - // Reset the subroot tree root now that it is obsolete - delete smallSRTroot; - - subTreesMerged = false; - } - - /// @notice A function that queues zeros to the specified level, hashes, - /// the level, and enqueues the hash to the next level. - /// @param _level The level at which to queue zeros. - // solhint-disable-next-line no-empty-blocks - function _fill(uint256 _level) internal virtual {} - - /// Insert a subtree. Used for batch enqueues. - function insertSubTree(uint256 _subRoot) public onlyOwner { - subRoots[currentSubtreeIndex] = _subRoot; - - // Increment the subtree index - currentSubtreeIndex++; - - // Update the number of leaves - numLeaves += subTreeCapacity; - - // Reset the subroot tree root now that it is obsolete - delete smallSRTroot; - - subTreesMerged = false; - } - - /// @notice Calculate the lowest possible height of a tree with - /// all the subroots merged together. - /// @return depth The lowest possible height of a tree with all the - function calcMinHeight() public view returns (uint256 depth) { - depth = 1; - while (true) { - if (hashLength ** depth >= currentSubtreeIndex) { - break; - } - depth++; - } - } - - /// @notice Merge all subtrees to form the shortest possible tree. - /// This function can be called either once to merge all subtrees in a - /// single transaction, or multiple times to do the same in multiple - /// transactions. - /// @param _numSrQueueOps The number of times this function will call - /// queueSubRoot(), up to the maximum number of times - /// necessary. If it is set to 0, it will call - /// queueSubRoot() as many times as is necessary. Set - /// this to a low number and call this function - /// multiple times if there are many subroots to - /// merge, or a single transaction could run out of - /// gas. - function mergeSubRoots(uint256 _numSrQueueOps) public onlyOwner { - // This function can only be called once unless a new subtree is created - if (subTreesMerged) revert SubTreesAlreadyMerged(); - - // There must be subtrees to merge - if (numLeaves == 0) revert NothingToMerge(); - - // Fill any empty leaves in the current subtree with zeros only if the - // current subtree is not full - if (numLeaves % subTreeCapacity != 0) { - fill(); - } - - // If there is only 1 subtree, use its root - if (currentSubtreeIndex == 1) { - smallSRTroot = getSubRoot(0); - subTreesMerged = true; - return; - } - - uint256 depth = calcMinHeight(); - - uint256 queueOpsPerformed = 0; - for (uint256 i = nextSubRootIndex; i < currentSubtreeIndex; i++) { - if (_numSrQueueOps != 0 && queueOpsPerformed == _numSrQueueOps) { - // If the limit is not 0, stop if the limit has been reached - return; - } - - // Queue the next subroot - queueSubRoot(getSubRoot(nextSubRootIndex), 0, depth); - - // Increment the next subroot counter - nextSubRootIndex++; - - // Increment the ops counter - queueOpsPerformed++; - } - - // The height of the tree of subroots - uint256 m = hashLength ** depth; - - // Queue zeroes to fill out the SRT - if (nextSubRootIndex == currentSubtreeIndex) { - uint256 z = getZero(subDepth); - for (uint256 i = currentSubtreeIndex; i < m; i++) { - queueSubRoot(z, 0, depth); - } - } - - // Store the smallest main root - smallSRTroot = subRootQueue.levels[depth][0]; - subTreesMerged = true; - } - - /// @notice Queues a subroot into the subroot tree. - /// @param _leaf The value to queue. - /// @param _level The level at which to queue _leaf. - /// @param _maxDepth The depth of the tree. - function queueSubRoot(uint256 _leaf, uint256 _level, uint256 _maxDepth) internal { - if (_level > _maxDepth) { - return; - } - - uint256 n = subRootQueue.indices[_level]; - - if (n != hashLength - 1) { - // Just store the leaf - subRootQueue.levels[_level][n] = _leaf; - subRootQueue.indices[_level]++; - } else { - // Hash the elements in this level and queue it in the next level - uint256 hashed; - if (isBinary) { - uint256[2] memory inputs; - inputs[0] = subRootQueue.levels[_level][0]; - inputs[1] = _leaf; - hashed = hash2(inputs); - } else { - uint256[5] memory inputs; - for (uint8 i = 0; i < n; i++) { - inputs[i] = subRootQueue.levels[_level][i]; - } - inputs[n] = _leaf; - hashed = hash5(inputs); - } - - // TODO: change recursion to a while loop - // Recurse - delete subRootQueue.indices[_level]; - queueSubRoot(hashed, _level + 1, _maxDepth); - } - } - - /// @notice Merge all subtrees to form a main tree with a desired depth. - /// @param _depth The depth of the main tree. It must fit all the leaves or - /// this function will revert. - /// @return root The root of the main tree. - function merge(uint256 _depth) public onlyOwner returns (uint256 root) { - // The tree depth must be more than 0 - if (_depth == 0) revert DepthCannotBeZero(); - - // Ensure that the subtrees have been merged - if (!subTreesMerged) revert SubTreesNotMerged(); - - // Check the depth - if (_depth > MAX_DEPTH) revert DepthTooLarge(_depth, MAX_DEPTH); - - // Calculate the SRT depth - uint256 srtDepth = subDepth; - while (true) { - if (hashLength ** srtDepth >= numLeaves) { - break; - } - srtDepth++; - } - - if (_depth < srtDepth) revert DepthTooSmall(_depth, srtDepth); - - // If the depth is the same as the SRT depth, just use the SRT root - if (_depth == srtDepth) { - mainRoots[_depth] = smallSRTroot; - treeMerged = true; - return smallSRTroot; - } else { - root = smallSRTroot; - - // Calculate the main root - - for (uint256 i = srtDepth; i < _depth; i++) { - uint256 z = getZero(i); - - if (isBinary) { - uint256[2] memory inputs; - inputs[0] = root; - inputs[1] = z; - root = hash2(inputs); - } else { - uint256[5] memory inputs; - inputs[0] = root; - inputs[1] = z; - inputs[2] = z; - inputs[3] = z; - inputs[4] = z; - root = hash5(inputs); - } - } - - mainRoots[_depth] = root; - treeMerged = true; - } - } - - /// @notice Returns the subroot at the specified index. Reverts if the index refers - /// to a subtree which has not been filled yet. - /// @param _index The subroot index. - /// @return subRoot The subroot at the specified index. - function getSubRoot(uint256 _index) public view returns (uint256 subRoot) { - if (currentSubtreeIndex <= _index) revert InvalidIndex(_index); - subRoot = subRoots[_index]; - } - - /// @notice Returns the subroot tree (SRT) root. Its value must first be computed - /// using mergeSubRoots. - /// @return smallSubTreeRoot The SRT root. - function getSmallSRTroot() public view returns (uint256 smallSubTreeRoot) { - if (!subTreesMerged) revert SubTreesNotMerged(); - smallSubTreeRoot = smallSRTroot; - } - - /// @notice Return the merged Merkle root of all the leaves at a desired depth. - /// @dev merge() or merged(_depth) must be called first. - /// @param _depth The depth of the main tree. It must first be computed - /// using mergeSubRoots() and merge(). - /// @return mainRoot The root of the main tree. - function getMainRoot(uint256 _depth) public view returns (uint256 mainRoot) { - if (hashLength ** _depth < numLeaves) revert DepthTooSmall(_depth, numLeaves); - - mainRoot = mainRoots[_depth]; - } - - /// @notice Get the next subroot index and the current subtree index. - function getSrIndices() public view returns (uint256 next, uint256 current) { - next = nextSubRootIndex; - current = currentSubtreeIndex; - } -} diff --git a/packages/contracts/contracts/trees/AccQueueBinary.sol b/packages/contracts/contracts/trees/AccQueueBinary.sol deleted file mode 100644 index c652b18bd7..0000000000 --- a/packages/contracts/contracts/trees/AccQueueBinary.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import { AccQueue } from "./AccQueue.sol"; - -/// @title AccQueueBinary -/// @notice This contract defines a Merkle tree where each leaf insertion only updates a -/// subtree. To obtain the main tree root, the contract owner must merge the -/// subtrees together. Merging subtrees requires at least 2 operations: -/// mergeSubRoots(), and merge(). To get around the gas limit, -/// the mergeSubRoots() can be performed in multiple transactions. -/// @dev This contract is for a binary tree (2 leaves per node) -abstract contract AccQueueBinary is AccQueue { - /// @notice Create a new AccQueueBinary - constructor(uint256 _subDepth) AccQueue(_subDepth, 2) {} - - /// @notice Hash the contents of the specified level and the specified leaf. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return hashed The hash of the level and leaf. - function hashLevel(uint256 _level, uint256 _leaf) internal override returns (uint256 hashed) { - hashed = hashLeftRight(leafQueue.levels[_level][0], _leaf); - - // Free up storage slots to refund gas. - delete leafQueue.levels[_level][0]; - } - - /// @notice Hash the contents of the specified level and the specified leaf. - function hashLevelLeaf(uint256 _level, uint256 _leaf) public view override returns (uint256 hashed) { - hashed = hashLeftRight(leafQueue.levels[_level][0], _leaf); - } - - /// @notice An internal function which fills a subtree. - /// @param _level The level at which to fill the subtree. - function _fill(uint256 _level) internal override { - while (_level < subDepth) { - uint256 n = leafQueue.indices[_level]; - - if (n != 0) { - // Fill the subtree level with zeros and hash the level - uint256 hashed; - - uint256[2] memory inputs; - uint256 z = getZero(_level); - inputs[0] = leafQueue.levels[_level][0]; - inputs[1] = z; - hashed = hash2(inputs); - - // Update the subtree from the next level onwards with the new leaf - _enqueue(hashed, _level + 1); - } - - // Reset the current level - delete leafQueue.indices[_level]; - - _level++; - } - } -} diff --git a/packages/contracts/contracts/trees/AccQueueBinary0.sol b/packages/contracts/contracts/trees/AccQueueBinary0.sol deleted file mode 100644 index 33c5eeba14..0000000000 --- a/packages/contracts/contracts/trees/AccQueueBinary0.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import { MerkleZeros as MerkleBinary0 } from "./zeros/MerkleBinary0.sol"; -import { AccQueueBinary } from "./AccQueueBinary.sol"; - -/// @title AccQueueBinary0 -/// @notice This contract extends AccQueueBinary and MerkleBinary0 -/// @dev This contract is used for creating a -/// Merkle tree with binary (2 leaves per node) structure -contract AccQueueBinary0 is AccQueueBinary, MerkleBinary0 { - /// @notice Constructor for creating AccQueueBinary0 contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueBinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - /// @return zero The zero leaf at the specified level - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/contracts/contracts/trees/AccQueueBinaryMaci.sol b/packages/contracts/contracts/trees/AccQueueBinaryMaci.sol deleted file mode 100644 index 0291e9d322..0000000000 --- a/packages/contracts/contracts/trees/AccQueueBinaryMaci.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import { MerkleZeros as MerkleBinaryMaci } from "./zeros/MerkleBinaryMaci.sol"; -import { AccQueueBinary } from "./AccQueueBinary.sol"; - -/// @title AccQueueBinaryMaci -/// @notice This contract extends AccQueueBinary and MerkleBinaryMaci -/// @dev This contract is used for creating a -/// Merkle tree with binary (2 leaves per node) structure -contract AccQueueBinaryMaci is AccQueueBinary, MerkleBinaryMaci { - /// @notice Constructor for creating AccQueueBinaryMaci contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueBinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/contracts/contracts/trees/AccQueueQuinary.sol b/packages/contracts/contracts/trees/AccQueueQuinary.sol deleted file mode 100644 index c31d54dd2c..0000000000 --- a/packages/contracts/contracts/trees/AccQueueQuinary.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import { AccQueue } from "./AccQueue.sol"; - -/// @title AccQueueQuinary -/// @notice This contract defines a Merkle tree where each leaf insertion only updates a -/// subtree. To obtain the main tree root, the contract owner must merge the -/// subtrees together. Merging subtrees requires at least 2 operations: -/// mergeSubRoots(), and merge(). To get around the gas limit, -/// the mergeSubRoots() can be performed in multiple transactions. -/// @dev This contract is for a quinary tree (5 leaves per node) -abstract contract AccQueueQuinary is AccQueue { - /// @notice Create a new AccQueueQuinary instance - constructor(uint256 _subDepth) AccQueue(_subDepth, 5) {} - - /// @notice Hash the contents of the specified level and the specified leaf. - /// @dev it also frees up storage slots to refund gas. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return hashed The hash of the level and leaf. - function hashLevel(uint256 _level, uint256 _leaf) internal override returns (uint256 hashed) { - uint256[5] memory inputs; - inputs[0] = leafQueue.levels[_level][0]; - inputs[1] = leafQueue.levels[_level][1]; - inputs[2] = leafQueue.levels[_level][2]; - inputs[3] = leafQueue.levels[_level][3]; - inputs[4] = _leaf; - hashed = hash5(inputs); - - // Free up storage slots to refund gas. Note that using a loop here - // would result in lower gas savings. - delete leafQueue.levels[_level]; - } - - /// @notice Hash the contents of the specified level and the specified leaf. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return hashed The hash of the level and leaf. - function hashLevelLeaf(uint256 _level, uint256 _leaf) public view override returns (uint256 hashed) { - uint256[5] memory inputs; - inputs[0] = leafQueue.levels[_level][0]; - inputs[1] = leafQueue.levels[_level][1]; - inputs[2] = leafQueue.levels[_level][2]; - inputs[3] = leafQueue.levels[_level][3]; - inputs[4] = _leaf; - hashed = hash5(inputs); - } - - /// @notice An internal function which fills a subtree - /// @param _level The level at which to fill the subtree - function _fill(uint256 _level) internal override { - while (_level < subDepth) { - uint256 n = leafQueue.indices[_level]; - - if (n != 0) { - // Fill the subtree level with zeros and hash the level - uint256 hashed; - - uint256[5] memory inputs; - uint256 z = getZero(_level); - uint8 i = 0; - for (; i < n; i++) { - inputs[i] = leafQueue.levels[_level][i]; - } - - for (; i < hashLength; i++) { - inputs[i] = z; - } - hashed = hash5(inputs); - - // Update the subtree from the next level onwards with the new leaf - _enqueue(hashed, _level + 1); - } - - // Reset the current level - delete leafQueue.indices[_level]; - - _level++; - } - } -} diff --git a/packages/contracts/contracts/trees/AccQueueQuinary0.sol b/packages/contracts/contracts/trees/AccQueueQuinary0.sol deleted file mode 100644 index 9ac606a5c8..0000000000 --- a/packages/contracts/contracts/trees/AccQueueQuinary0.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import { MerkleZeros as MerkleQuinary0 } from "./zeros/MerkleQuinary0.sol"; -import { AccQueueQuinary } from "./AccQueueQuinary.sol"; - -/// @title AccQueueQuinary0 -/// @notice This contract extends AccQueueQuinary and MerkleQuinary0 -/// @dev This contract is used for creating a -/// Merkle tree with quinary (5 leaves per node) structure -contract AccQueueQuinary0 is AccQueueQuinary, MerkleQuinary0 { - /// @notice Constructor for creating AccQueueQuinary0 contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueQuinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - /// @return zero The zero leaf at the specified level - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/contracts/contracts/trees/AccQueueQuinaryBlankSl.sol b/packages/contracts/contracts/trees/AccQueueQuinaryBlankSl.sol deleted file mode 100644 index c629677cd8..0000000000 --- a/packages/contracts/contracts/trees/AccQueueQuinaryBlankSl.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import { MerkleZeros as MerkleQuinaryBlankSl } from "./zeros/MerkleQuinaryBlankSl.sol"; -import { AccQueueQuinary } from "./AccQueueQuinary.sol"; - -/// @title AccQueueQuinaryBlankSl -/// @notice This contract extends AccQueueQuinary and MerkleQuinaryBlankSl -/// @dev This contract is used for creating a -/// Merkle tree with quinary (5 leaves per node) structure -contract AccQueueQuinaryBlankSl is AccQueueQuinary, MerkleQuinaryBlankSl { - /// @notice Constructor for creating AccQueueQuinaryBlankSl contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueQuinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - /// @return zero The zero leaf at the specified level - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/contracts/contracts/trees/AccQueueQuinaryMaci.sol b/packages/contracts/contracts/trees/AccQueueQuinaryMaci.sol deleted file mode 100644 index cfb5533311..0000000000 --- a/packages/contracts/contracts/trees/AccQueueQuinaryMaci.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import { MerkleZeros as MerkleQuinaryMaci } from "./zeros/MerkleQuinaryMaci.sol"; -import { AccQueueQuinary } from "./AccQueueQuinary.sol"; - -/// @title AccQueueQuinaryMaci -/// @notice This contract extends AccQueueQuinary and MerkleQuinaryMaci -/// @dev This contract is used for creating a -/// Merkle tree with quinary (5 leaves per node) structure -contract AccQueueQuinaryMaci is AccQueueQuinary, MerkleQuinaryMaci { - /// @notice Constructor for creating AccQueueQuinaryMaci contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueQuinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - /// @return zero The zero leaf at the specified level - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/contracts/contracts/trees/EmptyBallotRoots.sol b/packages/contracts/contracts/trees/EmptyBallotRoots.sol new file mode 100644 index 0000000000..89a4e9d910 --- /dev/null +++ b/packages/contracts/contracts/trees/EmptyBallotRoots.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +abstract contract EmptyBallotRoots { + // emptyBallotRoots contains the roots of Ballot trees of five leaf + // configurations. + // Each tree has a depth of 10, which is the hardcoded state tree depth. + // Each leaf is an empty ballot. A configuration refers to the depth of the + // voice option tree for that ballot. + + // The leaf for the root at index 0 contains hash(0, root of a VO tree with + // depth 1 and zero-value 0) + + // The leaf for the root at index 1 contains hash(0, root of a VO tree with + // depth 2 and zero-value 0) + + // ... and so on. + + // The first parameter to the hash function is the nonce, which is 0. + + uint256[5] internal emptyBallotRoots; + + constructor() { + emptyBallotRoots[0] = uint256(16015576667038038422103932363190100635991292382181099511410843174865570503661); + emptyBallotRoots[1] = uint256(166510078825589460025300915201657086611944528317298994959376081297530246971); + emptyBallotRoots[2] = uint256(10057734083972610459557695472359628128485394923403014377687504571662791937025); + emptyBallotRoots[3] = uint256(4904828619307091008204672239231377290495002626534171783829482835985709082773); + emptyBallotRoots[4] = uint256(18694062287284245784028624966421731916526814537891066525886866373016385890569); + } +} diff --git a/packages/contracts/contracts/utilities/Params.sol b/packages/contracts/contracts/utilities/Params.sol index 6e1f174cd3..bde214b17e 100644 --- a/packages/contracts/contracts/utilities/Params.sol +++ b/packages/contracts/contracts/utilities/Params.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.20; import { IMACI } from "../interfaces/IMACI.sol"; -import { AccQueue } from "../trees/AccQueue.sol"; +import { IVerifier } from "../interfaces/IVerifier.sol"; +import { IVkRegistry } from "../interfaces/IVkRegistry.sol"; /// @title Params /// @notice This contracts contains a number of structures @@ -13,8 +14,6 @@ contract Params { /// @notice A struct holding the depths of the merkle trees struct TreeDepths { uint8 intStateTreeDepth; - uint8 messageTreeSubDepth; - uint8 messageTreeDepth; uint8 voteOptionTreeDepth; } @@ -23,6 +22,7 @@ contract Params { /// deployment struct ExtContracts { IMACI maci; - AccQueue messageAq; + IVerifier verifier; + IVkRegistry vkRegistry; } } diff --git a/packages/contracts/deploy-config-example.json b/packages/contracts/deploy-config-example.json index b5020d5da2..f6d2bceef3 100644 --- a/packages/contracts/deploy-config-example.json +++ b/packages/contracts/deploy-config-example.json @@ -41,20 +41,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -106,20 +105,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -172,7 +170,6 @@ "VkRegistry": { "stateTreeDepth": 6, "intStateTreeDepth": 2, - "messageTreeDepth": 9, "voteOptionTreeDepth": 3, "messageBatchDepth": 2, "zkeys": { @@ -238,20 +235,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -304,20 +300,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -370,20 +365,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -441,20 +435,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -512,20 +505,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -583,20 +575,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -654,20 +645,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } @@ -725,20 +715,19 @@ "VkRegistry": { "stateTreeDepth": 10, "intStateTreeDepth": 1, - "messageTreeDepth": 2, "voteOptionTreeDepth": 2, - "messageBatchDepth": 1, + "messageBatchSize": 20, "zkeys": { "qv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm" }, "nonQv": { - "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", - "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm", + "processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test_js/ProcessMessagesNonQv_10-20-2_test.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm" } } diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 827dbc3694..32f0d222d4 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -49,8 +49,6 @@ "test:utilities": "pnpm run test ./tests/Utilities.test.ts", "test:signupGatekeeper": "pnpm run test ./tests/SignUpGatekeeper.test.ts", "test:verifier": "pnpm run test ./tests/Verifier.test.ts", - "test:accQueue": "pnpm run test ./tests/AccQueue.test.ts", - "test:accQueueBenchmark": "pnpm run test ./tests/AccQueueBenchmark.test.ts", "test:hasherBenchmarks": "pnpm run test ./tests/HasherBenchmarks.test.ts", "test:vkRegistry": "pnpm run test ./tests/VkRegistry.test.ts", "test:pollFactory": "pnpm run test ./tests/PollFactory.test.ts", diff --git a/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts b/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts index 1e777d7baa..4826526c57 100644 --- a/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts +++ b/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts @@ -31,7 +31,6 @@ deployment.deployTask(EDeploySteps.VkRegistry, "Deploy Vk Registry and set keys" const stateTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "stateTreeDepth"); const intStateTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "intStateTreeDepth"); - const messageTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageTreeDepth"); const messageBatchDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageBatchDepth"); const voteOptionTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "voteOptionTreeDepth"); const processMessagesZkeyPathQv = deployment.getDeployConfigField( @@ -94,7 +93,6 @@ deployment.deployTask(EDeploySteps.VkRegistry, "Deploy Vk Registry and set keys" .setVerifyingKeysBatch( stateTreeDepth, intStateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, 5 ** messageBatchDepth, modes, diff --git a/packages/contracts/tasks/deploy/poll/01-poll.ts b/packages/contracts/tasks/deploy/poll/01-poll.ts index 38c5719133..2e8c749e0a 100644 --- a/packages/contracts/tasks/deploy/poll/01-poll.ts +++ b/packages/contracts/tasks/deploy/poll/01-poll.ts @@ -41,8 +41,7 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => const coordinatorPubkey = deployment.getDeployConfigField(EContracts.Poll, "coordinatorPubkey"); const pollDuration = deployment.getDeployConfigField(EContracts.Poll, "pollDuration"); const intStateTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "intStateTreeDepth"); - const messageTreeSubDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageBatchDepth"); - const messageTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageTreeDepth"); + const messageBatchSize = deployment.getDeployConfigField(EContracts.VkRegistry, "messageBatchSize"); const voteOptionTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "voteOptionTreeDepth"); const useQuadraticVoting = @@ -50,14 +49,27 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => const unserializedKey = PubKey.deserialize(coordinatorPubkey); const mode = useQuadraticVoting ? EMode.QV : EMode.NON_QV; + const [pollContractAddress, messageProcessorContractAddress, tallyContractAddress] = + await maciContract.deployPoll.staticCall( + pollDuration, + { + intStateTreeDepth, + voteOptionTreeDepth, + }, + messageBatchSize, + unserializedKey.asContractParam(), + verifierContractAddress, + vkRegistryContractAddress, + mode, + ); + const tx = await maciContract.deployPoll( pollDuration, { intStateTreeDepth, - messageTreeSubDepth, - messageTreeDepth, voteOptionTreeDepth, }, + messageBatchSize, unserializedKey.asContractParam(), verifierContractAddress, vkRegistryContractAddress, @@ -70,13 +82,11 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => throw new Error("Deploy poll transaction is failed"); } - const pollContracts = await maciContract.getPoll(pollId); - const pollContractAddress = pollContracts.poll; - const messageProcessorContractAddress = pollContracts.messageProcessor; - const tallyContractAddress = pollContracts.tally; - const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollContractAddress }); - const extContracts = await pollContract.extContracts(); + const [maxVoteOptions, extContracts] = await Promise.all([ + pollContract.maxVoteOptions(), + pollContract.extContracts(), + ]); const messageProcessorContract = await deployment.getContract({ name: EContracts.MessageProcessor, @@ -88,14 +98,6 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => address: tallyContractAddress, }); - const messageAccQueueContract = await deployment.getContract({ - name: EContracts.AccQueueQuinaryMaci, - address: extContracts[1], - }); - - // get the empty ballot root - const emptyBallotRoot = await pollContract.emptyBallotRoot(); - await Promise.all([ storage.register({ id: EContracts.Poll, @@ -103,15 +105,16 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => contract: pollContract, args: [ pollDuration, + maxVoteOptions, { intStateTreeDepth, - messageTreeSubDepth, - messageTreeDepth, voteOptionTreeDepth, }, + { + messageBatchSize, + }, unserializedKey.asContractParam(), extContracts, - emptyBallotRoot.toString(), ], network: hre.network.name, }), @@ -137,15 +140,6 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => ], network: hre.network.name, }), - - storage.register({ - id: EContracts.AccQueueQuinaryMaci, - key: `poll-${pollId}`, - name: "contracts/trees/AccQueueQuinaryMaci.sol:AccQueueQuinaryMaci", - contract: messageAccQueueContract, - args: [messageTreeSubDepth], - network: hre.network.name, - }), ]); }), ); diff --git a/packages/contracts/tasks/helpers/ProofGenerator.ts b/packages/contracts/tasks/helpers/ProofGenerator.ts index ce06cecc7c..586c8d41c2 100644 --- a/packages/contracts/tasks/helpers/ProofGenerator.ts +++ b/packages/contracts/tasks/helpers/ProofGenerator.ts @@ -78,7 +78,6 @@ export class ProofGenerator { static async prepareState({ maciContract, pollContract, - messageAq, pollId, maciPrivateKey, coordinatorKeypair, @@ -108,26 +107,18 @@ export class ProofGenerator { } // build an off-chain representation of the MACI contract using data in the contract storage - const [defaultStartBlockSignup, defaultStartBlockPoll, { messageTreeDepth }, stateRoot, numSignups] = - await Promise.all([ - maciContract - .queryFilter(maciContract.filters.SignUp(), startBlock) - .then((events) => events[0]?.blockNumber ?? 0), - maciContract - .queryFilter(maciContract.filters.DeployPoll(), startBlock) - .then((events) => events[0]?.blockNumber ?? 0), - pollContract.treeDepths(), - maciContract.getStateTreeRoot(), - maciContract.numSignUps(), - ]); + const [defaultStartBlockSignup, defaultStartBlockPoll, stateRoot, numSignups] = await Promise.all([ + maciContract.queryFilter(maciContract.filters.SignUp(), startBlock).then((events) => events[0]?.blockNumber ?? 0), + maciContract + .queryFilter(maciContract.filters.DeployPoll(), startBlock) + .then((events) => events[0]?.blockNumber ?? 0), + maciContract.getStateTreeRoot(), + maciContract.numSignUps(), + ]); const defaultStartBlock = Math.min(defaultStartBlockPoll, defaultStartBlockSignup); let fromBlock = startBlock ? Number(startBlock) : defaultStartBlock; - const messageRoot = await messageAq.getMainRoot(messageTreeDepth); const defaultEndBlock = await Promise.all([ - pollContract - .queryFilter(pollContract.filters.MergeMessageAq(messageRoot), fromBlock) - .then((events) => events[events.length - 1]?.blockNumber), pollContract .queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock) .then((events) => events[events.length - 1]?.blockNumber), diff --git a/packages/contracts/tasks/helpers/Prover.ts b/packages/contracts/tasks/helpers/Prover.ts index 8085a55a2a..3f04a9a233 100644 --- a/packages/contracts/tasks/helpers/Prover.ts +++ b/packages/contracts/tasks/helpers/Prover.ts @@ -1,10 +1,10 @@ /* eslint-disable no-console, no-await-in-loop */ -import { STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "maci-core"; +import { STATE_TREE_ARITY } from "maci-core"; import { G1Point, G2Point, genTreeProof } from "maci-crypto"; import { VerifyingKey } from "maci-domainobjs"; import type { IVerifyingKeyStruct, Proof } from "../../ts/types"; -import type { AccQueue, MACI, MessageProcessor, Poll, Tally, Verifier, VkRegistry } from "../../typechain-types"; +import type { MACI, MessageProcessor, Poll, Tally, Verifier, VkRegistry } from "../../typechain-types"; import type { BigNumberish } from "ethers"; import { asHex, formatProofForVerifierContract } from "../../ts/utils"; @@ -25,11 +25,6 @@ export class Prover { */ private mpContract: MessageProcessor; - /** - * AccQueue contract typechain wrapper (messages) - */ - private messageAqContract: AccQueue; - /** * MACI contract typechain wrapper */ @@ -58,7 +53,6 @@ export class Prover { constructor({ pollContract, mpContract, - messageAqContract, maciContract, vkRegistryContract, verifierContract, @@ -66,7 +60,6 @@ export class Prover { }: IProverParams) { this.pollContract = pollContract; this.mpContract = mpContract; - this.messageAqContract = messageAqContract; this.maciContract = maciContract; this.vkRegistryContract = vkRegistryContract; this.verifierContract = verifierContract; @@ -80,19 +73,27 @@ export class Prover { */ async proveMessageProcessing(proofs: Proof[]): Promise { // retrieve the values we need from the smart contracts - const [treeDepths, numSignUpsAndMessages, numBatchesProcessed, stateTreeDepth, dd, coordinatorPubKeyHash, mode] = - await Promise.all([ - this.pollContract.treeDepths(), - this.pollContract.numSignUpsAndMessages(), - this.mpContract.numBatchesProcessed().then(Number), - this.maciContract.stateTreeDepth().then(Number), - this.pollContract.getDeployTimeAndDuration(), - this.pollContract.coordinatorPubKeyHash(), - this.mpContract.mode(), - ]); + const [ + treeDepths, + messageBatchSize, + numSignUpsAndMessages, + numBatchesProcessed, + stateTreeDepth, + dd, + coordinatorPubKeyHash, + mode, + ] = await Promise.all([ + this.pollContract.treeDepths(), + this.pollContract.messageBatchSize().then(Number), + this.pollContract.numSignUpsAndMessages(), + this.mpContract.numBatchesProcessed().then(Number), + this.maciContract.stateTreeDepth().then(Number), + this.pollContract.getDeployTimeAndDuration(), + this.pollContract.coordinatorPubKeyHash(), + this.mpContract.mode(), + ]); const numMessages = Number(numSignUpsAndMessages[1]); - const messageBatchSize = MESSAGE_TREE_ARITY ** Number(treeDepths[1]); let totalMessageBatches = numMessages <= messageBatchSize ? 1 : Math.floor(numMessages / messageBatchSize); let numberBatchesProcessed = numBatchesProcessed; @@ -100,16 +101,12 @@ export class Prover { totalMessageBatches += 1; } - const [messageRootOnChain, onChainProcessVk] = await Promise.all([ - this.messageAqContract.getMainRoot(Number(treeDepths[2])), - this.vkRegistryContract.getProcessVk( - stateTreeDepth, - treeDepths.messageTreeDepth, - treeDepths.voteOptionTreeDepth, - messageBatchSize, - mode, - ), - ]); + const onChainProcessVk = await this.vkRegistryContract.getProcessVk( + stateTreeDepth, + treeDepths.voteOptionTreeDepth, + messageBatchSize, + mode, + ); const pollEndTimestampOnChain = BigInt(dd[0]) + BigInt(dd[1]); @@ -144,7 +141,6 @@ export class Prover { // validation this.validatePollDuration(circuitInputs.pollEndTimestamp as BigNumberish, pollEndTimestampOnChain); - this.validateMessageRoot(circuitInputs.msgRoot as BigNumberish, messageRootOnChain); let currentSbCommitmentOnChain: bigint; @@ -162,9 +158,12 @@ export class Prover { const formattedProof = formatProofForVerifierContract(proof); + const batchHashes = await this.pollContract.getBatchHashes(); + const publicInputsOnChain = await this.mpContract.getPublicCircuitInputs( currentMessageBatchIndex, asHex(circuitInputs.newSbCommitment as BigNumberish), + batchHashes[currentMessageBatchIndex + 1].toString(), ); this.validatePublicInput(publicInputs, publicInputsOnChain); @@ -363,19 +362,6 @@ export class Prover { } } - /** - * Validate message root - * - * @param messageRoot - off-chain message root - * @param messageRootOnChain - on-chain message root - * @throws error if roots don't match - */ - private validateMessageRoot(messageRoot: BigNumberish, messageRootOnChain: BigNumberish) { - if (BigInt(messageRoot).toString() !== messageRootOnChain.toString()) { - throw new Error("message root mismatch"); - } - } - /** * Validate commitment * diff --git a/packages/contracts/tasks/helpers/TreeMerger.ts b/packages/contracts/tasks/helpers/TreeMerger.ts index a28105751d..729ca6c81d 100644 --- a/packages/contracts/tasks/helpers/TreeMerger.ts +++ b/packages/contracts/tasks/helpers/TreeMerger.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import type { ITreeMergeParams } from "./types"; -import type { AccQueue, Poll } from "../../typechain-types"; +import type { Poll } from "../../typechain-types"; import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; /** @@ -8,11 +8,6 @@ import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signer * This class is using for merging signups and messages. */ export class TreeMerger { - /** - * User messages AccQueue contract - */ - private messageAccQueueContract: AccQueue; - /** * Poll contract */ @@ -28,9 +23,8 @@ export class TreeMerger { * * @param {ITreeMergeParams} params - contracts and signer */ - constructor({ deployer, messageAccQueueContract, pollContract }: ITreeMergeParams) { + constructor({ deployer, pollContract }: ITreeMergeParams) { this.pollContract = pollContract; - this.messageAccQueueContract = messageAccQueueContract; this.deployer = deployer; } @@ -73,69 +67,4 @@ export class TreeMerger { console.log("The state tree has already been merged."); } } - - /** - * Merge message subtrees - * - * @param queueOps - the number of queue operations to perform - */ - async mergeMessageSubtrees(queueOps: number): Promise { - let subTreesMerged = false; - - // infinite loop to merge the sub trees - while (!subTreesMerged) { - // eslint-disable-next-line no-await-in-loop - subTreesMerged = await this.messageAccQueueContract.subTreesMerged(); - - if (subTreesMerged) { - console.log("All message subtrees have been merged."); - } else { - // eslint-disable-next-line no-await-in-loop - await this.messageAccQueueContract.getSrIndices().then((indices) => { - console.log(`Merging message subroots ${indices[0] + 1n} / ${indices[1] + 1n}`); - }); - - // eslint-disable-next-line no-await-in-loop - const tx = await this.pollContract.mergeMessageAqSubRoots(queueOps); - // eslint-disable-next-line no-await-in-loop - const receipt = await tx.wait(); - - if (receipt?.status !== 1) { - throw new Error("Merge message subroots transaction failed"); - } - - console.log(`Executed mergeMessageAqSubRoots(); gas used: ${receipt.gasUsed.toString()}`); - - console.log(`Transaction hash: ${receipt.hash}`); - } - } - } - - /** - * Merge message queue - */ - async mergeMessages(): Promise { - // check if the message AQ has been fully merged - const messageTreeDepth = await this.pollContract.treeDepths().then((depths) => Number(depths[2])); - - // check if the main root was not already computed - const mainRoot = await this.messageAccQueueContract.getMainRoot(messageTreeDepth.toString()); - if (mainRoot.toString() === "0") { - // go and merge the message tree - - console.log("Merging subroots to a main message root..."); - const tx = await this.pollContract.mergeMessageAq(); - const receipt = await tx.wait(); - - if (receipt?.status !== 1) { - throw new Error("Merge messages transaction failed"); - } - - console.log(`Executed mergeMessageAq(); gas used: ${receipt.gasUsed.toString()}`); - console.log(`Transaction hash: ${receipt.hash}`); - console.log("The message tree has been merged."); - } else { - console.log("The message tree has already been merged."); - } - } } diff --git a/packages/contracts/tasks/helpers/types.ts b/packages/contracts/tasks/helpers/types.ts index 66d6bb5f6e..2ed1616770 100644 --- a/packages/contracts/tasks/helpers/types.ts +++ b/packages/contracts/tasks/helpers/types.ts @@ -1,5 +1,5 @@ import type { Proof } from "../../ts/types"; -import type { AccQueue, MACI, MessageProcessor, Poll, Tally, Verifier, VkRegistry } from "../../typechain-types"; +import type { MACI, MessageProcessor, Poll, Tally, Verifier, VkRegistry } from "../../typechain-types"; import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import type { BaseContract, @@ -254,11 +254,6 @@ export interface IPrepareStateParams { */ pollContract: Poll; - /** - * MessageAq contract - */ - messageAq: AccQueue; - /** * Poll id */ @@ -376,11 +371,6 @@ export interface IProverParams { */ mpContract: MessageProcessor; - /** - * AccQueue contract typechain wrapper (messages) - */ - messageAqContract: AccQueue; - /** * MACI contract typechain wrapper */ @@ -608,9 +598,6 @@ export enum EContracts { Poll = "Poll", Tally = "Tally", MessageProcessor = "MessageProcessor", - AccQueue = "AccQueue", - AccQueueQuinaryBlankSl = "AccQueueQuinaryBlankSl", - AccQueueQuinaryMaci = "AccQueueQuinaryMaci", } /** @@ -659,11 +646,6 @@ export interface ITreeMergeParams { * Poll contract */ pollContract: Poll; - - /** - * Message AccQueue contract - */ - messageAccQueueContract: AccQueue; } /** diff --git a/packages/contracts/tasks/runner/merge.ts b/packages/contracts/tasks/runner/merge.ts index 55d3c95ea3..e008cbdc6c 100644 --- a/packages/contracts/tasks/runner/merge.ts +++ b/packages/contracts/tasks/runner/merge.ts @@ -2,22 +2,19 @@ import { ZeroAddress } from "ethers"; import { task, types } from "hardhat/config"; -import type { AccQueue, MACI, Poll } from "../../typechain-types"; +import type { MACI, Poll } from "../../typechain-types"; import { Deployment } from "../helpers/Deployment"; import { TreeMerger } from "../helpers/TreeMerger"; import { EContracts, type IMergeParams } from "../helpers/types"; -const DEFAULT_SR_QUEUE_OPS = 4; - /** - * Command to merge signup and message queues of a MACI contract + * Command to merge signups of a MACI contract */ -task("merge", "Merge signups and messages") +task("merge", "Merge signups") .addParam("poll", "The poll id", undefined, types.string) - .addOptionalParam("queueOps", "The number of queue operations to perform", DEFAULT_SR_QUEUE_OPS, types.int) .addOptionalParam("prove", "Run prove command after merging", false, types.boolean) - .setAction(async ({ poll, prove, queueOps = DEFAULT_SR_QUEUE_OPS }: IMergeParams, hre) => { + .setAction(async ({ poll, prove }: IMergeParams, hre) => { const deployment = Deployment.getInstance({ hre }); deployment.setHre(hre); @@ -26,23 +23,17 @@ task("merge", "Merge signups and messages") const maciContract = await deployment.getContract({ name: EContracts.MACI }); - const pollContracts = await maciContract.polls(poll); - const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollContracts.poll }); - const [, messageAccQueueContractAddress] = await pollContract.extContracts(); - - const messageAccQueueContract = await deployment.getContract({ - name: EContracts.AccQueue, - address: messageAccQueueContractAddress, - }); + const pollContractAddress = await maciContract.polls(poll); - if (pollContracts.poll === ZeroAddress) { + if (pollContractAddress === ZeroAddress) { throw new Error(`No poll ${poll} found`); } + const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollContractAddress }); + const treeMerger = new TreeMerger({ deployer, pollContract, - messageAccQueueContract, }); const startBalance = await deployer.provider.getBalance(deployer); @@ -52,8 +43,6 @@ task("merge", "Merge signups and messages") await treeMerger.checkPollDuration(); await treeMerger.mergeSignups(); - await treeMerger.mergeMessageSubtrees(queueOps); - await treeMerger.mergeMessages(); const endBalance = await deployer.provider.getBalance(deployer); diff --git a/packages/contracts/tasks/runner/prove.ts b/packages/contracts/tasks/runner/prove.ts index ab96b695fb..724b013340 100644 --- a/packages/contracts/tasks/runner/prove.ts +++ b/packages/contracts/tasks/runner/prove.ts @@ -5,11 +5,13 @@ import { Keypair, PrivKey } from "maci-domainobjs"; import fs from "fs"; -import type { MACI, Poll, AccQueue } from "../../typechain-types"; +import type { Proof } from "../../ts/types"; +import type { MACI, MessageProcessor, Poll, Tally, Verifier, VkRegistry } from "../../typechain-types"; import { ContractStorage } from "../helpers/ContractStorage"; import { Deployment } from "../helpers/Deployment"; import { ProofGenerator } from "../helpers/ProofGenerator"; +import { Prover } from "../helpers/Prover"; import { EContracts, type IProveParams } from "../helpers/types"; /** @@ -69,20 +71,11 @@ task("prove", "Command to generate proofs") const maciContractAddress = storage.mustGetAddress(EContracts.MACI, network.name); const maciContract = await deployment.getContract({ name: EContracts.MACI, address: maciContractAddress }); + const vkRegistryContract = await deployment.getContract({ name: EContracts.VkRegistry }); + const verifierContract = await deployment.getContract({ name: EContracts.Verifier }); - const pollContracts = await maciContract.polls(poll); - const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollContracts.poll }); - const messageAqAddress = await pollContract.extContracts().then((contracts) => contracts.messageAq); - const messageAq = await deployment.getContract({ - name: EContracts.AccQueue, - address: messageAqAddress, - }); - - const [, messageAqContractAddress] = await pollContract.extContracts(); - const messageAqContract = await deployment.getContract({ - name: EContracts.AccQueue, - address: messageAqContractAddress, - }); + const pollAddress = await maciContract.polls(poll); + const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollAddress }); const isStateAqMerged = await pollContract.stateMerged(); // Check that the state and message trees have been merged for at least the first poll @@ -90,19 +83,9 @@ task("prove", "Command to generate proofs") throw new Error("The state tree has not been merged yet. Please use the mergeSignups subcommand to do so."); } - const messageTreeDepth = await pollContract.treeDepths().then((depths) => Number(depths[2])); - - // check that the main root is set - const mainRoot = await messageAqContract.getMainRoot(messageTreeDepth.toString()); - - if (mainRoot.toString() === "0") { - throw new Error("The message tree has not been merged yet. Please use the mergeMessages subcommand to do so."); - } - const maciState = await ProofGenerator.prepareState({ maciContract, pollContract, - messageAq, maciPrivateKey, coordinatorKeypair, pollId: poll, @@ -123,6 +106,17 @@ task("prove", "Command to generate proofs") throw new Error(`Poll ${poll} not found`); } + const tallyContract = await deployment.getContract({ + name: EContracts.Tally, + key: `poll-${poll.toString()}`, + }); + const tallyContractAddress = await tallyContract.getAddress(); + + const mpContract = await deployment.getContract({ + name: EContracts.MessageProcessor, + key: `poll-${poll.toString()}`, + }); + const useQuadraticVoting = deployment.getDeployConfigField(EContracts.Poll, "useQuadraticVoting") ?? false; const mode = useQuadraticVoting ? "qv" : "nonQv"; @@ -145,7 +139,7 @@ task("prove", "Command to generate proofs") const proofGenerator = new ProofGenerator({ poll: foundPoll, maciContractAddress, - tallyContractAddress: pollContracts.tally, + tallyContractAddress, rapidsnark, tally: { zkey: tallyZkey, @@ -162,8 +156,25 @@ task("prove", "Command to generate proofs") useQuadraticVoting, }); - await proofGenerator.generateMpProofs(); - await proofGenerator.generateTallyProofs(network); + const data = { + processProofs: [] as Proof[], + tallyProofs: [] as Proof[], + }; + + const prover = new Prover({ + maciContract, + mpContract, + pollContract, + vkRegistryContract, + verifierContract, + tallyContract, + }); + + data.processProofs = await proofGenerator.generateMpProofs(); + await prover.proveMessageProcessing(data.processProofs); + + data.tallyProofs = await proofGenerator.generateTallyProofs(network).then(({ proofs }) => proofs); + await prover.proveTally(data.tallyProofs); const endBalance = await signer.provider.getBalance(signer); diff --git a/packages/contracts/tasks/runner/submitOnChain.ts b/packages/contracts/tasks/runner/submitOnChain.ts index 8b2761011f..ed9932cf99 100644 --- a/packages/contracts/tasks/runner/submitOnChain.ts +++ b/packages/contracts/tasks/runner/submitOnChain.ts @@ -5,7 +5,7 @@ import { task, types } from "hardhat/config"; import fs from "fs"; import type { Proof } from "../../ts/types"; -import type { VkRegistry, Verifier, MACI, Poll, AccQueue, MessageProcessor, Tally } from "../../typechain-types"; +import type { VkRegistry, Verifier, MACI, Poll, MessageProcessor, Tally } from "../../typechain-types"; import { ContractStorage } from "../helpers/ContractStorage"; import { Deployment } from "../helpers/Deployment"; @@ -78,43 +78,29 @@ task("submitOnChain", "Command to prove the result of a poll on-chain") deployment.getContract({ name: EContracts.Verifier }), ]); - const pollContracts = await maciContract.polls(poll); + const pollContractAddress = await maciContract.polls(poll); const pollContract = await deployment.getContract({ name: EContracts.Poll, - address: pollContracts.poll, + address: pollContractAddress, }); - const [[, messageAqContractAddress], isStateAqMerged, messageTreeDepth, mpContract, tallyContract] = - await Promise.all([ - pollContract.extContracts(), - pollContract.stateMerged(), - pollContract.treeDepths().then((depths) => Number(depths[2])), - deployment.getContract({ - name: EContracts.MessageProcessor, - address: pollContracts.messageProcessor, - }), - deployment.getContract({ - name: EContracts.Tally, - address: pollContracts.tally, - }), - ]); - const messageAqContract = await deployment.getContract({ - name: EContracts.AccQueue, - address: messageAqContractAddress, - }); + const [isStateAqMerged, mpContract, tallyContract] = await Promise.all([ + pollContract.stateMerged(), + deployment.getContract({ + name: EContracts.MessageProcessor, + key: `poll-${poll.toString()}`, + }), + deployment.getContract({ + name: EContracts.Tally, + key: `poll-${poll.toString()}`, + }), + ]); // Check that the state and message trees have been merged for at least the first poll if (!isStateAqMerged && poll.toString() === "0") { throw new Error("The state tree has not been merged yet. Please use the mergeSignups subcommand to do so."); } - // check that the main root is set - const mainRoot = await messageAqContract.getMainRoot(messageTreeDepth.toString()); - - if (mainRoot.toString() === "0") { - throw new Error("The message tree has not been merged yet. Please use the mergeMessages subcommand to do so."); - } - const data = { processProofs: [] as Proof[], tallyProofs: [] as Proof[], @@ -130,7 +116,6 @@ task("submitOnChain", "Command to prove the result of a poll on-chain") const prover = new Prover({ maciContract, - messageAqContract, mpContract, pollContract, vkRegistryContract, diff --git a/packages/contracts/tests/AccQueue.test.ts b/packages/contracts/tests/AccQueue.test.ts deleted file mode 100644 index 1929218b9e..0000000000 --- a/packages/contracts/tests/AccQueue.test.ts +++ /dev/null @@ -1,496 +0,0 @@ -import { expect } from "chai"; -import { AccQueue, hashLeftRight, NOTHING_UP_MY_SLEEVE } from "maci-crypto"; - -import { - AccQueueBinary0__factory as AccQueueBinary0Factory, - AccQueueBinaryMaci__factory as AccQueueBinaryMaciFactory, - AccQueue as AccQueueContract, - AccQueueQuinary0__factory as AccQueueQuinary0Factory, - AccQueueQuinaryMaci__factory as AccQueueQuinaryMaciFactory, -} from "../typechain-types"; - -import { - deployTestAccQueues, - fillGasLimit, - enqueueGasLimit, - testEmptySubtree, - testEmptyUponDeployment, - testEnqueue, - testEnqueueAndInsertSubTree, - testFillForAllIncompletes, - testIncompleteSubtree, - testInsertSubTrees, - testMerge, - testMergeAgain, -} from "./utils"; - -describe("AccQueues", () => { - describe("Binary AccQueue enqueues", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aqContract = r.aqContract as AccQueueContract; - }); - - it("should be empty upon deployment", async () => { - await testEmptyUponDeployment(aqContract); - }); - - it("should not be able to get a subroot that does not exist", async () => { - await expect(aqContract.getSubRoot(5)).to.be.revertedWithCustomError(aqContract, "InvalidIndex"); - }); - - it("should enqueue leaves", async () => { - await testEnqueue(aqContract, HASH_LENGTH, SUB_DEPTH, ZERO); - }); - }); - - describe("Quinary AccQueue enqueues", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 5; - const ZERO = BigInt(0); - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aqContract = r.aqContract as AccQueueContract; - }); - - it("should be empty upon deployment", async () => { - await testEmptyUponDeployment(aqContract); - }); - - it("should not be able to get a subroot that does not exist", async () => { - await expect(aqContract.getSubRoot(5)).to.be.revertedWithCustomError(aqContract, "InvalidIndex"); - }); - - it("should enqueue leaves", async () => { - await testEnqueue(aqContract, HASH_LENGTH, SUB_DEPTH, ZERO); - }); - }); - - describe("Binary AccQueue0 fills", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - let aq: AccQueue; - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract as AccQueueContract; - }); - - it("should fill an empty subtree", async () => { - await testEmptySubtree(aq, aqContract, 0); - }); - - it("should fill an incomplete subtree", async () => { - await testIncompleteSubtree(aq, aqContract); - }); - - it("Filling an empty subtree again should create the correct subroot", async () => { - await testEmptySubtree(aq, aqContract, 2); - }); - - it("fill() should be correct for every number of leaves in an incomplete subtree", async () => { - await testFillForAllIncompletes(aq, aqContract, HASH_LENGTH); - }); - }); - - describe("Quinary AccQueue0 fills", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 5; - const ZERO = BigInt(0); - let aq: AccQueue; - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract as AccQueueContract; - }); - - it("should fill an empty subtree", async () => { - await testEmptySubtree(aq, aqContract, 0); - }); - - it("should fill an incomplete subtree", async () => { - await testIncompleteSubtree(aq, aqContract); - }); - - it("Filling an empty subtree again should create the correct subroot", async () => { - await testEmptySubtree(aq, aqContract, 2); - }); - - it("fill() should be correct for every number of leaves in an incomplete subtree", async () => { - await testFillForAllIncompletes(aq, aqContract, HASH_LENGTH); - }); - }); - - describe("Binary AccQueueMaci fills", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 2; - const ZERO = NOTHING_UP_MY_SLEEVE; - let aq: AccQueue; - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueBinaryMaciFactory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract as AccQueueContract; - }); - - it("should fill an empty subtree", async () => { - await testEmptySubtree(aq, aqContract, 0); - }); - - it("should fill an incomplete subtree", async () => { - await testIncompleteSubtree(aq, aqContract); - }); - - it("Filling an empty subtree again should create the correct subroot", async () => { - await testEmptySubtree(aq, aqContract, 2); - }); - - it("fill() should be correct for every number of leaves in an incomplete subtree", async () => { - await testFillForAllIncompletes(aq, aqContract, HASH_LENGTH); - }); - }); - - describe("Quinary AccQueueMaci fills", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 5; - const ZERO = NOTHING_UP_MY_SLEEVE; - let aq: AccQueue; - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueQuinaryMaciFactory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract as AccQueueContract; - }); - - it("should fill an empty subtree", async () => { - await testEmptySubtree(aq, aqContract, 0); - }); - - it("should fill an incomplete subtree", async () => { - await testIncompleteSubtree(aq, aqContract); - }); - - it("Filling an empty subtree again should create the correct subroot", async () => { - await testEmptySubtree(aq, aqContract, 2); - }); - - it("fill() should be correct for every number of leaves in an incomplete subtree", async () => { - await testFillForAllIncompletes(aq, aqContract, HASH_LENGTH); - }); - }); - - describe("Merge after enqueuing more leaves", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - const MAIN_DEPTH = 3; - - it("should produce the correct main roots", async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - await testMergeAgain(r.aq, r.aqContract as AccQueueContract, MAIN_DEPTH); - }); - }); - - describe("Edge cases", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - - it("should not be possible to merge into a tree of depth 0", async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - - const aqContract = r.aqContract as AccQueueContract; - await aqContract.enqueue(1).then((tx) => tx.wait()); - await aqContract.mergeSubRoots(0, { gasLimit: 1000000 }).then((tx) => tx.wait()); - await expect(aqContract.merge(0, { gasLimit: 1000000 })).to.revertedWithCustomError( - aqContract, - "DepthCannotBeZero", - ); - }); - - it("A small SRT of depth 1 should just have 2 leaves", async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, 1, HASH_LENGTH, ZERO); - - const aqContract = r.aqContract as AccQueueContract; - await aqContract.enqueue(0, enqueueGasLimit).then((tx) => tx.wait()); - await aqContract.mergeSubRoots(0, { gasLimit: 1000000 }).then((tx) => tx.wait()); - const srtRoot = await aqContract.getSmallSRTroot(); - const expectedRoot = hashLeftRight(BigInt(0), BigInt(0)); - expect(srtRoot.toString()).to.eq(expectedRoot.toString()); - }); - - it("should not be possible to merge subroots into a tree shorter than the SRT depth", async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, 1, HASH_LENGTH, ZERO); - const aqContract = r.aqContract as AccQueueContract; - for (let i = 0; i < 4; i += 1) { - // eslint-disable-next-line no-await-in-loop - await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - } - - await aqContract.mergeSubRoots(0, { gasLimit: 1000000 }).then((tx) => tx.wait()); - - await expect(aqContract.merge(1, { gasLimit: 1000000 })).to.be.revertedWithCustomError( - aqContract, - "DepthTooSmall", - ); - }); - - it("Merging without enqueuing new data should not change the root", async () => { - const MAIN_DEPTH = 5; - - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - - const { aq } = r; - const aqContract = r.aqContract as AccQueueContract; - // Merge once - await testMerge(aq, aqContract, 1, MAIN_DEPTH); - // Get the root - const expectedMainRoot = (await aqContract.getMainRoot(MAIN_DEPTH)).toString(); - // Merge again - await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); - // Get the root again - const root = (await aqContract.getMainRoot(MAIN_DEPTH)).toString(); - // Check that the roots match - expect(root).to.eq(expectedMainRoot); - }); - }); - - describe("Binary AccQueue0 one-shot merges", () => { - const SUB_DEPTH = 2; - const MAIN_DEPTH = 5; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - - const testParams = [1, 2, 3, 4]; - testParams.forEach((testParam) => { - it(`should merge ${testParam} subtrees`, async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - const { aq } = r; - const aqContract = r.aqContract as AccQueueContract; - await testMerge(aq, aqContract, testParam, MAIN_DEPTH); - }); - }); - }); - - describe("Quinary AccQueue0 one-shot merges", () => { - const SUB_DEPTH = 2; - const MAIN_DEPTH = 6; - const HASH_LENGTH = 5; - const ZERO = BigInt(0); - - const testParams = [1, 5, 26]; - testParams.forEach((testParam) => { - it(`should merge ${testParam} subtrees`, async () => { - const r = await deployTestAccQueues(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - const { aq } = r; - const aqContract = r.aqContract as AccQueueContract; - await testMerge(aq, aqContract, testParam, MAIN_DEPTH); - }); - }); - }); - - describe("Binary AccQueue0 subtree insertions", () => { - const SUB_DEPTH = 2; - const MAIN_DEPTH = 6; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - - it("Enqueued leaves and inserted subtrees should be in the right order", async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - const { aq } = r; - const aqContract = r.aqContract as AccQueueContract; - await testEnqueueAndInsertSubTree(aq, aqContract); - }); - - const testParams = [1, 2, 3, 9]; - testParams.forEach((testParam) => { - it(`should insert ${testParam} subtrees`, async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - const { aq } = r; - const aqContract = r.aqContract as AccQueueContract; - await testInsertSubTrees(aq, aqContract, testParam, MAIN_DEPTH); - }); - }); - }); - - describe("Quinary AccQueue0 subtree insertions", () => { - const SUB_DEPTH = 2; - const MAIN_DEPTH = 6; - const HASH_LENGTH = 5; - const ZERO = BigInt(0); - - it("Enqueued leaves and inserted subtrees should be in the right order", async () => { - const r = await deployTestAccQueues(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - const { aq } = r; - const aqContract = r.aqContract as AccQueueContract; - await testEnqueueAndInsertSubTree(aq, aqContract); - }); - - const testParams = [1, 4, 9, 26]; - testParams.forEach((testParam) => { - it(`should insert ${testParam} subtrees`, async () => { - const r = await deployTestAccQueues(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - const { aq } = r; - const aqContract = r.aqContract as AccQueueContract; - await testInsertSubTrees(aq, aqContract, testParam, MAIN_DEPTH); - }); - }); - }); - - describe("Binary AccQueue0 progressive merges", () => { - const SUB_DEPTH = 2; - const MAIN_DEPTH = 5; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - const NUM_SUBTREES = 5; - let aq: AccQueue; - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract as AccQueueContract; - }); - - it(`should progressively merge ${NUM_SUBTREES} subtrees`, async () => { - for (let i = 0; i < NUM_SUBTREES; i += 1) { - const leaf = BigInt(123); - // eslint-disable-next-line no-await-in-loop - await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); - aq.enqueue(leaf); - - aq.fill(); - // eslint-disable-next-line no-await-in-loop - await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - } - - aq.mergeSubRoots(0); - const expectedSmallSRTroot = aq.getSmallSRTroot(); - - await expect(aqContract.getSmallSRTroot()).to.be.revertedWithCustomError(aqContract, "SubTreesNotMerged"); - - await aqContract.mergeSubRoots(2).then((tx) => tx.wait()); - await aqContract.mergeSubRoots(2).then((tx) => tx.wait()); - await aqContract.mergeSubRoots(1).then((tx) => tx.wait()); - - const contractSmallSRTroot = await aqContract.getSmallSRTroot(); - expect(expectedSmallSRTroot.toString()).to.eq(contractSmallSRTroot.toString()); - - aq.merge(MAIN_DEPTH); - await (await aqContract.merge(MAIN_DEPTH)).wait(); - - const expectedMainRoot = aq.getMainRoots()[MAIN_DEPTH]; - const contractMainRoot = await aqContract.getMainRoot(MAIN_DEPTH); - - expect(expectedMainRoot.toString()).to.eq(contractMainRoot.toString()); - }); - }); - - describe("Quinary AccQueue0 progressive merges", () => { - const SUB_DEPTH = 2; - const MAIN_DEPTH = 5; - const HASH_LENGTH = 5; - const ZERO = BigInt(0); - const NUM_SUBTREES = 6; - let aq: AccQueue; - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract as AccQueueContract; - }); - - it(`should progressively merge ${NUM_SUBTREES} subtrees`, async () => { - for (let i = 0; i < NUM_SUBTREES; i += 1) { - const leaf = BigInt(123); - // eslint-disable-next-line no-await-in-loop - await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); - aq.enqueue(leaf); - - aq.fill(); - // eslint-disable-next-line no-await-in-loop - await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - } - - aq.mergeSubRoots(0); - const expectedSmallSRTroot = aq.getSmallSRTroot(); - - await expect(aqContract.getSmallSRTroot()).to.be.revertedWithCustomError(aqContract, "SubTreesNotMerged"); - - await (await aqContract.mergeSubRoots(2)).wait(); - await (await aqContract.mergeSubRoots(2)).wait(); - await (await aqContract.mergeSubRoots(2)).wait(); - - const contractSmallSRTroot = await aqContract.getSmallSRTroot(); - expect(expectedSmallSRTroot.toString()).to.eq(contractSmallSRTroot.toString()); - - aq.merge(MAIN_DEPTH); - await (await aqContract.merge(MAIN_DEPTH)).wait(); - - const expectedMainRoot = aq.getMainRoots()[MAIN_DEPTH]; - const contractMainRoot = await aqContract.getMainRoot(MAIN_DEPTH); - - expect(expectedMainRoot.toString()).to.eq(contractMainRoot.toString()); - }); - }); - - describe("Conditions that cause merge() to revert", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - const NUM_SUBTREES = 1; - let aqContract: AccQueueContract; - - before(async () => { - const r = await deployTestAccQueues(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aqContract = r.aqContract as AccQueueContract; - }); - - it("mergeSubRoots() should fail on an empty AccQueue", async () => { - await expect(aqContract.mergeSubRoots(0, { gasLimit: 1000000 })).to.be.revertedWithCustomError( - aqContract, - "NothingToMerge", - ); - }); - - it("merge() should revert on an empty AccQueue", async () => { - await expect(aqContract.merge(1, { gasLimit: 1000000 })).to.be.revertedWithCustomError( - aqContract, - "SubTreesNotMerged", - ); - }); - - it(`merge() should revert if there are unmerged subtrees`, async () => { - for (let i = 0; i < NUM_SUBTREES; i += 1) { - // eslint-disable-next-line no-await-in-loop - await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - } - - await expect(aqContract.merge(1)).to.be.revertedWithCustomError(aqContract, "SubTreesNotMerged"); - }); - - it(`merge() should revert if the desired depth is invalid`, async () => { - await aqContract.mergeSubRoots(0, { gasLimit: 1000000 }).then((tx) => tx.wait()); - - await expect(aqContract.merge(0, { gasLimit: 1000000 })).to.be.revertedWithCustomError( - aqContract, - "DepthCannotBeZero", - ); - }); - }); -}); diff --git a/packages/contracts/tests/AccQueueBenchmark.test.ts b/packages/contracts/tests/AccQueueBenchmark.test.ts deleted file mode 100644 index 981ea51769..0000000000 --- a/packages/contracts/tests/AccQueueBenchmark.test.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { expect } from "chai"; -import { AccQueue, NOTHING_UP_MY_SLEEVE } from "maci-crypto"; - -import { linkPoseidonLibraries } from "../tasks/helpers/abi"; -import { deployPoseidonContracts, createContractFactory } from "../ts/deploy"; -import { getDefaultSigner } from "../ts/utils"; -import { - AccQueueBinary0__factory as AccQueueBinary0Factory, - AccQueue as AccQueueContract, - AccQueueQuinary0__factory as AccQueueQuinary0Factory, - AccQueueQuinaryMaci__factory as AccQueueQuinaryMaciFactory, -} from "../typechain-types"; - -let aqContract: AccQueueContract; - -const deploy = async ( - factory: typeof AccQueueBinary0Factory | typeof AccQueueQuinary0Factory | typeof AccQueueQuinaryMaciFactory, - SUB_DEPTH: number, - HASH_LENGTH: number, - ZERO: bigint, -) => { - const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(await getDefaultSigner(), {}, true); - const [poseidonT3ContractAddress, poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress] = - await Promise.all([ - PoseidonT3Contract.getAddress(), - PoseidonT4Contract.getAddress(), - PoseidonT5Contract.getAddress(), - PoseidonT6Contract.getAddress(), - ]); - - // Link Poseidon contracts - const AccQueueFactory = await createContractFactory( - factory.abi, - factory.linkBytecode( - linkPoseidonLibraries( - poseidonT3ContractAddress, - poseidonT4ContractAddress, - poseidonT5ContractAddress, - poseidonT6ContractAddress, - ), - ), - await getDefaultSigner(), - ); - - aqContract = (await AccQueueFactory.deploy(SUB_DEPTH)) as typeof aqContract; - - await aqContract.deploymentTransaction()?.wait(); - - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - return { aq, aqContract }; -}; - -const testMerge = async ( - aq: AccQueue, - contract: AccQueueContract, - NUM_SUBTREES: number, - MAIN_DEPTH: number, - NUM_MERGES: number, -) => { - for (let i = 0; i < NUM_SUBTREES; i += 1) { - const leaf = BigInt(123); - // eslint-disable-next-line no-await-in-loop - await contract.enqueue(leaf.toString(), { gasLimit: 200000 }).then((tx) => tx.wait()); - - aq.enqueue(leaf); - aq.fill(); - - // eslint-disable-next-line no-await-in-loop - await contract.fill({ gasLimit: 2000000 }).then((t) => t.wait()); - } - - if (NUM_MERGES === 0) { - aq.mergeSubRoots(NUM_MERGES); - const tx = await contract.mergeSubRoots(NUM_MERGES, { gasLimit: 8000000 }); - const receipt = await tx.wait(); - - expect(receipt).to.not.eq(null); - expect(receipt?.gasUsed.toString()).to.not.eq(""); - expect(receipt?.gasUsed.toString()).to.not.eq("0"); - } else { - for (let i = 0; i < NUM_MERGES; i += 1) { - const n = NUM_SUBTREES / NUM_MERGES; - aq.mergeSubRoots(n); - // eslint-disable-next-line no-await-in-loop - const receipt = await contract.mergeSubRoots(n, { gasLimit: 8000000 }).then((tx) => tx.wait()); - - expect(receipt).to.not.eq(null); - expect(receipt?.gasUsed.toString()).to.not.eq(""); - expect(receipt?.gasUsed.toString()).to.not.eq("0"); - } - } - - const expectedSmallSRTroot = aq.getSmallSRTroot(); - - const contractSmallSRTroot = await contract.getSmallSRTroot(); - - expect(expectedSmallSRTroot.toString()).to.eq(contractSmallSRTroot.toString()); - - aq.merge(MAIN_DEPTH); - const receipt = await contract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); - - expect(receipt).to.not.eq(null); - expect(receipt?.gasUsed.toString()).to.not.eq(""); - expect(receipt?.gasUsed.toString()).to.not.eq("0"); - - const expectedMainRoot = aq.getMainRoots()[MAIN_DEPTH]; - const contractMainRoot = await contract.getMainRoot(MAIN_DEPTH); - - expect(expectedMainRoot.toString()).to.eq(contractMainRoot.toString()); -}; - -const testOneShot = async (aq: AccQueue, contract: AccQueueContract, NUM_SUBTREES: number, MAIN_DEPTH: number) => { - await testMerge(aq, contract, NUM_SUBTREES, MAIN_DEPTH, 0); -}; - -const testMultiShot = async ( - aq: AccQueue, - contract: AccQueueContract, - NUM_SUBTREES: number, - MAIN_DEPTH: number, - NUM_MERGES: number, -) => { - await testMerge(aq, contract, NUM_SUBTREES, MAIN_DEPTH, NUM_MERGES); -}; - -describe("AccQueue gas benchmarks", () => { - describe("Binary enqueues", () => { - const SUB_DEPTH = 3; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - before(async () => { - const r = await deploy(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aqContract = r.aqContract; - }); - - it(`should enqueue to a subtree of depth ${SUB_DEPTH}`, async () => { - for (let i = 0; i < HASH_LENGTH ** SUB_DEPTH; i += 1) { - // eslint-disable-next-line no-await-in-loop - const receipt = await aqContract.enqueue(i, { gasLimit: 400000 }).then((tx) => tx.wait()); - - expect(receipt).to.not.eq(null); - expect(receipt?.gasUsed.toString()).to.not.eq(""); - expect(receipt?.gasUsed.toString()).to.not.eq("0"); - } - }); - }); - - describe("Binary fills", () => { - const SUB_DEPTH = 3; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - before(async () => { - const r = await deploy(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aqContract = r.aqContract; - }); - - it(`should fill to a subtree of depth ${SUB_DEPTH}`, async () => { - for (let i = 0; i < 2; i += 1) { - // eslint-disable-next-line no-await-in-loop - await aqContract.enqueue(i, { gasLimit: 800000 }).then((tx) => tx.wait()); - // eslint-disable-next-line no-await-in-loop - const receipt = await aqContract.fill({ gasLimit: 800000 }).then((tx) => tx.wait()); - - expect(receipt).to.not.eq(null); - expect(receipt?.gasUsed.toString()).to.not.eq(""); - expect(receipt?.gasUsed.toString()).to.not.eq("0"); - } - }); - }); - - describe("Quinary enqueues", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 5; - const ZERO = BigInt(0); - before(async () => { - const r = await deploy(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aqContract = r.aqContract; - }); - - it(`should enqueue to a subtree of depth ${SUB_DEPTH}`, async () => { - for (let i = 0; i < HASH_LENGTH ** SUB_DEPTH; i += 1) { - // eslint-disable-next-line no-await-in-loop - const receipt = await aqContract.enqueue(i, { gasLimit: 800000 }).then((tx) => tx.wait()); - - expect(receipt).to.not.eq(null); - expect(receipt?.gasUsed.toString()).to.not.eq(""); - expect(receipt?.gasUsed.toString()).to.not.eq("0"); - } - }); - }); - - describe("Quinary fills", () => { - const SUB_DEPTH = 2; - const HASH_LENGTH = 5; - const ZERO = NOTHING_UP_MY_SLEEVE; - before(async () => { - const r = await deploy(AccQueueQuinaryMaciFactory, SUB_DEPTH, HASH_LENGTH, ZERO); - aqContract = r.aqContract; - }); - - it(`should fill a subtree of depth ${SUB_DEPTH}`, async () => { - for (let i = 0; i < 2; i += 1) { - // eslint-disable-next-line no-await-in-loop - await aqContract.enqueue(i, { gasLimit: 800000 }).then((tx) => tx.wait()); - // eslint-disable-next-line no-await-in-loop - const receipt = await aqContract.fill({ gasLimit: 800000 }).then((tx) => tx.wait()); - - expect(receipt).to.not.eq(null); - expect(receipt?.gasUsed.toString()).to.not.eq(""); - expect(receipt?.gasUsed.toString()).to.not.eq("0"); - } - }); - }); - - describe("Binary AccQueue0 one-shot merge", () => { - const SUB_DEPTH = 4; - const MAIN_DEPTH = 32; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - const NUM_SUBTREES = 32; - let aq: AccQueue; - before(async () => { - const r = await deploy(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract; - }); - - it(`should merge ${NUM_SUBTREES} subtrees`, async () => { - await testOneShot(aq, aqContract, NUM_SUBTREES, MAIN_DEPTH); - }); - }); - - describe("Binary AccQueue0 multi-shot merge", () => { - const SUB_DEPTH = 4; - const MAIN_DEPTH = 32; - const HASH_LENGTH = 2; - const ZERO = BigInt(0); - const NUM_SUBTREES = 32; - const NUM_MERGES = 4; - let aq: AccQueue; - before(async () => { - const r = await deploy(AccQueueBinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract; - }); - - it(`should merge ${NUM_SUBTREES} subtrees in ${NUM_MERGES}`, async () => { - await testMultiShot(aq, aqContract, NUM_SUBTREES, MAIN_DEPTH, NUM_MERGES); - }); - }); - - describe("Quinary AccQueue0 one-shot merge", () => { - const SUB_DEPTH = 2; - const MAIN_DEPTH = 32; - const HASH_LENGTH = 5; - const ZERO = BigInt(0); - const NUM_SUBTREES = 25; - let aq: AccQueue; - before(async () => { - const r = await deploy(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract; - }); - - it(`should merge ${NUM_SUBTREES} subtrees`, async () => { - await testOneShot(aq, aqContract, NUM_SUBTREES, MAIN_DEPTH); - }); - }); - - describe("Quinary AccQueue0 multi-shot merge", () => { - const SUB_DEPTH = 2; - const MAIN_DEPTH = 32; - const HASH_LENGTH = 5; - const ZERO = BigInt(0); - const NUM_SUBTREES = 20; - const NUM_MERGES = 4; - let aq: AccQueue; - - before(async () => { - const r = await deploy(AccQueueQuinary0Factory, SUB_DEPTH, HASH_LENGTH, ZERO); - aq = r.aq; - aqContract = r.aqContract; - }); - - it(`should merge ${NUM_SUBTREES} subtrees in ${NUM_MERGES}`, async () => { - await testMultiShot(aq, aqContract, NUM_SUBTREES, MAIN_DEPTH, NUM_MERGES); - }); - }); -}); diff --git a/packages/contracts/tests/MACI.test.ts b/packages/contracts/tests/MACI.test.ts index 71c006e8fa..bbcc411b41 100644 --- a/packages/contracts/tests/MACI.test.ts +++ b/packages/contracts/tests/MACI.test.ts @@ -10,7 +10,14 @@ import { EMode } from "../ts/constants"; import { getDefaultSigner, getSigners } from "../ts/utils"; import { MACI, Poll as PollContract, Poll__factory as PollFactory, Verifier, VkRegistry } from "../typechain-types"; -import { STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, messageBatchSize, treeDepths } from "./constants"; +import { + STATE_TREE_DEPTH, + duration, + initialVoiceCreditBalance, + maxVoteOptions, + messageBatchSize, + treeDepths, +} from "./constants"; import { timeTravel, deployTestContracts } from "./utils"; describe("MACI", function test() { @@ -213,6 +220,7 @@ describe("MACI", function test() { const tx = await maciContract.deployPoll( duration, treeDepths, + messageBatchSize, coordinator.pubKey.asContractParam() as { x: BigNumberish; y: BigNumberish }, verifierContract, vkRegistryContract, @@ -226,7 +234,13 @@ describe("MACI", function test() { expect(receipt?.status).to.eq(1); pollId = (await maciContract.nextPollId()) - 1n; - const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); + const p = maciState.deployPoll( + BigInt(deployTime + duration), + maxVoteOptions, + treeDepths, + messageBatchSize, + coordinator, + ); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message @@ -246,6 +260,7 @@ describe("MACI", function test() { const tx = await maciContract.deployPoll( duration, treeDepths, + messageBatchSize, coordinator.pubKey.asContractParam() as { x: BigNumberish; y: BigNumberish }, verifierContract, vkRegistryContract, @@ -263,6 +278,7 @@ describe("MACI", function test() { .deployPoll( duration, treeDepths, + messageBatchSize, users[0].pubKey.asContractParam() as { x: BigNumberish; y: BigNumberish }, verifierContract, vkRegistryContract, @@ -278,8 +294,8 @@ describe("MACI", function test() { let pollContract: PollContract; before(async () => { - const pollContracts = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContractAddress, signer); }); it("should allow a Poll contract to merge the state tree (calculate the state root)", async () => { @@ -331,10 +347,8 @@ describe("MACI", function test() { describe("getPoll", () => { it("should return an object for a valid id", async () => { - const pollContracts = await maciContract.getPoll(pollId); - expect(pollContracts.poll).to.not.eq(ZeroAddress); - expect(pollContracts.messageProcessor).to.not.eq(ZeroAddress); - expect(pollContracts.tally).to.not.eq(ZeroAddress); + const pollContractAddress = await maciContract.getPoll(pollId); + expect(pollContractAddress).to.not.eq(ZeroAddress); }); it("should throw when given an invalid poll id", async () => { diff --git a/packages/contracts/tests/MessageProcessor.test.ts b/packages/contracts/tests/MessageProcessor.test.ts index 2ecea4e5d8..4cfe01175a 100644 --- a/packages/contracts/tests/MessageProcessor.test.ts +++ b/packages/contracts/tests/MessageProcessor.test.ts @@ -13,8 +13,6 @@ import { MACI, MessageProcessor, MessageProcessor__factory as MessageProcessorFactory, - Poll as PollContract, - Poll__factory as PollFactory, Verifier, VkRegistry, } from "../typechain-types"; @@ -23,6 +21,7 @@ import { STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, + maxVoteOptions, messageBatchSize, testProcessVk, testTallyVk, @@ -33,7 +32,6 @@ import { timeTravel, deployTestContracts } from "./utils"; describe("MessageProcessor", () => { // contracts let maciContract: MACI; - let pollContract: PollContract; let verifierContract: Verifier; let vkRegistryContract: VkRegistry; let mpContract: MessageProcessor; @@ -65,6 +63,7 @@ describe("MessageProcessor", () => { const tx = await maciContract.deployPoll( duration, treeDepths, + messageBatchSize, coordinator.pubKey.asContractParam(), verifierContract, vkRegistryContract, @@ -75,26 +74,44 @@ describe("MessageProcessor", () => { // extract poll id expect(receipt?.status).to.eq(1); - pollId = (await maciContract.nextPollId()) - 1n; + const iface = maciContract.interface; + const logs = receipt!.logs[receipt!.logs.length - 1]; + const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { + args: { + _pollId: bigint; + pollAddr: { + poll: string; + messageProcessor: string; + tally: string; + }; + }; + }; - const pollContracts = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContracts.poll, signer); + pollId = event.args._pollId; - mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); + mpContract = MessageProcessorFactory.connect(event.args.pollAddr.messageProcessor, signer); const block = await signer.provider!.getBlock(receipt!.blockHash); const deployTime = block!.timestamp; // deploy local poll - const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); + const p = maciState.deployPoll( + BigInt(deployTime + duration), + maxVoteOptions, + treeDepths, + messageBatchSize, + coordinator, + ); expect(p.toString()).to.eq(pollId.toString()); - // publish the NOTHING_UP_MY_SLEEVE message - const messageData = [NOTHING_UP_MY_SLEEVE]; - for (let i = 1; i < 10; i += 1) { - messageData.push(BigInt(0)); + const messages = []; + for (let i = 0; i <= 24; i += 1) { + const messageData = [NOTHING_UP_MY_SLEEVE]; + for (let j = 1; j < 10; j += 1) { + messageData.push(BigInt(0)); + } + messages.push(new Message(messageData)); } - const message = new Message(messageData); const padKey = new PubKey([ BigInt("10457101036533406547632367118273992217979173478358440826365724437999023779287"), BigInt("19824078218392094440610104313265183977899662750282163392862422243483260492317"), @@ -102,7 +119,9 @@ describe("MessageProcessor", () => { poll = maciState.polls.get(pollId)!; - poll.publishMessage(message, padKey); + for (let i = 0; i <= 24; i += 1) { + poll.publishMessage(messages[i], padKey); + } // update the poll state poll.updatePoll(BigInt(maciState.stateLeaves.length)); @@ -113,7 +132,6 @@ describe("MessageProcessor", () => { vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -124,26 +142,11 @@ describe("MessageProcessor", () => { expect(receipt?.status).to.eq(1); }); - describe("before merging acc queues", () => { + describe("testing with more messages", () => { before(async () => { await timeTravel(signer.provider! as unknown as EthereumProvider, duration + 1); }); - it("processMessages() should fail if the state AQ has not been merged", async () => { - await expect( - mpContract.processMessages(BigInt(generatedInputs.newSbCommitment), [0, 0, 0, 0, 0, 0, 0, 0]), - ).to.be.revertedWithCustomError(mpContract, "StateNotMerged"); - }); - }); - - describe("after merging acc queues", () => { - before(async () => { - await pollContract.mergeMaciState(); - - await pollContract.mergeMessageAqSubRoots(0); - await pollContract.mergeMessageAq(); - }); - it("processMessages() should update the state and ballot root commitment", async () => { // Submit the proof const tx = await mpContract.processMessages(BigInt(generatedInputs.newSbCommitment), [0, 0, 0, 0, 0, 0, 0, 0]); diff --git a/packages/contracts/tests/Poll.test.ts b/packages/contracts/tests/Poll.test.ts index c91db34b6a..dfb246749b 100644 --- a/packages/contracts/tests/Poll.test.ts +++ b/packages/contracts/tests/Poll.test.ts @@ -8,21 +8,13 @@ import { Keypair, Message, PCommand, PubKey } from "maci-domainobjs"; import { EMode } from "../ts/constants"; import { getDefaultSigner } from "../ts/utils"; -import { - AccQueue, - AccQueueQuinaryMaci__factory as AccQueueQuinaryMaciFactory, - Poll__factory as PollFactory, - MACI, - Poll as PollContract, - Verifier, - VkRegistry, -} from "../typechain-types"; +import { Poll__factory as PollFactory, MACI, Poll as PollContract, Verifier, VkRegistry } from "../typechain-types"; import { - MESSAGE_TREE_DEPTH, STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, + maxVoteOptions, messageBatchSize, treeDepths, } from "./constants"; @@ -58,6 +50,7 @@ describe("Poll", () => { const tx = await maciContract.deployPoll( duration, treeDepths, + messageBatchSize, coordinator.pubKey.asContractParam(), verifierContract, vkRegistryContract, @@ -72,11 +65,17 @@ describe("Poll", () => { pollId = (await maciContract.nextPollId()) - 1n; - const pollContracts = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContractAddress, signer); // deploy local poll - const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); + const p = maciState.deployPoll( + BigInt(deployTime + duration), + maxVoteOptions, + treeDepths, + messageBatchSize, + coordinator, + ); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message const messageData = [NOTHING_UP_MY_SLEEVE]; @@ -109,12 +108,15 @@ describe("Poll", () => { expect(dd[1].toString()).to.eq(duration.toString()); }); + it("should have the correct max values set", async () => { + const mvo = await pollContract.maxVoteOptions(); + expect(mvo.toString()).to.eq(maxVoteOptions.toString()); + }); + it("should have the correct tree depths set", async () => { const td = await pollContract.treeDepths(); expect(td[0].toString()).to.eq(treeDepths.intStateTreeDepth.toString()); - expect(td[1].toString()).to.eq(treeDepths.messageTreeSubDepth.toString()); - expect(td[2].toString()).to.eq(treeDepths.messageTreeDepth.toString()); - expect(td[3].toString()).to.eq(treeDepths.voteOptionTreeDepth.toString()); + expect(td[1].toString()).to.eq(treeDepths.voteOptionTreeDepth.toString()); }); it("should have numMessages set to 1 (blank message)", async () => { @@ -135,6 +137,7 @@ describe("Poll", () => { testMaciContract.deployPoll( duration, treeDepths, + messageBatchSize, { x: "100", y: "1", @@ -249,52 +252,21 @@ describe("Poll", () => { }); }); - describe("Merge messages", () => { - let messageAqContract: AccQueue; + describe("Message hash chain", () => { + it("should correctly compute chain hash and batch hashes array", async () => { + const currentChainHash = await pollContract.chainHash(); + const currentBatchHashes = await pollContract.getBatchHashes(); - beforeEach(async () => { - const extContracts = await pollContract.extContracts(); - - const messageAqAddress = extContracts.messageAq; - messageAqContract = AccQueueQuinaryMaciFactory.connect(messageAqAddress, signer); + expect(currentChainHash).to.eq(maciState.polls.get(pollId)?.chainHash); + expect(currentBatchHashes).to.deep.equal(maciState.polls.get(pollId)?.batchHashes); }); - it("should allow to merge the message AccQueue", async () => { - let tx = await pollContract.mergeMessageAqSubRoots(0); - let receipt = await tx.wait(); - expect(receipt?.status).to.eq(1); - - tx = await pollContract.mergeMessageAq(); - receipt = await tx.wait(); - expect(receipt?.status).to.eq(1); - }); - - it("should have the correct message root set", async () => { - const onChainMessageRoot = await messageAqContract.getMainRoot(MESSAGE_TREE_DEPTH); - const offChainMessageRoot = maciState.polls.get(pollId)!.messageTree.root; - - expect(onChainMessageRoot.toString()).to.eq(offChainMessageRoot.toString()); - }); - - it("should prevent merging subroots again", async () => { - await expect(pollContract.mergeMessageAqSubRoots(0)).to.be.revertedWithCustomError( - messageAqContract, - "SubTreesAlreadyMerged", - ); - }); - - it("should not change the message root if merging a second time", async () => { - await pollContract.mergeMessageAq(); - const onChainMessageRoot = await messageAqContract.getMainRoot(MESSAGE_TREE_DEPTH); - const offChainMessageRoot = maciState.polls.get(pollId)!.messageTree.root; - - expect(onChainMessageRoot.toString()).to.eq(offChainMessageRoot.toString()); - }); + it("should correctly pad batch hash array with zeros", async () => { + await pollContract.padLastBatch(); + maciState.polls.get(pollId)?.padLastBatch(); - it("should emit an event with the same root when merging another time", async () => { - expect(await pollContract.mergeMessageAq()) - .to.emit(pollContract, "MergeMessageAq") - .withArgs(maciState.polls.get(pollId)!.messageTree.root); + expect(await pollContract.chainHash()).to.eq(maciState.polls.get(pollId)?.chainHash); + expect(await pollContract.getBatchHashes()).to.deep.eq(maciState.polls.get(pollId)?.batchHashes); }); }); }); diff --git a/packages/contracts/tests/PollFactory.test.ts b/packages/contracts/tests/PollFactory.test.ts index 258325a79b..3cd8e4f3b7 100644 --- a/packages/contracts/tests/PollFactory.test.ts +++ b/packages/contracts/tests/PollFactory.test.ts @@ -1,23 +1,29 @@ import { expect } from "chai"; -import { BaseContract, Signer, ZeroAddress } from "ethers"; +import { BaseContract, Signer } from "ethers"; import { Keypair } from "maci-domainobjs"; -import { deployPollFactory, genEmptyBallotRoots, getDefaultSigner } from "../ts"; -import { PollFactory } from "../typechain-types"; +import { deployPollFactory, getDefaultSigner } from "../ts"; +import { MACI, PollFactory, Verifier, VkRegistry } from "../typechain-types"; -import { STATE_TREE_DEPTH, treeDepths } from "./constants"; +import { messageBatchSize, initialVoiceCreditBalance, maxVoteOptions, STATE_TREE_DEPTH, treeDepths } from "./constants"; +import { deployTestContracts } from "./utils"; describe("pollFactory", () => { + let maciContract: MACI; + let verifierContract: Verifier; + let vkRegistryContract: VkRegistry; let pollFactory: PollFactory; let signer: Signer; const { pubKey: coordinatorPubKey } = new Keypair(); - const emptyBallotRoots = genEmptyBallotRoots(STATE_TREE_DEPTH); - const emptyBallotRoot = emptyBallotRoots[treeDepths.voteOptionTreeDepth]; - before(async () => { signer = await getDefaultSigner(); + const r = await deployTestContracts({ initialVoiceCreditBalance, stateTreeDepth: STATE_TREE_DEPTH, signer }); + maciContract = r.maciContract; + verifierContract = r.mockVerifierContract as Verifier; + vkRegistryContract = r.vkRegistryContract; + pollFactory = (await deployPollFactory(signer, undefined, true)) as BaseContract as PollFactory; }); @@ -25,13 +31,28 @@ describe("pollFactory", () => { it("should allow anyone to deploy a new poll", async () => { const tx = await pollFactory.deploy( "100", + maxVoteOptions, treeDepths, + messageBatchSize, coordinatorPubKey.asContractParam(), - ZeroAddress, - emptyBallotRoot, + { maci: maciContract, verifier: verifierContract, vkRegistry: vkRegistryContract }, ); const receipt = await tx.wait(); expect(receipt?.status).to.eq(1); }); + + it("should revert when called with an invalid param for max vote options", async () => { + const maxVoteOptionsInvalid = 2 ** 50; + await expect( + pollFactory.deploy( + "100", + maxVoteOptionsInvalid, + treeDepths, + messageBatchSize, + coordinatorPubKey.asContractParam(), + { maci: maciContract, verifier: verifierContract, vkRegistry: vkRegistryContract }, + ), + ).to.be.revertedWithCustomError(pollFactory, "InvalidMaxVoteOptions"); + }); }); }); diff --git a/packages/contracts/tests/Tally.test.ts b/packages/contracts/tests/Tally.test.ts index 2a1b0f4717..32666a77df 100644 --- a/packages/contracts/tests/Tally.test.ts +++ b/packages/contracts/tests/Tally.test.ts @@ -25,6 +25,7 @@ import { STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, + maxVoteOptions, messageBatchSize, testProcessVk, testTallyVk, @@ -67,6 +68,7 @@ describe("TallyVotes", () => { const tx = await maciContract.deployPoll( duration, treeDepths, + messageBatchSize, coordinator.pubKey.asContractParam(), verifierContract, vkRegistryContract, @@ -79,15 +81,36 @@ describe("TallyVotes", () => { expect(receipt?.status).to.eq(1); - pollId = (await maciContract.nextPollId()) - 1n; + const iface = maciContract.interface; + const logs = receipt!.logs[receipt!.logs.length - 1]; + const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { + args: { + _pollId: bigint; + pollAddr: { + poll: string; + messageProcessor: string; + tally: string; + }; + }; + name: string; + }; + expect(event.name).to.eq("DeployPoll"); + + pollId = event.args._pollId; - const pollContracts = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContracts.poll, signer); - mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); - tallyContract = TallyFactory.connect(pollContracts.tally, signer); + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContractAddress, signer); + mpContract = MessageProcessorFactory.connect(event.args.pollAddr.messageProcessor, signer); + tallyContract = TallyFactory.connect(event.args.pollAddr.tally, signer); // deploy local poll - const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); + const p = maciState.deployPoll( + BigInt(deployTime + duration), + maxVoteOptions, + treeDepths, + messageBatchSize, + coordinator, + ); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message const messageData = [NOTHING_UP_MY_SLEEVE]; @@ -115,7 +138,6 @@ describe("TallyVotes", () => { await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -148,14 +170,12 @@ describe("TallyVotes", () => { ); }); - describe("after merging acc queues", () => { + describe("after messages processing", () => { let tallyGeneratedInputs: ITallyCircuitInputs; before(async () => { await pollContract.mergeMaciState(); - await pollContract.mergeMessageAqSubRoots(0); - await pollContract.mergeMessageAq(); tallyGeneratedInputs = poll.tallyVotes(); }); @@ -225,6 +245,7 @@ describe("TallyVotes", () => { ...treeDepths, intStateTreeDepth, }, + messageBatchSize, coordinator.pubKey.asContractParam(), verifierContract, vkRegistryContract, @@ -237,16 +258,32 @@ describe("TallyVotes", () => { expect(receipt?.status).to.eq(1); - pollId = (await maciContract.nextPollId()) - 1n; + const iface = maciContract.interface; + const logs = receipt!.logs[receipt!.logs.length - 1]; + const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { + args: { + _pollId: bigint; + pollAddr: { + poll: string; + messageProcessor: string; + tally: string; + }; + }; + name: string; + }; + expect(event.name).to.eq("DeployPoll"); + + pollId = event.args._pollId; - const pollContracts = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContracts.poll, signer); - mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); - tallyContract = TallyFactory.connect(pollContracts.tally, signer); + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContractAddress, signer); + mpContract = MessageProcessorFactory.connect(event.args.pollAddr.messageProcessor, signer); + tallyContract = TallyFactory.connect(event.args.pollAddr.tally, signer); // deploy local poll const p = maciState.deployPoll( BigInt(deployTime + updatedDuration), + maxVoteOptions, { ...treeDepths, intStateTreeDepth, @@ -278,7 +315,6 @@ describe("TallyVotes", () => { await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -289,9 +325,6 @@ describe("TallyVotes", () => { await timeTravel(signer.provider! as unknown as EthereumProvider, updatedDuration); await pollContract.mergeMaciState(); - await pollContract.mergeMessageAqSubRoots(0); - await pollContract.mergeMessageAq(); - const processMessagesInputs = poll.processMessages(pollId); await mpContract.processMessages(BigInt(processMessagesInputs.newSbCommitment), [0, 0, 0, 0, 0, 0, 0, 0]); @@ -509,6 +542,7 @@ describe("TallyVotes", () => { ...treeDepths, intStateTreeDepth, }, + messageBatchSize, coordinator.pubKey.asContractParam(), verifierContract, vkRegistryContract, @@ -521,16 +555,32 @@ describe("TallyVotes", () => { expect(receipt?.status).to.eq(1); - pollId = (await maciContract.nextPollId()) - 1n; + const iface = maciContract.interface; + const logs = receipt!.logs[receipt!.logs.length - 1]; + const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { + args: { + _pollId: bigint; + pollAddr: { + poll: string; + messageProcessor: string; + tally: string; + }; + }; + name: string; + }; + expect(event.name).to.eq("DeployPoll"); + + pollId = event.args._pollId; - const pollContracts = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContracts.poll, signer); - mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); - tallyContract = TallyFactory.connect(pollContracts.tally, signer); + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContractAddress, signer); + mpContract = MessageProcessorFactory.connect(event.args.pollAddr.messageProcessor, signer); + tallyContract = TallyFactory.connect(event.args.pollAddr.tally, signer); // deploy local poll const p = maciState.deployPoll( BigInt(deployTime + updatedDuration), + maxVoteOptions, { ...treeDepths, intStateTreeDepth, @@ -562,7 +612,6 @@ describe("TallyVotes", () => { await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -573,9 +622,6 @@ describe("TallyVotes", () => { await timeTravel(signer.provider! as unknown as EthereumProvider, updatedDuration); await pollContract.mergeMaciState(); - await pollContract.mergeMessageAqSubRoots(0); - await pollContract.mergeMessageAq(); - const processMessagesInputs = poll.processMessages(pollId); await mpContract.processMessages(BigInt(processMessagesInputs.newSbCommitment), [0, 0, 0, 0, 0, 0, 0, 0]); diff --git a/packages/contracts/tests/TallyNonQv.test.ts b/packages/contracts/tests/TallyNonQv.test.ts index 6e22e81fae..3125a76ec8 100644 --- a/packages/contracts/tests/TallyNonQv.test.ts +++ b/packages/contracts/tests/TallyNonQv.test.ts @@ -21,7 +21,15 @@ import { Tally__factory as TallyFactory, } from "../typechain-types"; -import { STATE_TREE_DEPTH, duration, messageBatchSize, testProcessVk, testTallyVk, treeDepths } from "./constants"; +import { + STATE_TREE_DEPTH, + duration, + maxVoteOptions, + messageBatchSize, + testProcessVk, + testTallyVk, + treeDepths, +} from "./constants"; import { timeTravel, deployTestContracts } from "./utils"; describe("TallyVotesNonQv", () => { @@ -56,6 +64,7 @@ describe("TallyVotesNonQv", () => { const tx = await maciContract.deployPoll( duration, treeDepths, + messageBatchSize, coordinator.pubKey.asContractParam(), verifierContract, vkRegistryContract, @@ -68,15 +77,36 @@ describe("TallyVotesNonQv", () => { expect(receipt?.status).to.eq(1); - pollId = (await maciContract.nextPollId()) - 1n; - - const pollContracts = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContracts.poll, signer); - mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); - tallyContract = TallyFactory.connect(pollContracts.tally, signer); + const iface = maciContract.interface; + const logs = receipt!.logs[receipt!.logs.length - 1]; + const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { + args: { + _pollId: bigint; + pollAddr: { + poll: string; + messageProcessor: string; + tally: string; + }; + }; + name: string; + }; + expect(event.name).to.eq("DeployPoll"); + + pollId = event.args._pollId; + + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContractAddress, signer); + mpContract = MessageProcessorFactory.connect(event.args.pollAddr.messageProcessor, signer); + tallyContract = TallyFactory.connect(event.args.pollAddr.tally, signer); // deploy local poll - const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); + const p = maciState.deployPoll( + BigInt(deployTime + duration), + maxVoteOptions, + treeDepths, + messageBatchSize, + coordinator, + ); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message const messageData = [NOTHING_UP_MY_SLEEVE]; @@ -104,7 +134,6 @@ describe("TallyVotesNonQv", () => { await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.NON_QV, @@ -137,13 +166,11 @@ describe("TallyVotesNonQv", () => { ); }); - describe("after merging acc queues", () => { + describe("after messages processing", () => { let tallyGeneratedInputs: ITallyCircuitInputs; before(async () => { await pollContract.mergeMaciState(); - await pollContract.mergeMessageAqSubRoots(0); - await pollContract.mergeMessageAq(); tallyGeneratedInputs = poll.tallyVotes(); }); diff --git a/packages/contracts/tests/VkRegistry.test.ts b/packages/contracts/tests/VkRegistry.test.ts index a3ce0e0993..e587f5e096 100644 --- a/packages/contracts/tests/VkRegistry.test.ts +++ b/packages/contracts/tests/VkRegistry.test.ts @@ -36,7 +36,6 @@ describe("VkRegistry", () => { const tx = await vkRegistryContract.setVerifyingKeys( stateTreeDepth, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -52,7 +51,6 @@ describe("VkRegistry", () => { vkRegistryContract.setVerifyingKeys( stateTreeDepth, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -66,7 +64,6 @@ describe("VkRegistry", () => { const tx = await vkRegistryContract.setVerifyingKeys( stateTreeDepth + 1, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -81,7 +78,6 @@ describe("VkRegistry", () => { const tx = await vkRegistryContract.setVerifyingKeys( stateTreeDepth + 1, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.NON_QV, @@ -98,7 +94,6 @@ describe("VkRegistry", () => { const tx = await vkRegistryContract.setVerifyingKeysBatch( stateTreeDepth, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, [EMode.NON_QV], @@ -115,7 +110,6 @@ describe("VkRegistry", () => { vkRegistryContract.setVerifyingKeysBatch( stateTreeDepth, treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, [EMode.QV], @@ -135,7 +129,6 @@ describe("VkRegistry", () => { expect( await vkRegistryContract.hasProcessVk( stateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -147,7 +140,6 @@ describe("VkRegistry", () => { expect( await vkRegistryContract.hasProcessVk( stateTreeDepth + 2, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, EMode.QV, @@ -186,7 +178,6 @@ describe("VkRegistry", () => { it("should generate a valid signature", async () => { const sig = await vkRegistryContract.genProcessVkSig( stateTreeDepth, - treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, ); diff --git a/packages/contracts/tests/constants.ts b/packages/contracts/tests/constants.ts index 6786d07f8a..3758375f44 100644 --- a/packages/contracts/tests/constants.ts +++ b/packages/contracts/tests/constants.ts @@ -1,4 +1,4 @@ -import { TreeDepths, STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "maci-core"; +import { TreeDepths, STATE_TREE_ARITY } from "maci-core"; import { G1Point, G2Point } from "maci-crypto"; import { VerifyingKey } from "maci-domainobjs"; @@ -7,7 +7,7 @@ export const duration = 2_000; export const STATE_TREE_DEPTH = 10; export const MESSAGE_TREE_DEPTH = 2; export const MESSAGE_TREE_SUBDEPTH = 1; -export const messageBatchSize = MESSAGE_TREE_ARITY ** MESSAGE_TREE_SUBDEPTH; +export const messageBatchSize = 20; export const testProcessVk = new VerifyingKey( new G1Point(BigInt(0), BigInt(1)), @@ -42,11 +42,10 @@ export const testTallyVkNonQv = new VerifyingKey( ); export const initialVoiceCreditBalance = 100; +export const maxVoteOptions = 25; export const treeDepths: TreeDepths = { intStateTreeDepth: 1, - messageTreeDepth: MESSAGE_TREE_DEPTH, - messageTreeSubDepth: MESSAGE_TREE_SUBDEPTH, voteOptionTreeDepth: 2, }; diff --git a/packages/contracts/tests/utils.ts b/packages/contracts/tests/utils.ts index faea6d096c..035d559402 100644 --- a/packages/contracts/tests/utils.ts +++ b/packages/contracts/tests/utils.ts @@ -1,30 +1,17 @@ /* eslint-disable import/no-extraneous-dependencies */ import { expect } from "chai"; -import { BaseContract } from "ethers"; -import { IncrementalQuinTree, AccQueue, calcDepthFromNumLeaves, hash2, hash5 } from "maci-crypto"; import { IVkContractParams, VerifyingKey } from "maci-domainobjs"; import type { IDeployedTestContracts, IDeployedTestContractsArgs } from "../ts/types"; import type { EthereumProvider } from "hardhat/types"; -import { linkPoseidonLibraries } from "../tasks/helpers/abi"; -import { getDefaultSigner } from "../ts"; import { deployConstantInitialVoiceCreditProxy, deployFreeForAllSignUpGatekeeper, deployMaci, deployMockVerifier, - deployPoseidonContracts, deployVkRegistry, - createContractFactory, } from "../ts/deploy"; -import { - AccQueueBinary0__factory as AccQueueBinary0Factory, - AccQueueBinaryMaci__factory as AccQueueBinaryMaciFactory, - AccQueue as AccQueueContract, - AccQueueQuinary0__factory as AccQueueQuinary0Factory, - AccQueueQuinaryMaci__factory as AccQueueQuinaryMaciFactory, -} from "../typechain-types"; export const insertSubTreeGasLimit = { gasLimit: 300000 }; export const enqueueGasLimit = { gasLimit: 500000 }; @@ -67,427 +54,6 @@ export const compareVks = (vk: VerifyingKey, vkOnChain: IVkContractParams): void expect(vk.gamma2.y[1].toString()).to.eq(vkOnChain.gamma2.y[1].toString()); }; -/** - * Deploy an AccQueue contract and setup a local TS instance of an AccQueue class - * @param contractName - the name of the contract to deploy - * @param SUB_DEPTH - the depth of the subtrees - * @param HASH_LENGTH - the number of leaves in each subtree - * @param ZERO - the zero value to be used as leaf - * @returns the AccQueue class instance and the AccQueue contract - */ -export const deployTestAccQueues = async ( - factory: - | typeof AccQueueBinary0Factory - | typeof AccQueueQuinary0Factory - | typeof AccQueueQuinaryMaciFactory - | typeof AccQueueBinaryMaciFactory, - SUB_DEPTH: number, - HASH_LENGTH: number, - ZERO: bigint, -): Promise<{ aq: AccQueue; aqContract: BaseContract }> => { - const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(await getDefaultSigner(), {}, true); - - const [poseidonT3ContractAddress, poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress] = - await Promise.all([ - PoseidonT3Contract.getAddress(), - PoseidonT4Contract.getAddress(), - PoseidonT5Contract.getAddress(), - PoseidonT6Contract.getAddress(), - ]); - // Link Poseidon contracts - const accQueueFactory = await createContractFactory( - factory.abi, - factory.linkBytecode( - linkPoseidonLibraries( - poseidonT3ContractAddress, - poseidonT4ContractAddress, - poseidonT5ContractAddress, - poseidonT6ContractAddress, - ), - ), - await getDefaultSigner(), - ); - - const aqContract = await accQueueFactory.deploy(SUB_DEPTH); - - await aqContract.deploymentTransaction()?.wait(); - - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - return { aq, aqContract }; -}; - -/** - * Test whether fill() works for an empty subtree - * @param aq - the AccQueue class instance - * @param aqContract - the AccQueue contract - * @param index - the index of the subtree - */ -export const testEmptySubtree = async (aq: AccQueue, aqContract: AccQueueContract, index: number): Promise => { - aq.fill(); - const tx = await aqContract.fill(fillGasLimit); - await tx.wait(); - const subRoot = await aqContract.getSubRoot(index); - expect(subRoot.toString()).to.equal(aq.getSubRoot(index).toString()); -}; - -/** - * Insert one leaf and compute the subroot - * @param aq - the AccQueue class instance - * @param aqContract - the AccQueue contract - */ -export const testIncompleteSubtree = async (aq: AccQueue, aqContract: AccQueueContract): Promise => { - const leaf = BigInt(1); - - aq.enqueue(leaf); - await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); - - aq.fill(); - await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - - const subRoot = await aqContract.getSubRoot(1); - expect(subRoot.toString()).to.equal(aq.getSubRoot(1).toString()); -}; - -/** - * Test whether fill() works for every number of leaves in an incomplete subtree - * @param aq - the AccQueue class instance - * @param aqContract - the AccQueue contract - * @param HASH_LENGTH - the number of leaves in each subtree - */ -export const testFillForAllIncompletes = async ( - aq: AccQueue, - aqContract: AccQueueContract, - HASH_LENGTH: number, -): Promise => { - for (let i = 0; i < HASH_LENGTH; i += 1) { - for (let j = 0; j < i; j += 1) { - const leaf = BigInt(i + 1); - aq.enqueue(leaf); - // eslint-disable-next-line no-await-in-loop - await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); - } - aq.fill(); - // eslint-disable-next-line no-await-in-loop - await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - - // eslint-disable-next-line no-await-in-loop - const subRoot = await aqContract.getSubRoot(3 + i); - expect(subRoot.toString()).to.equal(aq.getSubRoot(3 + i).toString()); - } -}; - -/** - * Test whether the AccQueue is empty upon deployment - * @param aqContract - the AccQueue contract - */ -export const testEmptyUponDeployment = async (aqContract: AccQueueContract): Promise => { - const numLeaves = await aqContract.numLeaves(); - expect(numLeaves.toString()).to.equal("0"); - - await expect(aqContract.getSubRoot(0)).to.be.revertedWithCustomError(aqContract, "InvalidIndex"); -}; - -/** - * Enqueue leaves and check their subroots - * @param aqContract - the AccQueue contract - * @param HASH_LENGTH - the number of leaves in each subtree - * @param SUB_DEPTH - the depth of the subtrees - * @param ZERO - the zero value to be used as leaf - */ -export const testEnqueue = async ( - aqContract: AccQueueContract, - HASH_LENGTH: number, - SUB_DEPTH: number, - ZERO: bigint, -): Promise => { - const hashFunc = HASH_LENGTH === 5 ? hash5 : hash2; - const tree0 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, hashFunc); - const subtreeCapacity = HASH_LENGTH ** SUB_DEPTH; - - // Insert up to a subtree - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree0.insert(leaf); - - // eslint-disable-next-line no-await-in-loop - await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); - } - - let numLeaves = await aqContract.numLeaves(); - expect(numLeaves.toString()).to.eq(subtreeCapacity.toString()); - - const r = await aqContract.getSubRoot(0); - expect(r.toString()).to.eq(tree0.root.toString()); - - const tree1 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, hashFunc); - - // Insert the other subtree - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 2); - tree1.insert(leaf); - - // eslint-disable-next-line no-await-in-loop - await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); - } - - numLeaves = await aqContract.numLeaves(); - expect(numLeaves.toString()).to.eq((subtreeCapacity * 2).toString()); - - const subroot1 = await aqContract.getSubRoot(1); - expect(subroot1.toString()).to.eq(tree1.root.toString()); -}; - -/** - * Insert subtrees directly - * @param aq - the AccQueue class instance - * @param aqContract - the AccQueue contract - * @param NUM_SUBTREES - the number of subtrees to insert - */ -export const testInsertSubTrees = async ( - aq: AccQueue, - aqContract: AccQueueContract, - NUM_SUBTREES: number, - MAIN_DEPTH: number, -): Promise => { - const leaves: bigint[] = []; - for (let i = 0; i < NUM_SUBTREES; i += 1) { - const subTree = new IncrementalQuinTree(aq.getSubDepth(), aq.getZeros()[0], aq.getHashLength(), aq.hashFunc); - const leaf = BigInt(i); - subTree.insert(leaf); - leaves.push(leaf); - - // insert the subtree root - aq.insertSubTree(subTree.root); - // eslint-disable-next-line no-await-in-loop - await aqContract.insertSubTree(subTree.root.toString(), insertSubTreeGasLimit).then((tx) => tx.wait()); - } - - let correctRoot: string; - if (NUM_SUBTREES === 1) { - correctRoot = aq.getSubRoots()[0].toString(); - } else { - const depth = calcDepthFromNumLeaves(aq.getHashLength(), aq.getSubRoots().length); - const tree = new IncrementalQuinTree(depth, aq.getZeros()[aq.getSubDepth()], aq.getHashLength(), aq.hashFunc); - - aq.getSubRoots().forEach((subRoot) => { - tree.insert(subRoot); - }); - - correctRoot = tree.root.toString(); - } - - // Check whether mergeSubRoots() works - aq.mergeSubRoots(0); - await aqContract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); - - const expectedSmallSRTroot = aq.getSmallSRTroot().toString(); - - expect(correctRoot).to.eq(expectedSmallSRTroot); - - const contractSmallSRTroot = await aqContract.getSmallSRTroot(); - expect(expectedSmallSRTroot.toString()).to.eq(contractSmallSRTroot.toString()); - - // Check whether merge() works - aq.merge(MAIN_DEPTH); - await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); - - const expectedMainRoot = aq.getMainRoots()[MAIN_DEPTH]; - const contractMainRoot = await aqContract.getMainRoot(MAIN_DEPTH); - - expect(expectedMainRoot.toString()).to.eq(contractMainRoot.toString()); -}; - -/** - * The order of leaves when using enqueue() and insertSubTree() should be correct. - * @param aq - the AccQueue class instance - * @param aqContract - the AccQueue contract - */ -export const testEnqueueAndInsertSubTree = async (aq: AccQueue, aqContract: AccQueueContract): Promise => { - const [z] = aq.getZeros(); - const n = BigInt(1); - - const leaves: bigint[] = []; - - const subTree = new IncrementalQuinTree(aq.getSubDepth(), z, aq.getHashLength(), aq.hashFunc); - - for (let i = 0; i < aq.getHashLength() ** aq.getSubDepth(); i += 1) { - leaves.push(z); - } - - leaves.push(n); - // leaves is now [z, z, z, z..., n] - - const depth = calcDepthFromNumLeaves(aq.getHashLength(), leaves.length); - const tree = new IncrementalQuinTree(depth, z, aq.getHashLength(), aq.hashFunc); - - leaves.forEach((leaf) => { - tree.insert(leaf); - }); - - const expectedRoot = tree.root.toString(); - - aq.enqueue(n); - await aqContract.enqueue(n.toString(), enqueueGasLimit).then((tx) => tx.wait()); - - aq.insertSubTree(subTree.root); - await aqContract.insertSubTree(subTree.root.toString(), insertSubTreeGasLimit).then((tx) => tx.wait()); - - aq.fill(); - await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - - aq.mergeSubRoots(0); - await aqContract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); - - expect(expectedRoot).to.eq(aq.getSmallSRTroot().toString()); - - const contractSmallSRTroot = await aqContract.getSmallSRTroot(); - expect(expectedRoot).to.eq(contractSmallSRTroot.toString()); -}; - -/** - * Insert a number of subtrees and merge them all into a main tree - * @param aq - the AccQueue class instance - * @param aqContract - the AccQueue contract - */ -export const testMerge = async ( - aq: AccQueue, - aqContract: AccQueueContract, - NUM_SUBTREES: number, - MAIN_DEPTH: number, -): Promise => { - // The raw leaves of the main tree - const leaves: bigint[] = []; - for (let i = 0; i < NUM_SUBTREES; i += 1) { - const leaf = BigInt(i); - - aq.enqueue(leaf); - aq.fill(); - // eslint-disable-next-line no-await-in-loop - await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); - // eslint-disable-next-line no-await-in-loop - await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - - leaves.push(leaf); - - for (let j = 1; j < aq.getHashLength() ** aq.getSubDepth(); j += 1) { - leaves.push(aq.getZeros()[0]); - } - } - - // Insert leaves into a main tree - const tree = new IncrementalQuinTree(MAIN_DEPTH, aq.getZeros()[0], aq.getHashLength(), aq.hashFunc); - - leaves.forEach((leaf) => { - tree.insert(leaf); - }); - - // minHeight should be the small SRT height - const minHeight = await aqContract.calcMinHeight(); - const c = calcDepthFromNumLeaves(aq.getHashLength(), NUM_SUBTREES); - expect(minHeight.toString()).to.eq(c.toString()); - - // Check whether mergeSubRoots() works - aq.mergeSubRoots(0); - await (await aqContract.mergeSubRoots(0, { gasLimit: 8000000 })).wait(); - - const expectedSmallSRTroot = aq.getSmallSRTroot().toString(); - const contractSmallSRTroot = (await aqContract.getSmallSRTroot()).toString(); - - expect(expectedSmallSRTroot).to.eq(contractSmallSRTroot); - - if (NUM_SUBTREES === 1) { - expect(expectedSmallSRTroot).to.eq(aq.getSubRoots()[0].toString()); - } else { - // Check whether the small SRT root is correct - const srtHeight = calcDepthFromNumLeaves(aq.getHashLength(), NUM_SUBTREES); - const smallTree = new IncrementalQuinTree( - srtHeight, - aq.getZeros()[aq.getSubDepth()], - aq.getHashLength(), - aq.hashFunc, - ); - - aq.getSubRoots().forEach((subRoot) => { - smallTree.insert(subRoot); - }); - - expect(expectedSmallSRTroot).to.eq(smallTree.root.toString()); - } - - // Check whether mergeDirect() works - const aq2 = aq.copy(); - - aq2.mergeDirect(MAIN_DEPTH); - const directlyMergedRoot = aq2.getMainRoots()[MAIN_DEPTH].toString(); - expect(directlyMergedRoot.toString()).to.eq(tree.root.toString()); - - // Check whether off-chain merge() works - aq.merge(MAIN_DEPTH); - - const expectedMainRoot = aq.getMainRoots()[MAIN_DEPTH].toString(); - - expect(expectedMainRoot).to.eq(directlyMergedRoot); - - // Check whether on-chain merge() works - await (await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 })).wait(); - const contractMainRoot = (await aqContract.getMainRoot(MAIN_DEPTH)).toString(); - expect(expectedMainRoot).to.eq(contractMainRoot); -}; - -/** - * Enqueue, merge, enqueue, and merge again - * @param aq - the AccQueue class instance - * @param aqContract - the AccQueue contract - */ -export const testMergeAgain = async (aq: AccQueue, aqContract: AccQueueContract, MAIN_DEPTH: number): Promise => { - const tree = new IncrementalQuinTree(MAIN_DEPTH, aq.getZeros()[0], aq.getHashLength(), aq.hashFunc); - const leaf = BigInt(123); - - // Enqueue - aq.enqueue(leaf); - await aqContract.enqueue(leaf.toString()).then((tx) => tx.wait()); - tree.insert(leaf); - - // Merge - aq.mergeDirect(MAIN_DEPTH); - await aqContract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); - await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); - - for (let i = 1; i < aq.getHashLength() ** aq.getSubDepth(); i += 1) { - tree.insert(aq.getZeros()[0]); - } - - const mainRoot = (await aqContract.getMainRoot(MAIN_DEPTH)).toString(); - const expectedMainRoot = aq.getMainRoots()[MAIN_DEPTH].toString(); - expect(expectedMainRoot).to.eq(mainRoot); - expect(expectedMainRoot).to.eq(tree.root.toString()); - - const leaf2 = BigInt(456); - - // Enqueue - aq.enqueue(leaf2); - await aqContract.enqueue(leaf2.toString()).then((tx) => tx.wait()); - tree.insert(leaf2); - - // Merge - aq.mergeDirect(MAIN_DEPTH); - await aqContract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); - await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); - - for (let i = 1; i < aq.getHashLength() ** aq.getSubDepth(); i += 1) { - tree.insert(aq.getZeros()[0]); - } - - const mainRoot2 = (await aqContract.getMainRoot(MAIN_DEPTH)).toString(); - const expectedMainRoot2 = aq.getMainRoots()[MAIN_DEPTH].toString(); - expect(expectedMainRoot2).to.eq(tree.root.toString()); - - expect(expectedMainRoot2).not.to.eq(expectedMainRoot); - expect(expectedMainRoot2).to.eq(mainRoot2); -}; - /** * Deploy a set of smart contracts that can be used for testing. * @param initialVoiceCreditBalance - the initial voice credit balance for each user diff --git a/packages/contracts/ts/genMaciState.ts b/packages/contracts/ts/genMaciState.ts index 84b9e7669f..f6f90136c0 100644 --- a/packages/contracts/ts/genMaciState.ts +++ b/packages/contracts/ts/genMaciState.ts @@ -1,6 +1,6 @@ /* eslint-disable no-underscore-dangle */ import { type Provider } from "ethers"; -import { MaciState, MESSAGE_TREE_ARITY, STATE_TREE_ARITY } from "maci-core"; +import { MaciState } from "maci-core"; import { type Keypair, PubKey, Message } from "maci-domainobjs"; import assert from "assert"; @@ -91,17 +91,17 @@ export const genMaciStateFromContract = async ( const pubKey = new PubKey([BigInt(event.args._coordinatorPubKeyX), BigInt(event.args._coordinatorPubKeyY)]); // eslint-disable-next-line no-await-in-loop - const pollContracts = await maciContract.getPoll(id); + const pollAddr = await maciContract.getPoll(id); actions.push({ type: "DeployPoll", blockNumber: event.blockNumber, transactionIndex: event.transactionIndex, - data: { pollId: id, pollAddr: pollContracts.poll, pubKey }, + data: { pollId: id, pollAddr, pubKey }, }); foundPollIds.add(Number(id)); - pollContractAddresses.set(BigInt(id), pollContracts.poll); + pollContractAddresses.set(BigInt(id), pollAddr); } if (sleepAmount) { @@ -116,38 +116,33 @@ export const genMaciStateFromContract = async ( const pollContractAddress = pollContractAddresses.get(pollId)!; const pollContract = PollFactory.connect(pollContractAddress, provider); - const [coordinatorPubKeyHashOnChain, [deployTime, duration], onChainTreeDepths] = await Promise.all([ - pollContract.coordinatorPubKeyHash(), - pollContract.getDeployTimeAndDuration().then((values) => values.map(Number)), - pollContract.treeDepths(), - ]); + const [coordinatorPubKeyOnChain, [deployTime, duration], onChainMaxVoteOptions, onChainTreeDepths, msgBatchSize] = + await Promise.all([ + pollContract.coordinatorPubKey(), + pollContract.getDeployTimeAndDuration().then((values) => values.map(Number)), + pollContract.maxVoteOptions(), + pollContract.treeDepths(), + pollContract.messageBatchSize(), + ]); + + assert(coordinatorPubKeyOnChain[0].toString() === coordinatorKeypair.pubKey.rawPubKey[0].toString()); + assert(coordinatorPubKeyOnChain[1].toString() === coordinatorKeypair.pubKey.rawPubKey[1].toString()); - assert(coordinatorKeypair.pubKey.hash().toString() === coordinatorPubKeyHashOnChain.toString()); + const maxVoteOptions = Number(onChainMaxVoteOptions); const treeDepths = { intStateTreeDepth: Number(onChainTreeDepths.intStateTreeDepth), - messageTreeDepth: Number(onChainTreeDepths.messageTreeDepth), - messageTreeSubDepth: Number(onChainTreeDepths.messageTreeSubDepth), voteOptionTreeDepth: Number(onChainTreeDepths.voteOptionTreeDepth), }; - const batchSizes = { - tallyBatchSize: STATE_TREE_ARITY ** Number(onChainTreeDepths.intStateTreeDepth), - messageBatchSize: MESSAGE_TREE_ARITY ** Number(onChainTreeDepths.messageTreeSubDepth), - }; + const messageBatchSize = Number(msgBatchSize); // fetch poll contract logs for (let i = fromBlock; i <= lastBlock; i += blocksPerRequest + 1) { const toBlock = i + blocksPerRequest >= lastBlock ? lastBlock : i + blocksPerRequest; - const [ - publishMessageLogs, - mergeMessageAqLogs, - // eslint-disable-next-line no-await-in-loop - ] = await Promise.all([ - pollContract.queryFilter(pollContract.filters.PublishMessage(), i, toBlock), - pollContract.queryFilter(pollContract.filters.MergeMessageAq(), i, toBlock), - ]); + // eslint-disable-next-line no-await-in-loop + const publishMessageLogs = await pollContract.queryFilter(pollContract.filters.PublishMessage(), i, toBlock); publishMessageLogs.forEach((event) => { assert(!!event); @@ -167,18 +162,6 @@ export const genMaciStateFromContract = async ( }); }); - mergeMessageAqLogs.forEach((event) => { - assert(!!event); - - const messageRoot = BigInt((event.args as unknown as { _messageRoot: string })._messageRoot); - actions.push({ - type: "MergeMessageAq", - blockNumber: event.blockNumber, - transactionIndex: event.transactionIndex, - data: { messageRoot }, - }); - }); - if (sleepAmount) { // eslint-disable-next-line no-await-in-loop await sleep(sleepAmount); @@ -198,8 +181,9 @@ export const genMaciStateFromContract = async ( case action.type === "DeployPoll" && action.data.pollId?.toString() === pollId.toString(): { maciState.deployPoll( BigInt(deployTime + duration), + maxVoteOptions, treeDepths, - batchSizes.messageBatchSize, + messageBatchSize, coordinatorKeypair, ); break; @@ -216,12 +200,6 @@ export const genMaciStateFromContract = async ( break; } - // ensure that the message root is correct (i.e. all messages have been published offchain) - case action.type === "MergeMessageAq": { - assert(maciState.polls.get(pollId)?.messageTree.root.toString() === action.data.messageRoot?.toString()); - break; - } - default: break; } diff --git a/packages/core/ts/MaciState.ts b/packages/core/ts/MaciState.ts index 9a79fd4458..e62e840656 100644 --- a/packages/core/ts/MaciState.ts +++ b/packages/core/ts/MaciState.ts @@ -56,6 +56,7 @@ export class MaciState implements IMaciState { /** * Deploy a new poll with the given parameters. * @param pollEndTimestamp - The Unix timestamp at which the poll ends. + * @param maxVoteOptions - The maximum number of vote option. * @param treeDepths - The depths of the tree. * @param messageBatchSize - The batch size for processing messages. * @param coordinatorKeypair - The keypair of the MACI round coordinator. @@ -63,6 +64,7 @@ export class MaciState implements IMaciState { */ deployPoll( pollEndTimestamp: bigint, + maxVoteOptions: number, treeDepths: TreeDepths, messageBatchSize: number, coordinatorKeypair: Keypair, @@ -75,6 +77,7 @@ export class MaciState implements IMaciState { messageBatchSize, tallyBatchSize: STATE_TREE_ARITY ** treeDepths.intStateTreeDepth, }, + maxVoteOptions, this, ); diff --git a/packages/core/ts/Poll.ts b/packages/core/ts/Poll.ts index 459c4fec82..91f5800be2 100644 --- a/packages/core/ts/Poll.ts +++ b/packages/core/ts/Poll.ts @@ -39,7 +39,7 @@ import type { } from "./utils/types"; import type { PathElements } from "maci-crypto"; -import { STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "./utils/constants"; +import { STATE_TREE_ARITY, VOTE_OPTION_TREE_ARITY } from "./utils/constants"; import { ProcessMessageErrors, ProcessMessageError } from "./utils/errors"; /** @@ -54,16 +54,14 @@ export class Poll implements IPoll { batchSizes: BatchSizes; + maxVoteOptions: number; + // the depth of the state tree stateTreeDepth: number; // the actual depth of the state tree (can be <= stateTreeDepth) actualStateTreeDepth: number; - maxVoteOptions: number; - - maxMessages: number; - pollEndTimestamp: bigint; ballots: Ballot[] = []; @@ -72,8 +70,6 @@ export class Poll implements IPoll { messages: Message[] = []; - messageTree: IncrementalQuinTree; - commands: PCommand[] = []; encPubKeys: PubKey[] = []; @@ -87,7 +83,7 @@ export class Poll implements IPoll { // For message processing numBatchesProcessed = 0; - currentMessageBatchIndex?: number; + currentMessageBatchIndex: number; maciStateRef: MaciState; @@ -116,6 +112,12 @@ export class Poll implements IPoll { emptyBallotHash?: bigint; + // message chain hash + chainHash = NOTHING_UP_MY_SLEEVE; + + // batch chain hashes + batchHashes = [NOTHING_UP_MY_SLEEVE]; + // how many users signed up private numSignups = 0n; @@ -125,6 +127,7 @@ export class Poll implements IPoll { * @param coordinatorKeypair - The keypair of the coordinator. * @param treeDepths - The depths of the trees used in the poll. * @param batchSizes - The sizes of the batches used in the poll. + * @param maxVoteOptions - The maximum vote options the MACI circuits can accept. * @param maciStateRef - The reference to the MACI state. */ constructor( @@ -132,26 +135,20 @@ export class Poll implements IPoll { coordinatorKeypair: Keypair, treeDepths: TreeDepths, batchSizes: BatchSizes, + maxVoteOptions: number, maciStateRef: MaciState, ) { this.pollEndTimestamp = pollEndTimestamp; this.coordinatorKeypair = coordinatorKeypair; this.treeDepths = treeDepths; this.batchSizes = batchSizes; + this.maxVoteOptions = maxVoteOptions; this.maciStateRef = maciStateRef; this.pollId = BigInt(maciStateRef.polls.size); this.stateTreeDepth = maciStateRef.stateTreeDepth; this.actualStateTreeDepth = maciStateRef.stateTreeDepth; + this.currentMessageBatchIndex = 0; - this.messageTree = new IncrementalQuinTree( - this.treeDepths.messageTreeDepth, - NOTHING_UP_MY_SLEEVE, - MESSAGE_TREE_ARITY, - hash5, - ); - - this.maxVoteOptions = MESSAGE_TREE_ARITY ** this.treeDepths.voteOptionTreeDepth; - this.maxMessages = MESSAGE_TREE_ARITY ** this.treeDepths.messageTreeDepth; this.tallyResult = new Array(this.maxVoteOptions).fill(0n) as bigint[]; this.perVOSpentVoiceCredits = new Array(this.maxVoteOptions).fill(0n) as bigint[]; @@ -167,8 +164,8 @@ export class Poll implements IPoll { */ updatePoll = (numSignups: bigint): void => { // there might be occasions where we fetch logs after new signups have been made - // logs are fetched (and MaciState/Poll created locally) after stateAq have been - // merged in. If someone signs up after that and we fetch that record + // logs are fetched (and MaciState/Poll created locally). + // If someone signs up after that and we fetch that record // then we won't be able to verify the processing on chain as the data will // not match. For this, we must only copy up to the number of signups @@ -292,7 +289,7 @@ export class Poll implements IPoll { const originalBallotPathElements = this.ballotTree?.genProof(Number(stateLeafIndex)).pathElements; // create a new quinary tree where we insert the votes of the origin (up until this message is processed) ballot - const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, MESSAGE_TREE_ARITY, hash5); + const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, VOTE_OPTION_TREE_ARITY, hash5); for (let i = 0; i < this.ballots[0].votes.length; i += 1) { vt.insert(ballot.votes[i]); } @@ -342,7 +339,9 @@ export class Poll implements IPoll { // store the message locally this.messages.push(message); // add the message hash to the message tree - this.messageTree.insert(message.hash(encPubKey)); + const messageHash = message.hash(encPubKey); + // update chain hash + this.updateChainHash(messageHash); // Decrypt the message and store the Command // step 1. we generate the shared key @@ -360,6 +359,28 @@ export class Poll implements IPoll { } }; + /** + * Updates message chain hash + * @param messageHash hash of message with encPubKey + */ + updateChainHash = (messageHash: bigint): void => { + this.chainHash = hash2([this.chainHash, messageHash]); + + if (this.messages.length % this.batchSizes.messageBatchSize === 0) { + this.batchHashes.push(this.chainHash); + this.currentMessageBatchIndex += 1; + } + }; + + /** + * Pad last unclosed batch + */ + padLastBatch = (): void => { + if (this.messages.length % this.batchSizes.messageBatchSize !== 0) { + this.batchHashes.push(this.chainHash); + } + }; + /** * This method checks if there are any unprocessed messages in the Poll instance. * @returns Returns true if the number of processed batches is @@ -397,45 +418,30 @@ export class Poll implements IPoll { const batchSize = this.batchSizes.messageBatchSize; if (this.numBatchesProcessed === 0) { - // The starting index of the batch of messages to process. - // Note that we process messages in reverse order. - // e.g if there are 8 messages and the batch size is 5, then - // the starting index should be 5. - assert( - this.currentMessageBatchIndex === undefined, - "The current message batch index should not be defined if this is the first batch", - ); // Prevent other polls from being processed until this poll has // been fully processed this.maciStateRef.pollBeingProcessed = true; this.maciStateRef.currentPollBeingProcessed = pollId; - } - // Only allow one poll to be processed at a time - if (this.maciStateRef.pollBeingProcessed) { - assert(this.maciStateRef.currentPollBeingProcessed === pollId, "Another poll is currently being processed"); - } + this.padLastBatch(); - if (this.numBatchesProcessed === 0) { - const r = this.messages.length % batchSize; - - this.currentMessageBatchIndex = this.messages.length; + this.currentMessageBatchIndex = this.batchHashes.length; // if there are messages if (this.currentMessageBatchIndex > 0) { - if (r === 0) { - this.currentMessageBatchIndex -= batchSize; - } else { - this.currentMessageBatchIndex -= r; - } + this.currentMessageBatchIndex -= 1; } this.sbSalts[this.currentMessageBatchIndex] = 0n; } + // Only allow one poll to be processed at a time + if (this.maciStateRef.pollBeingProcessed) { + assert(this.maciStateRef.currentPollBeingProcessed === pollId, "Another poll is currently being processed"); + } + // The starting index must be valid - assert(this.currentMessageBatchIndex! >= 0, "The starting index must be >= 0"); - assert(this.currentMessageBatchIndex! % batchSize === 0, "The starting index must be a multiple of the batch size"); + assert(this.currentMessageBatchIndex >= 0, "The starting index must be >= 0"); // ensure we copy the state from MACI when we start processing the // first batch @@ -445,7 +451,7 @@ export class Poll implements IPoll { // Generate circuit inputs const circuitInputs = stringifyBigInts( - this.genProcessMessagesCircuitInputsPartial(this.currentMessageBatchIndex!), + this.genProcessMessagesCircuitInputsPartial(this.currentMessageBatchIndex), ) as CircuitInputs; // we want to store the state leaves at this point in time @@ -466,7 +472,7 @@ export class Poll implements IPoll { // loop through the batch of messages for (let i = 0; i < batchSize; i += 1) { // we process the messages in reverse order - const idx = this.currentMessageBatchIndex! + batchSize - i - 1; + const idx = this.currentMessageBatchIndex * batchSize - i - 1; assert(idx >= 0, "The message index must be >= 0"); let message: Message; let encPubKey: PubKey; @@ -543,7 +549,12 @@ export class Poll implements IPoll { currentVoteWeights.unshift(ballot.votes[Number(command.voteOptionIndex)]); // create a new quinary tree and add all votes we have so far - const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, MESSAGE_TREE_ARITY, hash5); + const vt = new IncrementalQuinTree( + this.treeDepths.voteOptionTreeDepth, + 0n, + VOTE_OPTION_TREE_ARITY, + hash5, + ); // fill the vote option tree with the votes we have so far for (let j = 0; j < this.ballots[0].votes.length; j += 1) { @@ -556,7 +567,12 @@ export class Poll implements IPoll { currentVoteWeights.unshift(ballot.votes[0]); // create a new quinary tree and add all votes we have so far - const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, MESSAGE_TREE_ARITY, hash5); + const vt = new IncrementalQuinTree( + this.treeDepths.voteOptionTreeDepth, + 0n, + VOTE_OPTION_TREE_ARITY, + hash5, + ); // fill the vote option tree with the votes we have so far for (let j = 0; j < this.ballots[0].votes.length; j += 1) { @@ -577,7 +593,12 @@ 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, 0n, MESSAGE_TREE_ARITY, hash5); + const vt = new IncrementalQuinTree( + this.treeDepths.voteOptionTreeDepth, + 0n, + VOTE_OPTION_TREE_ARITY, + hash5, + ); vt.insert(this.ballots[0].votes[0]); // get the path elements for this empty vote weight leaf currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements); @@ -598,7 +619,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, 0n, MESSAGE_TREE_ARITY, hash5); + const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, VOTE_OPTION_TREE_ARITY, hash5); vt.insert(this.ballots[0].votes[0]); // get the path elements for this empty vote weight leaf @@ -625,16 +646,16 @@ export class Poll implements IPoll { // record that we processed one batch this.numBatchesProcessed += 1; - if (this.currentMessageBatchIndex! > 0) { - this.currentMessageBatchIndex! -= batchSize; + if (this.currentMessageBatchIndex > 0) { + this.currentMessageBatchIndex -= 1; } // ensure newSbSalt differs from currentSbSalt let newSbSalt = genRandomSalt(); - while (this.sbSalts[this.currentMessageBatchIndex!] === newSbSalt) { + while (this.sbSalts[this.currentMessageBatchIndex] === newSbSalt) { newSbSalt = genRandomSalt(); } - this.sbSalts[this.currentMessageBatchIndex!] = newSbSalt; + this.sbSalts[this.currentMessageBatchIndex] = newSbSalt; // store the salt in the circuit inputs circuitInputs.newSbSalt = newSbSalt; @@ -644,10 +665,6 @@ export class Poll implements IPoll { // this will be the hash of the roots with a salt circuitInputs.newSbCommitment = hash3([newStateRoot, newBallotRoot, newSbSalt]); - // here is important that a user validates it matches the one in the - // smart contract - circuitInputs.pollEndTimestamp = this.pollEndTimestamp; - // If this is the last batch, release the lock if (this.numBatchesProcessed * batchSize >= this.messages.length) { this.maciStateRef.pollBeingProcessed = false; @@ -669,7 +686,6 @@ export class Poll implements IPoll { const { messageBatchSize } = this.batchSizes; assert(index <= this.messages.length, "The index must be <= the number of messages"); - assert(index % messageBatchSize === 0, "The index must be a multiple of the message batch size"); // fill the msgs array with a copy of the messages we have // plus empty messages to fill the batch @@ -695,26 +711,16 @@ export class Poll implements IPoll { while (msgs.length % messageBatchSize > 0) { msgs.push(msg.asCircuitInputs()); } - // we only take the messages we need for this batch - msgs = msgs.slice(index, index + messageBatchSize); - - // insert zero value in the message tree as padding - while (this.messageTree.nextIndex < index + messageBatchSize) { - this.messageTree.insert(this.messageTree.zeroValue); - } - - // generate the path to the subroot of the message tree for this batch - const messageSubrootPath = this.messageTree.genSubrootProof(index, index + messageBatchSize); - - // verify it - assert(this.messageTree.verifyProof(messageSubrootPath), "The message subroot path is invalid"); + // it slice msgs array from index of first message in current batch to + // index of last message in current batch + msgs = msgs.slice((index - 1) * messageBatchSize, index * messageBatchSize); // validate that the batch index is correct, if not fix it // this means that the end will be the last message - let batchEndIndex = index + messageBatchSize; + let batchEndIndex = index * messageBatchSize; if (batchEndIndex > this.messages.length) { - batchEndIndex = this.messages.length; + batchEndIndex = this.messages.length - (index - 1) * messageBatchSize; } // copy the public keys, pad the array with the last keys if needed @@ -723,11 +729,11 @@ export class Poll implements IPoll { // pad with the public key used to encrypt the message with state index 0 (padding) encPubKeys.push(key.pubKey.copy()); } + // then take the ones part of this batch - encPubKeys = encPubKeys.slice(index, index + messageBatchSize); + encPubKeys = encPubKeys.slice((index - 1) * messageBatchSize, index * messageBatchSize); // cache tree roots - const msgRoot = this.messageTree.root; const currentStateRoot = this.stateTree!.root; const currentBallotRoot = this.ballotTree!.root; // calculate the current state and ballot root @@ -736,23 +742,25 @@ export class Poll implements IPoll { const currentSbCommitment = hash3([ currentStateRoot, currentBallotRoot, - this.sbSalts[this.currentMessageBatchIndex!], + this.sbSalts[this.currentMessageBatchIndex], ]); + const inputBatchHash = this.batchHashes[index - 1]; + const outputBatchHash = this.batchHashes[index]; + return stringifyBigInts({ - pollEndTimestamp: this.pollEndTimestamp, numSignUps: BigInt(this.numSignups), batchEndIndex: BigInt(batchEndIndex), index: BigInt(index), - msgRoot, + inputBatchHash, + outputBatchHash, msgs, - msgSubrootPathElements: messageSubrootPath.pathElements, coordPrivKey: this.coordinatorKeypair.privKey.asCircuitInputs(), encPubKeys: encPubKeys.map((x) => x.asCircuitInputs()), currentStateRoot, currentBallotRoot, currentSbCommitment, - currentSbSalt: this.sbSalts[this.currentMessageBatchIndex!], + currentSbSalt: this.sbSalts[this.currentMessageBatchIndex], }) as CircuitInputs; }; @@ -786,7 +794,7 @@ export class Poll implements IPoll { */ tallyVotes = (): ITallyCircuitInputs => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (this.sbSalts[this.currentMessageBatchIndex!] === undefined) { + if (this.sbSalts[this.currentMessageBatchIndex] === undefined) { throw new Error("You must process the messages first"); } @@ -921,7 +929,7 @@ export class Poll implements IPoll { // cache vars const stateRoot = this.stateTree!.root; const ballotRoot = this.ballotTree!.root; - const sbSalt = this.sbSalts[this.currentMessageBatchIndex!]; + const sbSalt = this.sbSalts[this.currentMessageBatchIndex]; const sbCommitment = hash3([stateRoot, ballotRoot, sbSalt]); const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize); @@ -958,7 +966,7 @@ export class Poll implements IPoll { tallyVotesNonQv = (): ITallyCircuitInputs => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (this.sbSalts[this.currentMessageBatchIndex!] === undefined) { + if (this.sbSalts[this.currentMessageBatchIndex] === undefined) { throw new Error("You must process the messages first"); } @@ -1058,7 +1066,7 @@ export class Poll implements IPoll { // cache vars const stateRoot = this.stateTree!.root; const ballotRoot = this.ballotTree!.root; - const sbSalt = this.sbSalts[this.currentMessageBatchIndex!]; + const sbSalt = this.sbSalts[this.currentMessageBatchIndex]; const sbCommitment = hash3([stateRoot, ballotRoot, sbSalt]); const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize); @@ -1159,14 +1167,13 @@ export class Poll implements IPoll { this.coordinatorKeypair.copy(), { intStateTreeDepth: Number(this.treeDepths.intStateTreeDepth), - messageTreeDepth: Number(this.treeDepths.messageTreeDepth), - messageTreeSubDepth: Number(this.treeDepths.messageTreeSubDepth), voteOptionTreeDepth: Number(this.treeDepths.voteOptionTreeDepth), }, { tallyBatchSize: Number(this.batchSizes.tallyBatchSize.toString()), messageBatchSize: Number(this.batchSizes.messageBatchSize.toString()), }, + this.maxVoteOptions, this.maciStateRef, ); @@ -1182,7 +1189,6 @@ export class Poll implements IPoll { copied.currentMessageBatchIndex = this.currentMessageBatchIndex; copied.maciStateRef = this.maciStateRef; - copied.messageTree = this.messageTree.copy(); copied.tallyResult = this.tallyResult.map((x: bigint) => BigInt(x.toString())); copied.perVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x: bigint) => BigInt(x.toString())); @@ -1227,12 +1233,9 @@ export class Poll implements IPoll { const result = this.coordinatorKeypair.equals(p.coordinatorKeypair) && this.treeDepths.intStateTreeDepth === p.treeDepths.intStateTreeDepth && - this.treeDepths.messageTreeDepth === p.treeDepths.messageTreeDepth && - this.treeDepths.messageTreeSubDepth === p.treeDepths.messageTreeSubDepth && this.treeDepths.voteOptionTreeDepth === p.treeDepths.voteOptionTreeDepth && this.batchSizes.tallyBatchSize === p.batchSizes.tallyBatchSize && this.batchSizes.messageBatchSize === p.batchSizes.messageBatchSize && - this.maxMessages === p.maxMessages && this.maxVoteOptions === p.maxVoteOptions && this.messages.length === p.messages.length && this.encPubKeys.length === p.encPubKeys.length && @@ -1264,15 +1267,18 @@ export class Poll implements IPoll { pollEndTimestamp: this.pollEndTimestamp.toString(), treeDepths: this.treeDepths, batchSizes: this.batchSizes, + maxVoteOptions: this.maxVoteOptions, messages: this.messages.map((message) => message.toJSON()), commands: this.commands.map((command) => command.toJSON()), ballots: this.ballots.map((ballot) => ballot.toJSON()), encPubKeys: this.encPubKeys.map((encPubKey) => encPubKey.serialize()), - currentMessageBatchIndex: this.currentMessageBatchIndex!, + currentMessageBatchIndex: this.currentMessageBatchIndex, stateLeaves: this.stateLeaves.map((leaf) => leaf.toJSON()), results: this.tallyResult.map((result) => result.toString()), numBatchesProcessed: this.numBatchesProcessed, numSignups: this.numSignups.toString(), + chainHash: this.chainHash.toString(), + batchHashes: this.batchHashes.map((batchHash) => batchHash.toString()), }; } @@ -1283,7 +1289,14 @@ export class Poll implements IPoll { * @returns a new Poll instance */ static fromJSON(json: IJsonPoll, maciState: MaciState): Poll { - const poll = new Poll(BigInt(json.pollEndTimestamp), new Keypair(), json.treeDepths, json.batchSizes, maciState); + const poll = new Poll( + BigInt(json.pollEndTimestamp), + new Keypair(), + json.treeDepths, + json.batchSizes, + json.maxVoteOptions, + maciState, + ); // set all properties poll.ballots = json.ballots.map((ballot) => Ballot.fromJSON(ballot)); @@ -1293,12 +1306,8 @@ export class Poll implements IPoll { poll.tallyResult = json.results.map((result: string) => BigInt(result)); poll.currentMessageBatchIndex = json.currentMessageBatchIndex; poll.numBatchesProcessed = json.numBatchesProcessed; - - // fill the trees - for (let i = 0; i < poll.messages.length; i += 1) { - const messageLeaf = poll.messages[i].hash(poll.encPubKeys[i]); - poll.messageTree.insert(messageLeaf); - } + poll.chainHash = BigInt(json.chainHash); + poll.batchHashes = json.batchHashes.map((batchHash: string) => BigInt(batchHash)); // copy maci state poll.updatePoll(BigInt(json.numSignups)); diff --git a/packages/core/ts/__benchmarks__/index.ts b/packages/core/ts/__benchmarks__/index.ts index ef9bb44ccf..72c13f1e20 100644 --- a/packages/core/ts/__benchmarks__/index.ts +++ b/packages/core/ts/__benchmarks__/index.ts @@ -6,6 +6,7 @@ import { MaciState } from ".."; import { COORDINATOR_KEYPAIR, DURATION, + MAX_VALUES, MESSAGE_BATCH_SIZE, STATE_TREE_DEPTH, TREE_DEPTHS, @@ -34,6 +35,7 @@ export default function runCore(): void { const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + DURATION), + MAX_VALUES.maxVoteOptions, TREE_DEPTHS, MESSAGE_BATCH_SIZE, COORDINATOR_KEYPAIR, diff --git a/packages/core/ts/__benchmarks__/utils/constants.ts b/packages/core/ts/__benchmarks__/utils/constants.ts index c3dc2833c0..5c41dcb71d 100644 --- a/packages/core/ts/__benchmarks__/utils/constants.ts +++ b/packages/core/ts/__benchmarks__/utils/constants.ts @@ -6,9 +6,12 @@ export const MESSAGE_BATCH_SIZE = 5; export const COORDINATOR_KEYPAIR = new Keypair(); export const STATE_TREE_DEPTH = 10; +export const MAX_VALUES = { + maxUsers: 25, + maxVoteOptions: 25, +}; + export const TREE_DEPTHS = { intStateTreeDepth: 2, - messageTreeDepth: 2, - messageTreeSubDepth: 2, - voteOptionTreeDepth: 2, + voteOptionTreeDepth: 4, }; diff --git a/packages/core/ts/__tests__/MaciState.test.ts b/packages/core/ts/__tests__/MaciState.test.ts index 3d855e45a6..c14f6ed70c 100644 --- a/packages/core/ts/__tests__/MaciState.test.ts +++ b/packages/core/ts/__tests__/MaciState.test.ts @@ -7,7 +7,14 @@ import { MaciState } from "../MaciState"; import { STATE_TREE_DEPTH } from "../utils/constants"; import { IJsonMaciState } from "../utils/types"; -import { coordinatorKeypair, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; +import { + coordinatorKeypair, + duration, + maxValues, + messageBatchSize, + treeDepths, + voiceCreditBalance, +} from "./utils/constants"; describe("MaciState", function test() { this.timeout(100000); @@ -29,6 +36,7 @@ describe("MaciState", function test() { m1.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = m1.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -71,50 +79,35 @@ describe("MaciState", function test() { m9.polls.get(pollId)!.treeDepths.intStateTreeDepth += 1; expect(m1.equals(m9)).not.to.eq(true); - // modify poll.treeDepths.messageTreeDepth + // modify poll.treeDepths.voteOptionTreeDepth const m10 = m1.copy(); - m10.polls.get(pollId)!.treeDepths.messageTreeDepth += 1; + m10.polls.get(pollId)!.treeDepths.voteOptionTreeDepth += 1; expect(m1.equals(m10)).not.to.eq(true); - // modify poll.treeDepths.messageTreeSubDepth + // modify poll.batchSizes.tallyBatchSize const m11 = m1.copy(); - m11.polls.get(pollId)!.treeDepths.messageTreeSubDepth += 1; + m11.polls.get(pollId)!.batchSizes.tallyBatchSize += 1; expect(m1.equals(m11)).not.to.eq(true); - // modify poll.treeDepths.voteOptionTreeDepth + // modify poll.batchSizes.messageBatchSize const m12 = m1.copy(); - m12.polls.get(pollId)!.treeDepths.voteOptionTreeDepth += 1; + m12.polls.get(pollId)!.batchSizes.messageBatchSize += 1; expect(m1.equals(m12)).not.to.eq(true); - // modify poll.batchSizes.tallyBatchSize + // modify poll.maxVoteOptions const m13 = m1.copy(); - m13.polls.get(pollId)!.batchSizes.tallyBatchSize += 1; + m13.polls.get(pollId)!.maxVoteOptions += 1; expect(m1.equals(m13)).not.to.eq(true); - // modify poll.batchSizes.messageBatchSize + // modify poll.messages const m14 = m1.copy(); - m14.polls.get(pollId)!.batchSizes.messageBatchSize += 1; + m14.polls.get(pollId)!.messages[0].data[0] = BigInt(m14.polls.get(pollId)!.messages[0].data[0]) + 1n; expect(m1.equals(m14)).not.to.eq(true); - // modify poll.maxValues.maxMessages - const m16 = m1.copy(); - m16.polls.get(pollId)!.maxMessages += 1; - expect(m1.equals(m16)).not.to.eq(true); - - // modify poll.maxValues.maxVoteOptions - const m17 = m1.copy(); - m17.polls.get(pollId)!.maxVoteOptions += 1; - expect(m1.equals(m17)).not.to.eq(true); - - // modify poll.messages - const m20 = m1.copy(); - m20.polls.get(pollId)!.messages[0].data[0] = BigInt(m20.polls.get(pollId)!.messages[0].data[0]) + 1n; - expect(m1.equals(m20)).not.to.eq(true); - // modify poll.encPubKeys - const m21 = m1.copy(); - m21.polls.get(pollId)!.encPubKeys[0] = new Keypair().pubKey; - expect(m1.equals(m21)).not.to.eq(true); + const m15 = m1.copy(); + m15.polls.get(pollId)!.encPubKeys[0] = new Keypair().pubKey; + expect(m1.equals(m15)).not.to.eq(true); }); it("should create a JSON object from a MaciState object", () => { @@ -126,7 +119,6 @@ describe("MaciState", function test() { poll.setCoordinatorKeypair(coordinatorKeypair.privKey.serialize()); expect(poll.coordinatorKeypair.equals(coordinatorKeypair)).to.eq(true); }); - expect(state.equals(m1)).to.eq(true); }); }); diff --git a/packages/core/ts/__tests__/Poll.test.ts b/packages/core/ts/__tests__/Poll.test.ts index 4cb968ae2f..af7de7739c 100644 --- a/packages/core/ts/__tests__/Poll.test.ts +++ b/packages/core/ts/__tests__/Poll.test.ts @@ -3,9 +3,16 @@ import { PCommand, Keypair, StateLeaf, PrivKey, Ballot } from "maci-domainobjs"; import { MaciState } from "../MaciState"; import { Poll } from "../Poll"; -import { MESSAGE_TREE_ARITY, STATE_TREE_DEPTH } from "../utils/constants"; +import { STATE_TREE_DEPTH } from "../utils/constants"; -import { coordinatorKeypair, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; +import { + coordinatorKeypair, + duration, + maxValues, + messageBatchSize, + treeDepths, + voiceCreditBalance, +} from "./utils/constants"; describe("Poll", function test() { this.timeout(90000); @@ -14,6 +21,7 @@ describe("Poll", function test() { const maciState = new MaciState(STATE_TREE_DEPTH); const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -114,7 +122,7 @@ describe("Poll", function test() { const command = new PCommand( BigInt(user1StateIndex), user1Keypair.pubKey, - BigInt(MESSAGE_TREE_ARITY ** treeDepths.voteOptionTreeDepth), + BigInt(maxValues.maxVoteOptions), // voice credits spent would be this value ** this value 1n, 1n, @@ -233,6 +241,7 @@ describe("Poll", function test() { const maciState = new MaciState(STATE_TREE_DEPTH); const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -249,29 +258,10 @@ describe("Poll", function test() { BigInt(Math.floor(Date.now() / 1000)), ); - it("should throw if this is the first batch and currentMessageBatchIndex is defined", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // mock - poll.currentMessageBatchIndex = 0; - expect(() => poll.processMessages(pollId)).to.throw( - "The current message batch index should not be defined if this is the first batch", - ); - poll.currentMessageBatchIndex = undefined; - }); - it("should throw if the state has not been copied prior to calling processMessages", () => { const tmpPoll = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -325,6 +315,7 @@ describe("Poll", function test() { const maciState = new MaciState(STATE_TREE_DEPTH); const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -412,6 +403,7 @@ describe("Poll", function test() { const maciState = new MaciState(STATE_TREE_DEPTH); const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -481,6 +473,7 @@ describe("Poll", function test() { // deploy a second poll const secondPollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -529,6 +522,7 @@ describe("Poll", function test() { const maciState = new MaciState(STATE_TREE_DEPTH); const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -547,6 +541,7 @@ describe("Poll", function test() { const maciState = new MaciState(STATE_TREE_DEPTH); const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -572,6 +567,7 @@ describe("Poll", function test() { const maciState = new MaciState(STATE_TREE_DEPTH); const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, diff --git a/packages/core/ts/__tests__/e2e.test.ts b/packages/core/ts/__tests__/e2e.test.ts index daa5c517f5..6822225f8f 100644 --- a/packages/core/ts/__tests__/e2e.test.ts +++ b/packages/core/ts/__tests__/e2e.test.ts @@ -1,12 +1,19 @@ import { expect } from "chai"; -import { hash5, NOTHING_UP_MY_SLEEVE, IncrementalQuinTree, AccQueue, hash2 } from "maci-crypto"; +import { hash5, IncrementalQuinTree, hash2 } from "maci-crypto"; import { PCommand, Keypair, StateLeaf, blankStateLeafHash } from "maci-domainobjs"; import { MaciState } from "../MaciState"; import { Poll } from "../Poll"; -import { STATE_TREE_DEPTH, STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "../utils/constants"; - -import { coordinatorKeypair, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; +import { STATE_TREE_DEPTH, STATE_TREE_ARITY } from "../utils/constants"; + +import { + coordinatorKeypair, + duration, + maxValues, + messageBatchSize, + treeDepths, + voiceCreditBalance, +} from "./utils/constants"; import { TestHarness, calculateTotal } from "./utils/utils"; describe("MaciState/Poll e2e", function test() { @@ -46,6 +53,7 @@ describe("MaciState/Poll e2e", function test() { // deploy a poll pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -148,6 +156,7 @@ describe("MaciState/Poll e2e", function test() { // deploy a poll pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -260,6 +269,7 @@ describe("MaciState/Poll e2e", function test() { // deploy a poll pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -355,6 +365,7 @@ describe("MaciState/Poll e2e", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -381,7 +392,7 @@ describe("MaciState/Poll e2e", function test() { expect(stateTree.root.toString()).to.eq(poll.stateTree?.root.toString()); }); - it("the message root should be correct", () => { + it("Process a batch of messages (though only 1 message is in the batch)", () => { const command = new PCommand( BigInt(stateIndex), userKeypair.pubKey, @@ -399,20 +410,6 @@ describe("MaciState/Poll e2e", function test() { poll.publishMessage(message, ecdhKeypair.pubKey); - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - treeDepths.messageTreeSubDepth, - MESSAGE_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(poll.messageTree.root.toString()); - }); - - it("Process a batch of messages (though only 1 message is in the batch)", () => { poll.processMessages(pollId); // Check the ballot @@ -436,7 +433,7 @@ describe("MaciState/Poll e2e", function test() { }); }); - describe(`Process and tally ${messageBatchSize * 2} messages from ${messageBatchSize} users`, () => { + describe(`Process and tally ${messageBatchSize * 2 - 2} messages from ${messageBatchSize - 1} users`, () => { let maciState: MaciState; let pollId: bigint; let poll: Poll; @@ -456,6 +453,7 @@ describe("MaciState/Poll e2e", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -465,7 +463,7 @@ describe("MaciState/Poll e2e", function test() { }); it("should process votes correctly", () => { - // 4 valid votes + // 19 valid votes for (let i = 0; i < messageBatchSize - 1; i += 1) { const userKeypair = users[i]; @@ -488,9 +486,10 @@ describe("MaciState/Poll e2e", function test() { expect(poll.messages.length).to.eq(messageBatchSize - 1); - // 4 invalid votes + // 19 invalid votes for (let i = 0; i < messageBatchSize - 1; i += 1) { const userKeypair = users[i]; + const command = new PCommand( BigInt(i + 1), userKeypair.pubKey, @@ -508,18 +507,15 @@ describe("MaciState/Poll e2e", function test() { poll.publishMessage(message, ecdhKeypair.pubKey); } - // 48 messages in total + // 38 messages in total expect(poll.messages.length).to.eq(2 * (messageBatchSize - 1)); - expect(poll.currentMessageBatchIndex).to.eq(undefined); + expect(poll.currentMessageBatchIndex).to.eq(1); expect(poll.numBatchesProcessed).to.eq(0); // Process messages poll.processMessages(pollId); - // currentMessageBatchIndex is 0 because the current batch starts - // with index 0. - expect(poll.currentMessageBatchIndex).to.eq(0); expect(poll.numBatchesProcessed).to.eq(1); // Process messages @@ -564,7 +560,7 @@ describe("MaciState/Poll e2e", function test() { // Recall that each user `i` cast the same number of votes for // their option `i` - for (let i = 0; i < messageBatchSize - 1; i += 1) { + for (let i = 1; i < messageBatchSize - 1; i += 1) { expect(poll.tallyResult[i].toString()).to.eq(voteWeight.toString()); } @@ -584,7 +580,6 @@ describe("MaciState/Poll e2e", function test() { let maciState: MaciState; let pollId: bigint; let poll: Poll; - let msgTree: IncrementalQuinTree; let stateTree: IncrementalQuinTree; const voteWeight = 9n; const voteOptionIndex = 0n; @@ -594,11 +589,11 @@ describe("MaciState/Poll e2e", function test() { before(() => { maciState = new MaciState(STATE_TREE_DEPTH); - msgTree = new IncrementalQuinTree(treeDepths.messageTreeDepth, NOTHING_UP_MY_SLEEVE, 5, hash5); stateTree = new IncrementalQuinTree(STATE_TREE_DEPTH, blankStateLeafHash, STATE_TREE_ARITY, hash5); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -631,7 +626,6 @@ describe("MaciState/Poll e2e", function test() { const message = command.encrypt(signature, sharedKey); poll.publishMessage(message, ecdhKeypair.pubKey); - msgTree.insert(message.hash(ecdhKeypair.pubKey)); }); it("Process a batch of messages (though only 1 message is in the batch)", () => { diff --git a/packages/core/ts/__tests__/utils/constants.ts b/packages/core/ts/__tests__/utils/constants.ts index de07f75e7d..a163a7ac06 100644 --- a/packages/core/ts/__tests__/utils/constants.ts +++ b/packages/core/ts/__tests__/utils/constants.ts @@ -2,12 +2,15 @@ import { Keypair } from "maci-domainobjs"; export const voiceCreditBalance = 100n; export const duration = 30; -export const messageBatchSize = 5; +export const messageBatchSize = 20; export const coordinatorKeypair = new Keypair(); +export const maxValues = { + maxUsers: 25, + maxVoteOptions: 25, +}; + export const treeDepths = { intStateTreeDepth: 2, - messageTreeDepth: 2, - messageTreeSubDepth: 2, - voteOptionTreeDepth: 2, + voteOptionTreeDepth: 4, }; diff --git a/packages/core/ts/__tests__/utils/utils.ts b/packages/core/ts/__tests__/utils/utils.ts index 404cd86226..cb3cec6ae8 100644 --- a/packages/core/ts/__tests__/utils/utils.ts +++ b/packages/core/ts/__tests__/utils/utils.ts @@ -5,7 +5,7 @@ import { MaciState } from "../../MaciState"; import { Poll } from "../../Poll"; import { STATE_TREE_DEPTH } from "../../utils/constants"; -import { duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./constants"; +import { duration, maxValues, messageBatchSize, treeDepths, voiceCreditBalance } from "./constants"; /** * Calculates the total of a tally result @@ -36,6 +36,7 @@ export class TestHarness { constructor() { this.pollId = this.maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues.maxVoteOptions, treeDepths, messageBatchSize, this.coordinatorKeypair, diff --git a/packages/core/ts/index.ts b/packages/core/ts/index.ts index 844418da51..e0d71333d5 100644 --- a/packages/core/ts/index.ts +++ b/packages/core/ts/index.ts @@ -13,4 +13,4 @@ export type { IJsonMaciState, } from "./utils/types"; -export { STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "./utils/constants"; +export { STATE_TREE_ARITY, MESSAGE_BATCH_SIZE, VOTE_OPTION_TREE_ARITY } from "./utils/constants"; diff --git a/packages/core/ts/utils/constants.ts b/packages/core/ts/utils/constants.ts index a77a2b5375..ab5175128e 100644 --- a/packages/core/ts/utils/constants.ts +++ b/packages/core/ts/utils/constants.ts @@ -1,5 +1,5 @@ export const STATE_TREE_DEPTH = 10; export const STATE_TREE_ARITY = 2; export const STATE_TREE_SUBDEPTH = 2; -export const MESSAGE_TREE_ARITY = 5; export const VOTE_OPTION_TREE_ARITY = 5; +export const MESSAGE_BATCH_SIZE = 20; diff --git a/packages/core/ts/utils/types.ts b/packages/core/ts/utils/types.ts index f4ffb0a9fc..2cfbf14101 100644 --- a/packages/core/ts/utils/types.ts +++ b/packages/core/ts/utils/types.ts @@ -21,14 +21,10 @@ export type CircuitInputs = Record - (BigInt(batchSize) << 192n) + - (BigInt(stateTreeDepth) << 128n) + - (BigInt(messageTreeDepth) << 64n) + - BigInt(voteOptionTreeDepth); +export const genProcessVkSig = (stateTreeDepth: number, voteOptionTreeDepth: number, batchSize: number): bigint => + (BigInt(batchSize) << 128n) + (BigInt(stateTreeDepth) << 64n) + BigInt(voteOptionTreeDepth); /** * This function generates the signature of a Tally Verifying Key(VK). diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 605b42f07f..d2cf8cfdef 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -15,7 +15,6 @@ "types": "tsc -p tsconfig.json --noEmit", "test": "nyc ts-mocha --exit ts/__tests__/*.test.ts", "test:crypto": "ts-mocha --exit ts/__tests__/Crypto.test.ts", - "test:accQueue": "ts-mocha --exit ts/__tests__/AccQueue.test.ts", "test:utils": "ts-mocha --exit ts/__tests__/Utils.test.ts", "test:imt": "ts-mocha --exit ts/__tests__/IMT.test.ts", "docs": "typedoc --plugin typedoc-plugin-markdown --options ./typedoc.json" diff --git a/packages/crypto/ts/AccQueue.ts b/packages/crypto/ts/AccQueue.ts deleted file mode 100644 index 9d4159478e..0000000000 --- a/packages/crypto/ts/AccQueue.ts +++ /dev/null @@ -1,627 +0,0 @@ -import assert from "assert"; - -import type { Leaf, Queue, StringifiedBigInts } from "./types"; - -import { deepCopyBigIntArray, stringifyBigInts, unstringifyBigInts } from "./bigIntUtils"; -import { sha256Hash, hashLeftRight, hash5 } from "./hashing"; -import { IncrementalQuinTree } from "./quinTree"; -import { calcDepthFromNumLeaves } from "./utils"; - -/** - * An Accumulator Queue which conforms to the implementation in AccQueue.sol. - * Each enqueue() operation updates a subtree, and a merge() operation combines - * all subtrees into a main tree. - * @notice It supports 2 or 5 elements per leaf. - */ -export class AccQueue { - private MAX_DEPTH = 32; - - // The depth per subtree - private subDepth: number; - - // The number of inputs per hash function - private hashLength: number; - - // The default value for empty leaves - private zeroValue: bigint; - - // The current subtree index. e.g. the first subtree has index 0, the - // second has 1, and so on - private currentSubtreeIndex = 0; - - // The number of leaves across all subtrees - private numLeaves = 0; - - // The current subtree - private leafQueue: Queue = { - levels: new Map(), - indices: [], - }; - - // For merging subtrees into the smallest tree - private nextSRindexToQueue = 0; - - private smallSRTroot = 0n; - - private subRootQueue: Queue = { - levels: new Map(), - indices: [], - }; - - // The root of each complete subtree - private subRoots: Leaf[] = []; - - // The root of merged subtrees - private mainRoots: Leaf[] = []; - - // The zero value per level. i.e. zeros[0] is zeroValue, - // zeros[1] is the hash of leavesPerNode zeros, and so on. - private zeros: bigint[] = []; - - // Whether the subtrees have been merged - private subTreesMerged = false; - - // The hash function to use for the subtrees - readonly subHashFunc: (leaves: Leaf[]) => bigint; - - // The hash function to use for rest of the tree (above the subroots) - readonly hashFunc: (leaves: Leaf[]) => bigint; - - /** - * Create a new instance of AccQueue - * @param subDepth - the depth of the subtrees - * @param hashLength - the number of leaves per node - * @param zeroValue - the default value for empty leaves - */ - constructor(subDepth: number, hashLength: number, zeroValue: bigint) { - // This class supports either 2 leaves per node, or 5 leaves per node. - // 5 is largest number of inputs which circomlib's Poseidon EVM hash - // function implementation supports. - - assert(hashLength === 2 || hashLength === 5); - assert(subDepth > 0); - - this.hashLength = hashLength; - this.subDepth = subDepth; - this.zeroValue = zeroValue; - - // Set this.hashFunc depending on the number of leaves per node - if (this.hashLength === 2) { - // Uses PoseidonT3 under the hood, which accepts 2 inputs - this.hashFunc = (inputs: bigint[]) => hashLeftRight(inputs[0], inputs[1]); - } else { - // Uses PoseidonT6 under the hood, which accepts up to 5 inputs - this.hashFunc = hash5; - } - - this.subHashFunc = sha256Hash; - - let hashed = this.zeroValue; - for (let i = 0; i < this.MAX_DEPTH; i += 1) { - this.zeros.push(hashed); - - let e: bigint[] = []; - if (this.hashLength === 2) { - e = [0n]; - hashed = this.hashFunc([hashed, hashed]); - } else { - e = [0n, 0n, 0n, 0n]; - hashed = this.hashFunc([hashed, hashed, hashed, hashed, hashed]); - } - - const levels = new Map(Object.entries(e).map(([key, value]) => [Number(key), value])); - - this.leafQueue.levels.set(this.leafQueue.levels.size, levels); - this.leafQueue.indices[i] = 0; - this.subRootQueue.levels.set(this.subRootQueue.levels.size, levels); - this.subRootQueue.indices[i] = 0; - } - } - - /** - * Get the small SRT root - * @returns small SRT root - */ - getSmallSRTroot(): bigint { - return this.smallSRTroot; - } - - /** - * Get the subroots - * @returns subroots - */ - getSubRoots(): Leaf[] { - return this.subRoots; - } - - /** - * Get the subdepth - * @returns subdepth - */ - getSubDepth(): number { - return this.subDepth; - } - - /** - * Get the root of merged subtrees - * @returns the root of merged subtrees - */ - getMainRoots(): Leaf[] { - return this.mainRoots; - } - - /** - * Get the zero values per level. i.e. zeros[0] is zeroValue, - * zeros[1] is the hash of leavesPerNode zeros, and so on. - * @returns zeros - */ - getZeros(): bigint[] { - return this.zeros; - } - - /** - * Get the subroot at a given index - * @param index - The index of the subroot - * @returns the subroot - */ - getSubRoot(index: number): Leaf { - return this.subRoots[index]; - } - - /** - * Get the number of inputs per hash function - * - * @returns the number of inputs - */ - getHashLength(): number { - return this.hashLength; - } - - /** - * Enqueue a leaf into the current subtree - * @param leaf The leaf to insert. - * @returns The index of the leaf - */ - enqueue(leaf: Leaf): number { - // validation - assert(this.numLeaves < this.hashLength ** this.MAX_DEPTH, "AccQueue is full"); - - this.enqueueOp(leaf, 0); - - // the index is the number of leaves (0-index) - const leafIndex = this.numLeaves; - - // increase the number of leaves - this.numLeaves += 1; - // we set merged false because there are new leaves - this.subTreesMerged = false; - // reset the smallSRTroot because it is obsolete - this.smallSRTroot = 0n; - - // @todo this can be moved in the constructor rather than computing every time - const subTreeCapacity = this.hashLength ** this.subDepth; - // If the current subtree is full - if (this.numLeaves % subTreeCapacity === 0) { - // store the subroot - const subRoot = this.leafQueue.levels.get(this.subDepth)?.get(0) ?? 0n; - - this.subRoots[this.currentSubtreeIndex] = subRoot; - this.currentSubtreeIndex += 1; - // reset the current subtree - this.leafQueue.levels.get(this.subDepth)?.set(0, 0n); - - for (let i = 0; i < this.MAX_DEPTH; i += 1) { - this.leafQueue.indices[i] = 0; - } - } - - return leafIndex; - } - - /** - * Private function that performs the actual enqueue operation - * @param leaf - The leaf to insert - * @param level - The level of the subtree - */ - private enqueueOp = (leaf: Leaf, level: number) => { - // small validation, do no throw - if (level > this.subDepth) { - return; - } - - // get the index to determine where to insert the next leaf - const n = this.leafQueue.indices[level]; - - // we check that the index is not the last one (1 or 4 depending on the hash length) - if (n !== this.hashLength - 1) { - // Just store the leaf - this.leafQueue.levels.get(level)?.set(n, leaf); - this.leafQueue.indices[level] += 1; - } else { - // if not we compute the root - let hashed: bigint; - if (this.hashLength === 2) { - const subRoot = this.leafQueue.levels.get(level)?.get(0) ?? 0n; - hashed = this.hashFunc([subRoot, leaf]); - this.leafQueue.levels.get(level)?.set(0, 0n); - } else { - const levelSlice = this.leafQueue.levels.get(level) ?? new Map(); - hashed = this.hashFunc(Array.from(levelSlice.values()).concat(leaf)); - - for (let i = 0; i < 4; i += 1) { - this.leafQueue.levels.get(level)?.set(i, 0n); - } - } - - this.leafQueue.indices[level] = 0; - - // Recurse - this.enqueueOp(hashed, level + 1); - } - }; - - /** - * Fill any empty leaves of the last subtree with zeros and store the - * resulting subroot. - */ - fill(): void { - // The total capacity of the subtree - const subTreeCapacity = this.hashLength ** this.subDepth; - - if (this.numLeaves % subTreeCapacity === 0) { - // If the subtree is completely empty, then the subroot is a - // precalculated zero value - this.subRoots[this.currentSubtreeIndex] = this.zeros[this.subDepth]; - } else { - this.fillOp(0); - - // Store the subroot - const subRoot = this.leafQueue.levels.get(this.subDepth)?.get(0) ?? 0n; - this.subRoots[this.currentSubtreeIndex] = subRoot; - - // Blank out the subtree data - for (let i = 0; i < this.subDepth + 1; i += 1) { - if (this.hashLength === 2) { - this.leafQueue.levels.get(i)?.set(0, 0n); - } else { - const levels = new Map(Object.entries([0n, 0n, 0n, 0n]).map(([key, value]) => [Number(key), value])); - this.leafQueue.levels.set(i, levels); - } - } - } - - // Update the subtree index - this.currentSubtreeIndex += 1; - - // Update the number of leaves - this.numLeaves = this.currentSubtreeIndex * subTreeCapacity; - - this.subTreesMerged = false; - this.smallSRTroot = 0n; - } - - /** - * Private function that performs the actual fill operation - * @param level - The level of the subtree - */ - private fillOp(level: number) { - if (level > this.subDepth) { - return; - } - - const n = this.leafQueue.indices[level]; - - if (n !== 0) { - // Fill the subtree level and hash it - let hashed: bigint; - if (this.hashLength === 2) { - hashed = this.hashFunc([this.leafQueue.levels.get(level)?.get(0) ?? 0n, this.zeros[level]]); - } else { - for (let i = n; i < this.hashLength; i += 1) { - this.leafQueue.levels.get(level)?.set(i, this.zeros[level]); - } - - const levelSlice = this.leafQueue.levels.get(level) ?? new Map(); - hashed = this.hashFunc(Array.from(levelSlice.values())); - } - - // Update the subtree from the next level onwards with the new leaf - this.enqueueOp(hashed, level + 1); - - // Reset the current level - this.leafQueue.indices[level] = 0; - } - - // Recurse - this.fillOp(level + 1); - } - - /** - * Calculate the depth of the smallest possible Merkle tree which fits all - * @returns the depth of the smallest possible Merkle tree which fits all - */ - calcSRTdepth(): number { - // Calculate the SRT depth - let srtDepth = this.subDepth; - const subTreeCapacity = this.hashLength ** this.subDepth; - while (this.hashLength ** srtDepth < this.subRoots.length * subTreeCapacity) { - srtDepth += 1; - } - - return srtDepth; - } - - /** - * Insert a subtree into the queue. This is used when the subtree is - * already computed. - * @param subRoot - The root of the subtree - */ - insertSubTree(subRoot: bigint): void { - // If the current subtree is not full, fill it. - const subTreeCapacity = this.hashLength ** this.subDepth; - - this.subRoots[this.currentSubtreeIndex] = subRoot; - - // Update the subtree index - this.currentSubtreeIndex += 1; - - // Update the number of leaves - this.numLeaves += subTreeCapacity; - - // Reset the subroot tree root now that it is obsolete - this.smallSRTroot = 0n; - - this.subTreesMerged = false; - } - - /** - * Merge all the subroots into a tree of a specified depth. - * It requires this.mergeSubRoots() to be run first. - */ - merge(depth: number): void { - assert(this.subTreesMerged); - assert(depth <= this.MAX_DEPTH); - - const srtDepth = this.calcSRTdepth(); - - assert(depth >= srtDepth); - - if (depth === srtDepth) { - this.mainRoots[depth] = this.smallSRTroot; - } else { - let root = this.smallSRTroot; - - // Calculate the main root - for (let i = srtDepth; i < depth; i += 1) { - const inputs: bigint[] = [root]; - const z = this.zeros[i]; - - for (let j = 1; j < this.hashLength; j += 1) { - inputs.push(z); - } - - root = this.hashFunc(inputs); - } - - this.mainRoots[depth] = root; - } - } - - /** - * Merge all the subroots into a tree of a specified depth. - * Uses an IncrementalQuinTree instead of the two-step method that - * AccQueue.sol uses. - */ - mergeDirect(depth: number): void { - // There must be subtrees to merge - assert(this.numLeaves > 0); - - const srtDepth = this.calcSRTdepth(); - - // The desired tree must be deep enough - assert(depth >= srtDepth); - - if (depth === this.subDepth) { - // If there is only 1 subtree, and the desired depth is the subtree - // depth, the subroot is the result - assert(this.numLeaves === this.hashLength ** this.subDepth); - const [subRoot] = this.subRoots; - this.mainRoots[depth] = subRoot; - this.subTreesMerged = true; - return; - } - - // The desired main tree must be deep enough to fit all leaves - assert(BigInt(depth ** this.hashLength) >= this.numLeaves); - - // Fill any empty leaves in the last subtree with zeros - if (this.numLeaves % this.hashLength ** this.subDepth > 0) { - this.fill(); - } - - const tree = new IncrementalQuinTree( - depth - this.subDepth, - this.zeros[this.subDepth], - this.hashLength, - this.hashFunc, - ); - - this.subRoots.forEach((subRoot) => { - tree.insert(subRoot); - }); - - this.mainRoots[depth] = tree.root; - } - - /** - * Merge all subroots into the smallest possible Merkle tree which fits - * them. e.g. if there are 5 subroots and hashLength == 2, the tree depth - * is 3 since 2 ** 3 = 8 which is the next power of 2. - * @param numSrQueueOps - The number of subroots to queue into the SRT - */ - mergeSubRoots(numSrQueueOps = 0): void { - // This function can only be called once unless a new subtree is created - assert(!this.subTreesMerged); - - // There must be subtrees to merge - assert(this.numLeaves > 0); - - // Fill any empty leaves in the last subtree with zeros - if (this.numLeaves % this.hashLength ** this.subDepth !== 0) { - this.fill(); - } - - // If there is only 1 subtree, use its root - if (this.currentSubtreeIndex === 1) { - this.smallSRTroot = this.getSubRoot(0); - this.subTreesMerged = true; - return; - } - - // Compute the depth and maximum capacity of the smallMainTreeRoot - const depth = calcDepthFromNumLeaves(this.hashLength, this.currentSubtreeIndex); - - let numQueueOps = 0; - - for (let i = this.nextSRindexToQueue; i < this.currentSubtreeIndex; i += 1) { - // Stop if the limit has been reached - if (numSrQueueOps !== 0 && numQueueOps === numSrQueueOps) { - return; - } - - // Queue the next subroot - const subRoot = this.getSubRoot(this.nextSRindexToQueue); - this.queueSubRoot(subRoot, 0, depth); - - // Increment the next subroot counter - this.nextSRindexToQueue += 1; - numQueueOps += 1; - } - - // Queue zeros to get the SRT. `m` is the number of leaves in the - // main tree, which already has `this.currentSubtreeIndex` leaves - const m = this.hashLength ** depth; - if (this.nextSRindexToQueue === this.currentSubtreeIndex) { - for (let i = this.currentSubtreeIndex; i < m; i += 1) { - const z = this.zeros[this.subDepth]; - this.queueSubRoot(z, 0, depth); - } - } - - // Store the root - const subRoot = this.subRootQueue.levels.get(depth)?.get(0) ?? 0n; - this.smallSRTroot = subRoot; - this.subTreesMerged = true; - } - - /** - * Queues the leaf (a subroot) into queuedSRTlevels - * @param leaf - The leaf to insert - * @param level - The level of the subtree - * @param maxDepth - The maximum depth of the tree - */ - private queueSubRoot(leaf: bigint, level: number, maxDepth: number) { - if (level > maxDepth) { - return; - } - - const n = this.subRootQueue.indices[level]; - - if (n !== this.hashLength - 1) { - // Just store the leaf - this.subRootQueue.levels.get(level)?.set(n, leaf); - this.subRootQueue.indices[level] += 1; - } else { - // Hash the elements in this level and queue it in the next level - const inputs: bigint[] = []; - for (let i = 0; i < this.hashLength - 1; i += 1) { - inputs.push(this.subRootQueue.levels.get(level)?.get(i) ?? 0n); - } - inputs.push(leaf); - const hashed = this.hashFunc(inputs); - - // Recurse - this.subRootQueue.indices[level] = 0; - this.queueSubRoot(hashed, level + 1, maxDepth); - } - } - - /** - * Get the root at a certain depth - * @param depth - The depth of the tree - * @returns the root - */ - getRoot(depth: number): bigint | null | undefined { - return this.mainRoots[depth]; - } - - /** - * Check if the root at a certain depth exists (subtree root) - * @param depth - the depth of the tree - * @returns whether the root exists - */ - hasRoot(depth: number): boolean { - const root = this.getRoot(depth); - return !(root === null || root === undefined); - } - - /** - * @notice Deep-copies this object - * @returns a deep copy of this object - */ - copy(): AccQueue { - const newAccQueue = new AccQueue(this.subDepth, this.hashLength, this.zeroValue); - newAccQueue.currentSubtreeIndex = JSON.parse(JSON.stringify(this.currentSubtreeIndex)) as number; - newAccQueue.numLeaves = JSON.parse(JSON.stringify(this.numLeaves)) as number; - - const arrayLeafLevels = unstringifyBigInts( - JSON.parse(JSON.stringify(stringifyBigInts(this.mapToArray(this.leafQueue.levels)))) as StringifiedBigInts, - ) as bigint[][]; - newAccQueue.leafQueue.levels = this.arrayToMap(arrayLeafLevels); - newAccQueue.leafQueue.indices = JSON.parse(JSON.stringify(this.leafQueue.indices)) as number[]; - newAccQueue.subRoots = deepCopyBigIntArray(this.subRoots); - newAccQueue.mainRoots = deepCopyBigIntArray(this.mainRoots); - newAccQueue.zeros = deepCopyBigIntArray(this.zeros); - newAccQueue.subTreesMerged = !!this.subTreesMerged; - newAccQueue.nextSRindexToQueue = Number(this.nextSRindexToQueue.toString()); - newAccQueue.smallSRTroot = BigInt(this.smallSRTroot.toString()); - newAccQueue.subRootQueue.indices = JSON.parse(JSON.stringify(this.subRootQueue.indices)) as number[]; - - const arraySubRootLevels = unstringifyBigInts( - JSON.parse(JSON.stringify(stringifyBigInts(this.mapToArray(this.subRootQueue.levels)))) as StringifiedBigInts, - ) as bigint[][]; - newAccQueue.subRootQueue.levels = this.arrayToMap(arraySubRootLevels); - - return newAccQueue; - } - - /** - * Convert map to 2D array - * - * @param map - map representation of 2D array - * @returns 2D array - */ - private mapToArray(map: Map>): bigint[][] { - return Array.from(map.values()).map((v) => Array.from(v.values())); - } - - /** - * Convert 2D array to its map representation - * - * @param array - 2D array - * @returns map representation of 2D array - */ - private arrayToMap(array: bigint[][]): Map> { - return new Map(array.map((level, i) => [i, new Map(level.map((leaf, j) => [j, leaf]))])); - } - - /** - * Hash an array of leaves - * @param leaves - The leaves to hash - * @returns the hash value of the leaves - */ - hash(leaves: bigint[]): bigint { - assert(leaves.length === this.hashLength); - return this.hashFunc(leaves); - } -} diff --git a/packages/crypto/ts/__tests__/AccQueue.test.ts b/packages/crypto/ts/__tests__/AccQueue.test.ts deleted file mode 100644 index 0e6585279d..0000000000 --- a/packages/crypto/ts/__tests__/AccQueue.test.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { expect } from "chai"; - -import { IncrementalQuinTree, AccQueue } from ".."; - -import { testMerge, testMergeExhaustive, testMergeShortest, testMergeShortestOne } from "./utils"; - -describe("AccQueue", function test() { - this.timeout(100000); - - describe("Enqueue", () => { - describe("Binary AccQueue", () => { - const HASH_LENGTH = 2; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - it("should enqueue leaves into a subtree", () => { - const tree0 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const subtreeCapacity = HASH_LENGTH ** SUB_DEPTH; - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree0.insert(leaf); - aq.enqueue(leaf); - } - expect(aq.getSubRoot(0).toString()).to.eq(tree0.root.toString()); - }); - - it("should enqueue another subtree", () => { - const tree1 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const subtreeCapacity = HASH_LENGTH ** SUB_DEPTH; - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree1.insert(leaf); - aq.enqueue(leaf); - } - expect(aq.getSubRoot(1).toString()).to.eq(tree1.root.toString()); - }); - }); - - describe("Quinary AccQueue", () => { - const HASH_LENGTH = 5; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - it("should enqueue leaves into a subtree", () => { - const tree0 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const subtreeCapacity = HASH_LENGTH ** SUB_DEPTH; - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree0.insert(leaf); - aq.enqueue(leaf); - } - expect(aq.getSubRoot(0).toString()).to.eq(tree0.root.toString()); - - const tree1 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree1.insert(leaf); - aq.enqueue(leaf); - } - expect(aq.getSubRoot(1).toString()).to.eq(tree1.root.toString()); - }); - }); - }); - - describe("Fill", () => { - describe("Binary AccQueue", () => { - const HASH_LENGTH = 2; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - it("Filling an empty subtree should create the correct subroot", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - aq.fill(); - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - }); - - it("should fill an incomplete subtree", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const leaf = BigInt(1); - aq.enqueue(leaf); - tree.insert(leaf); - - aq.fill(); - - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - }); - - it("Filling an empty subtree again should create the correct subroot", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const leaf = BigInt(1); - - // Create the first subtree with one leaf - aq.enqueue(leaf); - aq.fill(); - - // Fill the second subtree with zeros - aq.fill(); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - expect(aq.getSubRoot(1).toString()).to.eq(tree.root.toString()); - }); - - it("fill() should be correct for every number of leaves in an incomplete subtree", () => { - for (let i = 0; i < 2; i += 1) { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - for (let j = 0; j < i; j += 1) { - const leaf = BigInt(i + 1); - aq.enqueue(leaf); - tree.insert(leaf); - } - aq.fill(); - - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - } - }); - }); - - describe("Quinary AccQueue", () => { - const HASH_LENGTH = 5; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - it("Filling an empty subtree should create the correct subroot", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - aq.fill(); - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - }); - - it("should fill one incomplete subtree", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const leaf = BigInt(1); - aq.enqueue(leaf); - tree.insert(leaf); - - aq.fill(); - - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - }); - - it("Filling an empty subtree again should create the correct subroot", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const leaf = BigInt(1); - - // Create the first subtree with one leaf - aq.enqueue(leaf); - aq.fill(); - - // Fill the second subtree with zeros - aq.fill(); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - expect(aq.getSubRoot(1).toString()).to.eq(tree.root.toString()); - }); - - it("fill() should be correct for every number of leaves in an incomplete subtree", () => { - const capacity = HASH_LENGTH ** SUB_DEPTH; - for (let i = 1; i < capacity - 1; i += 1) { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - for (let j = 0; j < i; j += 1) { - const leaf = BigInt(i + 1); - aq.enqueue(leaf); - tree.insert(leaf); - } - aq.fill(); - - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - } - }); - }); - }); - - describe("Merge", () => { - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - const NUM_SUBTREES = 5; - const MAIN_DEPTH = 5; - - describe("Binary AccQueue", () => { - const HASH_LENGTH = 2; - - describe("merge()", () => { - it("should produce the correct main root", () => { - testMerge(SUB_DEPTH, HASH_LENGTH, ZERO, NUM_SUBTREES, MAIN_DEPTH); - }); - }); - - describe("mergeSubRoots()", () => { - it("should work progressively", () => { - testMergeShortest(SUB_DEPTH, HASH_LENGTH, ZERO, NUM_SUBTREES); - }); - - it("should fail if there are 0 leaves", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - expect(() => { - aq.mergeSubRoots(0); - }).to.throw(); - }); - - it("should a generate the same smallMainTreeRoot root from 1 subroot", () => { - testMergeShortestOne(SUB_DEPTH, HASH_LENGTH, ZERO); - }); - - it("Exhaustive test from 2 to 16 subtrees", () => { - const MAX = 16; - testMergeExhaustive(SUB_DEPTH, HASH_LENGTH, ZERO, MAX); - }); - }); - }); - - describe("Quinary AccQueue", () => { - const HASH_LENGTH = 5; - - describe("merge()", () => { - it("should produce the correct main root", () => { - testMerge(SUB_DEPTH, HASH_LENGTH, ZERO, NUM_SUBTREES, MAIN_DEPTH); - }); - }); - - describe("mergeSubRoots()", () => { - it("should work progressively", () => { - testMergeShortest(SUB_DEPTH, HASH_LENGTH, ZERO, NUM_SUBTREES); - }); - - it("should fail if there are 0 leaves", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - expect(() => { - aq.mergeSubRoots(0); - }).to.throw(); - }); - - it("should a generate the same smallMainTreeRoot root from 1 subroot", () => { - testMergeShortestOne(SUB_DEPTH, HASH_LENGTH, ZERO); - }); - - it("Exhaustive test from 2 to 16 subtrees", () => { - const MAX = 16; - testMergeExhaustive(SUB_DEPTH, HASH_LENGTH, ZERO, MAX); - }); - }); - }); - }); - - describe("InsertSubTree", () => { - describe("Binary AccQueue", () => { - const HASH_LENGTH = 2; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - it("should insert a subtree root into the correct position", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const subRoot = BigInt(1); - expect(aq.getSubRoots().length).to.eq(0); - aq.insertSubTree(subRoot); - expect(aq.getSubRoots()[0].toString()).to.eq(subRoot.toString()); - }); - - it("should insert a subtree root when multiple subtrees exist", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const subRoot1 = BigInt(1); - const subRoot2 = BigInt(2); - aq.insertSubTree(subRoot1); - aq.insertSubTree(subRoot2); - expect(aq.getSubRoots()[0].toString()).to.eq(subRoot1.toString()); - expect(aq.getSubRoots()[1].toString()).to.eq(subRoot2.toString()); - }); - }); - }); - - describe("Copy", () => { - const HASH_LENGTH = 2; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - it("should create a deep copy of the AccQueue", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - aq.enqueue(ZERO); - const copy = aq.copy(); - - expect(copy).to.be.an.instanceof(AccQueue); - expect(copy.getSubRoots().length).to.eq(aq.getSubRoots().length); - expect(copy.getSubRoots()).to.eql(aq.getSubRoots()); - expect(copy.getHashLength()).to.eq(aq.getHashLength()); - expect(copy.getSubDepth()).to.eql(aq.getSubDepth()); - expect(copy.getZeros()).to.eql(aq.getZeros()); - }); - - it("should not be the same object as the original", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const copy = aq.copy(); - expect(copy).not.eq(aq); - }); - - it("should not affect the original AccQueue when modifying the copy", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - aq.enqueue(ZERO); - const copy = aq.copy(); - - copy.enqueue(ZERO); - copy.insertSubTree(ZERO); - - expect(aq.getSubRoots().length).to.eq(0); - }); - }); -}); diff --git a/packages/crypto/ts/__tests__/utils.ts b/packages/crypto/ts/__tests__/utils.ts deleted file mode 100644 index 75ccfa0f75..0000000000 --- a/packages/crypto/ts/__tests__/utils.ts +++ /dev/null @@ -1,132 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import { expect } from "chai"; - -import { AccQueue, IncrementalQuinTree, calcDepthFromNumLeaves } from ".."; - -/** - * Test a full merge - * @param SUB_DEPTH - * @param HASH_LENGTH - * @param ZERO - * @param NUM_SUBTREES - * @param MAIN_DEPTH - */ -export const testMerge = ( - SUB_DEPTH: number, - HASH_LENGTH: number, - ZERO: bigint, - NUM_SUBTREES: number, - MAIN_DEPTH: number, -): void => { - // const hashFunc = HASH_LENGTH === 5 ? hash5 : hash2 - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const aq2 = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(MAIN_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - for (let i = 0; i < NUM_SUBTREES; i += 1) { - for (let j = 0; j < HASH_LENGTH ** SUB_DEPTH; j += 1) { - const leaf = BigInt(j + 1); - tree.insert(leaf); - aq.enqueue(leaf); - aq2.enqueue(leaf); - } - } - - // The main root should not exist yet - expect(aq.hasRoot(MAIN_DEPTH)).to.eq(false); - expect(aq2.hasRoot(MAIN_DEPTH)).to.eq(false); - - aq2.mergeSubRoots(0); - aq2.merge(MAIN_DEPTH); - - // For reference only - aq.mergeDirect(MAIN_DEPTH); - - // merge and mergeDirect should produce the same root - expect(aq.hasRoot(MAIN_DEPTH)).to.eq(true); - expect(aq2.hasRoot(MAIN_DEPTH)).to.eq(true); - expect(aq.getRoot(MAIN_DEPTH)!.toString()).to.eq(aq2.getRoot(MAIN_DEPTH)!.toString()); - - // merge and mergeDirect should produce the correct root - expect(aq.getRoot(MAIN_DEPTH)!.toString()).to.eq(tree.root.toString()); -}; - -/** - * Test merging the shortest subtree - * @param SUB_DEPTH - * @param HASH_LENGTH - * @param ZERO - * @param NUM_SUBTREES - */ -export const testMergeShortest = (SUB_DEPTH: number, HASH_LENGTH: number, ZERO: bigint, NUM_SUBTREES: number): void => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const aq2 = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - for (let i = 0; i < NUM_SUBTREES; i += 1) { - for (let j = 0; j < HASH_LENGTH ** SUB_DEPTH; j += 1) { - const leaf = BigInt(j + 1); - aq.enqueue(leaf); - aq2.enqueue(leaf); - } - } - - // Merge all subroots in aq - aq.mergeSubRoots(0); - - // Merge all but one subroot in aq2 - aq2.mergeSubRoots(2); - expect(aq.getSmallSRTroot().toString()).not.to.eq(aq2.getSmallSRTroot().toString()); - aq2.mergeSubRoots(2); - expect(aq.getSmallSRTroot().toString()).not.to.eq(aq2.getSmallSRTroot().toString()); - - // Merge the last subroot in aq2 - aq2.mergeSubRoots(1); - - expect(aq.getSmallSRTroot().toString()).to.eq(aq2.getSmallSRTroot().toString()); -}; - -/** - * Insert one leaf, then run mergeSubRoots - */ -export const testMergeShortestOne = (SUB_DEPTH: number, HASH_LENGTH: number, ZERO: bigint): void => { - const leaf = BigInt(123); - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const smallTree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - aq.enqueue(leaf); - smallTree.insert(leaf); - - aq.mergeSubRoots(0); - - expect(aq.getSmallSRTroot().toString()).to.eq(smallTree.root.toString()); - expect(aq.getSubRoot(0).toString()).to.eq(smallTree.root.toString()); -}; - -/** - * Create a number of subtrees, and merge them all - */ -export const testMergeExhaustive = (SUB_DEPTH: number, HASH_LENGTH: number, ZERO: bigint, MAX: number): void => { - for (let numSubtrees = 2; numSubtrees <= MAX; numSubtrees += 1) { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - // Create numSubtrees subtrees - for (let i = 0; i < numSubtrees; i += 1) { - for (let j = 0; j < HASH_LENGTH ** SUB_DEPTH; j += 1) { - const leaf = BigInt(j + 1); - aq.enqueue(leaf); - } - } - - // Merge subroots - aq.mergeSubRoots(0); - - const depth = calcDepthFromNumLeaves(HASH_LENGTH, numSubtrees); - const smallTree = new IncrementalQuinTree(depth, aq.getZeros()[aq.getSubDepth()], HASH_LENGTH, aq.hashFunc); - - aq.getSubRoots().forEach((subRoot) => { - smallTree.insert(subRoot); - }); - - expect(aq.getSmallSRTroot().toString()).to.eq(smallTree.root.toString()); - } -}; diff --git a/packages/crypto/ts/index.ts b/packages/crypto/ts/index.ts index 7da88db0e6..e2b4b19403 100644 --- a/packages/crypto/ts/index.ts +++ b/packages/crypto/ts/index.ts @@ -1,5 +1,3 @@ -export { AccQueue } from "./AccQueue"; - export { calcDepthFromNumLeaves, genTreeCommitment, genTreeProof } from "./utils"; export { IncrementalQuinTree } from "./quinTree"; diff --git a/packages/integrationTests/ts/__tests__/data/suites.json b/packages/integrationTests/ts/__tests__/data/suites.json index c642efc58e..8dfc58ea65 100644 --- a/packages/integrationTests/ts/__tests__/data/suites.json +++ b/packages/integrationTests/ts/__tests__/data/suites.json @@ -11,7 +11,7 @@ }, { "name": "Happy path", - "description": "Full tree, 4 full batches, no bribers", + "description": "Full tree, 4 full batches, no bribers", "numVotesPerUser": 1, "numUsers": 16, "expectedTally": [16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], diff --git a/packages/integrationTests/ts/__tests__/integration.test.ts b/packages/integrationTests/ts/__tests__/integration.test.ts index 6f6e84a1da..a424a0b86b 100644 --- a/packages/integrationTests/ts/__tests__/integration.test.ts +++ b/packages/integrationTests/ts/__tests__/integration.test.ts @@ -7,7 +7,6 @@ import { deployPoll, deployVkRegistryContract, genProofs, - mergeMessages, mergeSignups, proveOnChain, publish, @@ -28,8 +27,7 @@ import path from "path"; import { INT_STATE_TREE_DEPTH, - MSG_BATCH_DEPTH, - MSG_TREE_DEPTH, + MESSAGE_BATCH_SIZE, SG_DATA, STATE_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, @@ -37,7 +35,6 @@ import { initialVoiceCredits, ivcpData, maxMessages, - messageBatchDepth, } from "./utils/constants"; import { ITestSuite } from "./utils/interfaces"; import { expectTally, genTestUserCommands, isArm } from "./utils/utils"; @@ -73,12 +70,11 @@ describe("Integration tests", function test() { await setVerifyingKeys({ stateTreeDepth: STATE_TREE_DEPTH, intStateTreeDepth: INT_STATE_TREE_DEPTH, - messageTreeDepth: MSG_TREE_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, - messageBatchDepth: MSG_BATCH_DEPTH, + messageBatchSize: MESSAGE_BATCH_SIZE, processMessagesZkeyPathQv: path.resolve( __dirname, - "../../../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "../../../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", ), tallyVotesZkeyPathQv: path.resolve( __dirname, @@ -86,7 +82,7 @@ describe("Integration tests", function test() { ), processMessagesZkeyPathNonQv: path.resolve( __dirname, - "../../../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + "../../../cli/zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey", ), tallyVotesZkeyPathNonQv: path.resolve( __dirname, @@ -105,12 +101,13 @@ describe("Integration tests", function test() { // 3. deploy maci contracts = await deploy({ stateTreeDepth: STATE_TREE_DEPTH, initialVoiceCredits, signer }); + const maxVoteOptions = 25; + // 4. create a poll await deployPoll({ pollDuration: duration, intStateTreeDepth: INT_STATE_TREE_DEPTH, - messageTreeSubDepth: MSG_BATCH_DEPTH, - messageTreeDepth: MSG_TREE_DEPTH, + messageBatchSize: MESSAGE_BATCH_SIZE, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, coordinatorPubkey: coordinatorKeypair.pubKey.serialize(), maciAddress: contracts.maciAddress, @@ -120,15 +117,17 @@ describe("Integration tests", function test() { const treeDepths: TreeDepths = { intStateTreeDepth: INT_STATE_TREE_DEPTH, - messageTreeDepth: MSG_TREE_DEPTH, - messageTreeSubDepth: MSG_BATCH_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, }; - const messageBatchSize = 5 ** messageBatchDepth; + const messageBatchSize = MESSAGE_BATCH_SIZE; pollId = maciState.deployPoll( BigInt(Date.now() + duration * 60000), +<<<<<<< HEAD:packages/integrationTests/ts/__tests__/integration.test.ts +======= + maxVoteOptions, +>>>>>>> e84f61047 (feat: anonymous poll joining milestone 1 (#1625)):integrationTests/ts/__tests__/integration.test.ts treeDepths, messageBatchSize, coordinatorKeypair, @@ -232,11 +231,6 @@ describe("Integration tests", function test() { await timeTravel({ seconds: duration, signer }); - // merge messages - await expect( - mergeMessages({ pollId, maciAddress: contracts.maciAddress, signer }), - ).to.eventually.not.be.rejectedWith(); - // merge signups await expect( mergeSignups({ pollId, maciAddress: contracts.maciAddress, signer }), @@ -249,17 +243,17 @@ describe("Integration tests", function test() { tallyZkey: path.resolve(__dirname, "../../../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey"), processZkey: path.resolve( __dirname, - "../../../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "../../../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", ), pollId, rapidsnark: `${homedir()}/rapidsnark/build/prover`, processWitgen: path.resolve( __dirname, - "../../../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_cpp/ProcessMessages_10-2-1-2_test", + "../../../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_cpp/ProcessMessages_10-20-2_test", ), processDatFile: path.resolve( __dirname, - "../../../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_cpp/ProcessMessages_10-2-1-2_test.dat", + "../../../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_cpp/ProcessMessages_10-20-2_test.dat", ), tallyWitgen: path.resolve( __dirname, @@ -273,7 +267,7 @@ describe("Integration tests", function test() { maciAddress: contracts.maciAddress, processWasm: path.resolve( __dirname, - "../../../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm", + "../../../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_js/ProcessMessages_10-20-2_test.wasm", ), tallyWasm: path.resolve( __dirname, diff --git a/packages/integrationTests/ts/__tests__/maci-keys.test.ts b/packages/integrationTests/ts/__tests__/maci-keys.test.ts index e2f4b3e9ca..3b3ce7ebb5 100644 --- a/packages/integrationTests/ts/__tests__/maci-keys.test.ts +++ b/packages/integrationTests/ts/__tests__/maci-keys.test.ts @@ -11,8 +11,7 @@ import { VOTE_OPTION_TREE_DEPTH, duration, initialVoiceCredits, - messageBatchDepth, - messageTreeDepth, + MESSAGE_BATCH_SIZE, } from "./utils/constants"; import { deployTestContracts } from "./utils/utils"; @@ -85,10 +84,9 @@ describe("integration tests private/public/keypair", () => { BigInt(duration), { intStateTreeDepth: INT_STATE_TREE_DEPTH, - messageTreeDepth, - messageTreeSubDepth: messageBatchDepth, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, }, + MESSAGE_BATCH_SIZE, coordinatorKeypair.pubKey.asContractParam(), verifier, vkRegistry, diff --git a/packages/integrationTests/ts/__tests__/utils/constants.ts b/packages/integrationTests/ts/__tests__/utils/constants.ts index 2777805f2d..2583b36f16 100644 --- a/packages/integrationTests/ts/__tests__/utils/constants.ts +++ b/packages/integrationTests/ts/__tests__/utils/constants.ts @@ -28,16 +28,12 @@ export const signUpDuration = 120; export const votingDuration = 120; export const signUpDurationInSeconds = 3600; export const votingDurationInSeconds = 3600; -export const messageBatchSize = 4; export const tallyBatchSize = 4; export const quadVoteTallyBatchSize = 4; export const voteOptionsMaxLeafIndex = 3; export const duration = 300; export const intStateTreeDepth = 1; -export const messageTreeDepth = 2; -export const messageBatchDepth = 1; export const STATE_TREE_DEPTH = 10; export const INT_STATE_TREE_DEPTH = 1; -export const MSG_TREE_DEPTH = 2; export const VOTE_OPTION_TREE_DEPTH = 2; -export const MSG_BATCH_DEPTH = 1; +export const MESSAGE_BATCH_SIZE = 20;