From 5566cfece1ed989ececdaad07edb2fc3a0b4741a 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 01/14] 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 5f7b9e0b58..f382bd8891 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; From 47d69371113f2b0f07cc9f60208e0601a4f9a065 Mon Sep 17 00:00:00 2001 From: Cvitak Boris <57068961+djanluka@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:50:55 +0200 Subject: [PATCH 02/14] feat: anonymous poll joining milestone 2 and 3 (#1750) 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 * fix(merge): tmp-anon-poll-joining merge * fix(merge): tmp-anon-poll-joining merge * test(ceremonyparams): add poll joining in the test * test(processmessages): add poll joining for the test without key-change test * test(polljoining): add poll joining in the test * test(tallyvotes): add poll joining in the test * test(core): add joinPoll function in tests * style(typo): inclusion proof * style(todo): remove finished todo * style(merge): after merge style * style(return): inline return * style(eslint): remove unnecessary eslint-disable * refactor(joiningcircuitargs): add interface IJoiningCircuitArgs * refactor(joinpoll): async read state file * style(genmacisignup): add function description * refactor(gensignuptree): add IGenSignUpTreeArgs interface * style(polljoining): remove extra inlcudes and comments * feat(pollvkkeys): init * feat(vkregistry): separate set functions (process/tally/poll) * test(pollvkkey): adjust test to setPollVkKey * refactor(vkregistry): use setVerifyingKeys in setVerifyingKeysBatch * refactor(poll): add verifier and vkRegystry in constructor * refactor(poll): put verifier and vkRegistry into extContracts * test(core e2e): fix sanity checks test for incorrect signature * refactor(test): removing only from tests * refactor(macistatetree): use LeanIMT instead of QuinTree * refactor(crypto): export hashLeanIMT from index * feat(joinpoll): use genSignUpTree instead of genMaciStateFromContract * feat(joinpoll cli): add optional parameters * test(coordinator): add pollJoiningZkeyPath in app.test * refactor(joinpoll): prettier * test(coordinator): add joinPoll * fix(poll): joiningCircuitInputs with correct siblings, indices and actualStateTreeDepth * test(integration): add joinPoll * build(coordinator): add COORDINATOR_POLL_ZKEY_NAME * refactor(mergestate): remove Maci from MergeState * test(e2e): test:e2e add joinPoll * test(e2e): test:keyChange add joinPoll * docs(complete documentation): complete documentation of the new workflow * docs(documentation): add v3 docs, revert v2 docs * style(docs): prettier * refactor(joinpoll): add generateAndVerifyProof and getStateIndexAndCreditBalance * docs(blogpost): blogpost cuvering the latest updates * docs(blogpost): kudos to our team members! * style(prettier): blog * fix(joinpoll): index value of the user in the state tree leaves * docs(blog): remove poll-joining --------- Co-authored-by: radojevicMihailo Co-authored-by: Aleksandar Veljković --- apps/subgraph/src/poll.ts | 7 +- .../subgraph/templates/subgraph.template.yaml | 6 +- apps/subgraph/tests/poll/poll.test.ts | 10 +- apps/subgraph/tests/poll/utils.ts | 6 +- packages/circuits/circom/circuits.json | 6 + .../circom/core/qv/pollJoining.circom | 81 +++ packages/circuits/package.json | 3 +- .../ts/__tests__/CeremonyParams.test.ts | 44 +- .../circuits/ts/__tests__/PollJoining.test.ts | 153 ++++++ .../ts/__tests__/ProcessMessages.test.ts | 201 +++++--- .../circuits/ts/__tests__/TallyVotes.test.ts | 59 ++- packages/circuits/ts/types.ts | 17 + packages/cli/package.json | 1 + .../ceremony-params/ceremonyParams.test.ts | 2 + packages/cli/tests/constants.ts | 21 +- packages/cli/tests/e2e/e2e.test.ts | 463 +++++++++++++++--- packages/cli/tests/e2e/keyChange.test.ts | 124 +++-- packages/cli/tests/unit/joinPoll.test.ts | 138 ++++++ packages/cli/tests/unit/poll.test.ts | 4 +- packages/cli/tests/unit/publish.test.ts | 4 +- packages/cli/tests/unit/signup.test.ts | 4 +- .../cli/ts/commands/checkVerifyingKeys.ts | 8 + packages/cli/ts/commands/extractVkToFile.ts | 9 +- packages/cli/ts/commands/genLocalState.ts | 2 +- packages/cli/ts/commands/genProofs.ts | 5 +- packages/cli/ts/commands/index.ts | 1 + packages/cli/ts/commands/mergeSignups.ts | 2 +- packages/cli/ts/commands/publish.ts | 10 +- packages/cli/ts/commands/setVerifyingKeys.ts | 47 +- packages/cli/ts/index.ts | 109 +++++ packages/cli/ts/utils/constants.ts | 2 + packages/cli/ts/utils/index.ts | 5 + packages/cli/ts/utils/interfaces.ts | 190 ++++++- packages/contracts/contracts/MACI.sol | 38 +- packages/contracts/contracts/Poll.sol | 108 +++- packages/contracts/contracts/VkRegistry.sol | 117 ++++- .../contracts/contracts/interfaces/IMACI.sol | 9 + .../contracts/contracts/interfaces/IPoll.sol | 13 +- .../contracts/interfaces/IPollFactory.sol | 2 +- .../contracts/interfaces/IVkRegistry.sol | 9 + .../contracts/contracts/trees/LeanIMT.sol | 348 +++++++++++++ packages/contracts/package.json | 2 + .../tasks/deploy/maci/09-vkRegistry.ts | 6 +- .../contracts/tasks/helpers/TreeMerger.ts | 2 +- packages/contracts/tests/MACI.test.ts | 10 - .../contracts/tests/MessageProcessor.test.ts | 2 +- packages/contracts/tests/Poll.test.ts | 112 ++++- packages/contracts/tests/PollFactory.test.ts | 15 +- packages/contracts/tests/Tally.test.ts | 62 ++- packages/contracts/tests/TallyNonQv.test.ts | 2 +- packages/contracts/tests/VkRegistry.test.ts | 39 +- packages/contracts/tests/constants.ts | 15 + packages/contracts/ts/genMaciState.ts | 41 +- packages/contracts/ts/genSignUpTree.ts | 69 +++ packages/contracts/ts/index.ts | 3 +- packages/contracts/ts/types.ts | 60 ++- packages/core/package.json | 3 +- packages/core/ts/MaciState.ts | 15 +- packages/core/ts/Poll.ts | 179 ++++++- packages/core/ts/__tests__/Poll.test.ts | 166 ++++--- packages/core/ts/__tests__/e2e.test.ts | 287 ++++++----- packages/core/ts/__tests__/utils/utils.ts | 3 + packages/core/ts/index.ts | 7 +- packages/core/ts/utils/types.ts | 32 +- packages/core/ts/utils/utils.ts | 11 + packages/crypto/ts/hashing.ts | 1 + packages/crypto/ts/index.ts | 14 +- .../ts/__tests__/integration.test.ts | 46 +- .../ts/__tests__/utils/constants.ts | 2 +- pnpm-lock.yaml | 115 ++++- 70 files changed, 3186 insertions(+), 523 deletions(-) create mode 100644 packages/circuits/circom/core/qv/pollJoining.circom create mode 100644 packages/circuits/ts/__tests__/PollJoining.test.ts create mode 100644 packages/cli/tests/unit/joinPoll.test.ts create mode 100644 packages/contracts/contracts/trees/LeanIMT.sol create mode 100644 packages/contracts/ts/genSignUpTree.ts diff --git a/apps/subgraph/src/poll.ts b/apps/subgraph/src/poll.ts index 82117110c6..d57a1dd010 100644 --- a/apps/subgraph/src/poll.ts +++ b/apps/subgraph/src/poll.ts @@ -1,14 +1,11 @@ /* eslint-disable no-underscore-dangle */ import { Poll, Vote, MACI } from "../generated/schema"; -import { - MergeMaciState as MergeMaciStateEvent, - PublishMessage as PublishMessageEvent, -} from "../generated/templates/Poll/Poll"; +import { MergeState as MergeStateEvent, PublishMessage as PublishMessageEvent } from "../generated/templates/Poll/Poll"; import { ONE_BIG_INT } from "./utils/constants"; -export function handleMergeMaciState(event: MergeMaciStateEvent): void { +export function handleMergeState(event: MergeStateEvent): void { const poll = Poll.load(event.address); if (poll) { diff --git a/apps/subgraph/templates/subgraph.template.yaml b/apps/subgraph/templates/subgraph.template.yaml index b62ff6afee..76b6ac291c 100644 --- a/apps/subgraph/templates/subgraph.template.yaml +++ b/apps/subgraph/templates/subgraph.template.yaml @@ -31,7 +31,7 @@ dataSources: eventHandlers: - event: DeployPoll(uint256,indexed uint256,indexed uint256,uint8) handler: handleDeployPoll - - event: SignUp(uint256,indexed uint256,indexed uint256,uint256,uint256) + - event: SignUp(uint256,indexed uint256,indexed uint256,uint256,uint256,uint256) handler: handleSignUp file: ./src/maci.ts templates: @@ -54,8 +54,8 @@ templates: - name: Poll file: ./node_modules/maci-contracts/build/artifacts/contracts/Poll.sol/Poll.json eventHandlers: - - event: MergeMaciState(indexed uint256,indexed uint256) - handler: handleMergeMaciState + - event: MergeState(indexed uint256,indexed uint256) + handler: handleMergeState - event: PublishMessage((uint256[10]),(uint256,uint256)) handler: handlePublishMessage file: ./src/poll.ts diff --git a/apps/subgraph/tests/poll/poll.test.ts b/apps/subgraph/tests/poll/poll.test.ts index 5caffbb228..20c1f7b9a5 100644 --- a/apps/subgraph/tests/poll/poll.test.ts +++ b/apps/subgraph/tests/poll/poll.test.ts @@ -4,13 +4,13 @@ import { test, describe, afterEach, clearStore, assert, beforeEach } from "match import { MACI, Poll } from "../../generated/schema"; import { handleDeployPoll } from "../../src/maci"; -import { handleMergeMaciState, handlePublishMessage } from "../../src/poll"; +import { handleMergeState, handlePublishMessage } from "../../src/poll"; import { DEFAULT_POLL_ADDRESS, mockPollContract } from "../common"; import { createDeployPollEvent } from "../maci/utils"; -import { createMergeMaciStateEvent, createPublishMessageEvent } from "./utils"; +import { createMergeStateEvent, createPublishMessageEvent } from "./utils"; -export { handleMergeMaciState, handlePublishMessage }; +export { handleMergeState, handlePublishMessage }; describe("Poll", () => { beforeEach(() => { @@ -27,9 +27,9 @@ describe("Poll", () => { }); test("should handle merge maci state properly", () => { - const event = createMergeMaciStateEvent(DEFAULT_POLL_ADDRESS, BigInt.fromI32(1), BigInt.fromI32(3)); + const event = createMergeStateEvent(DEFAULT_POLL_ADDRESS, BigInt.fromI32(1), BigInt.fromI32(3)); - handleMergeMaciState(event); + handleMergeState(event); const poll = Poll.load(event.address)!; const maci = MACI.load(poll.maci)!; diff --git a/apps/subgraph/tests/poll/utils.ts b/apps/subgraph/tests/poll/utils.ts index 118618c0d3..ce3ec3b136 100644 --- a/apps/subgraph/tests/poll/utils.ts +++ b/apps/subgraph/tests/poll/utils.ts @@ -2,10 +2,10 @@ 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, PublishMessage } from "../../generated/templates/Poll/Poll"; +import { MergeState, PublishMessage } from "../../generated/templates/Poll/Poll"; -export function createMergeMaciStateEvent(address: Address, stateRoot: GraphBN, numSignups: GraphBN): MergeMaciState { - const event = changetype(newMockEvent()); +export function createMergeStateEvent(address: Address, stateRoot: GraphBN, numSignups: GraphBN): MergeState { + const event = changetype(newMockEvent()); event.parameters.push(new ethereum.EventParam("_stateRoot", ethereum.Value.fromUnsignedBigInt(stateRoot))); event.parameters.push(new ethereum.EventParam("_numSignups", ethereum.Value.fromUnsignedBigInt(numSignups))); diff --git a/packages/circuits/circom/circuits.json b/packages/circuits/circom/circuits.json index 01e0d7b731..459f73cd8d 100644 --- a/packages/circuits/circom/circuits.json +++ b/packages/circuits/circom/circuits.json @@ -1,4 +1,10 @@ { + "PollJoining_10_test": { + "file": "./core/qv/pollJoining", + "template": "PollJoining", + "params": [10], + "pubs": ["inputHash"] + }, "ProcessMessages_10-20-2_test": { "file": "./core/qv/processMessages", "template": "ProcessMessages", diff --git a/packages/circuits/circom/core/qv/pollJoining.circom b/packages/circuits/circom/core/qv/pollJoining.circom new file mode 100644 index 0000000000..9de81f7951 --- /dev/null +++ b/packages/circuits/circom/core/qv/pollJoining.circom @@ -0,0 +1,81 @@ +pragma circom 2.0.0; + + +// circomlib import +include "./mux1.circom"; +// zk-kit imports +include "./safe-comparators.circom"; +// local imports +include "../../utils/hashers.circom"; +include "../../utils/privToPubKey.circom"; +include "../../trees/incrementalMerkleTree.circom"; + +template PollJoining(stateTreeDepth) { + + // Constants defining the structure and size of state. + var STATE_LEAF_LENGTH = 4; + var STATE_TREE_ARITY = 2; + + // Public key IDs. + var STATE_LEAF_PUB_X_IDX = 0; + var STATE_LEAF_PUB_Y_IDX = 1; + // Voice Credit balance id + var STATE_LEAF_VOICE_CREDIT_BALANCE_IDX = 2; + var N_BITS = 252; + + // User's private key + signal input privKey; + // Poll's private key + signal input pollPrivKey; + // Poll's public key + signal input pollPubKey[2]; + // The state leaf and related path elements. + signal input stateLeaf[STATE_LEAF_LENGTH]; + // Siblings + signal input siblings[stateTreeDepth][STATE_TREE_ARITY - 1]; + // Indices + signal input indices[stateTreeDepth]; + // User's hashed private key + signal input nullifier; + // User's credits for poll joining (might be <= oldCredits) + signal input credits; + // MACI State tree root which proves the user is signed up + signal input stateRoot; + // The actual tree depth (might be <= stateTreeDepth) Used in BinaryMerkleRoot + signal input actualStateTreeDepth; + // Public input hash (nullifier, credits, stateRoot) + signal input inputHash; + + // Check public input hash + var computedInputHash = Sha256Hasher(5)([nullifier, credits, stateRoot, pollPubKey[0], pollPubKey[1]]); + inputHash === computedInputHash; + + // User private to public key + var derivedPubKey[2] = PrivToPubKey()(privKey); + derivedPubKey[0] === stateLeaf[STATE_LEAF_PUB_X_IDX]; + derivedPubKey[1] === stateLeaf[STATE_LEAF_PUB_Y_IDX]; + + // Poll private to public key + var derivedPollPubKey[2] = PrivToPubKey()(pollPrivKey); + derivedPollPubKey[0] === pollPubKey[0]; + derivedPollPubKey[1] === pollPubKey[1]; + + // Inclusion proof + var stateLeafHash = PoseidonHasher(4)(stateLeaf); + var stateLeafQip = BinaryMerkleRoot(stateTreeDepth)( + stateLeafHash, + actualStateTreeDepth, + indices, + siblings + ); + + stateLeafQip === stateRoot; + + // Check credits + var isCreditsValid = SafeLessEqThan(N_BITS)([credits, stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX]]); + isCreditsValid === 1; + + // Check nullifier + var hashedPrivKey = PoseidonHasher(1)([privKey]); + hashedPrivKey === nullifier; +} diff --git a/packages/circuits/package.json b/packages/circuits/package.json index 79974158f7..38ddfa55f8 100644 --- a/packages/circuits/package.json +++ b/packages/circuits/package.json @@ -34,7 +34,8 @@ "test:processMessages": "pnpm run mocha-test ts/__tests__/ProcessMessages.test.ts", "test:tallyVotes": "pnpm run mocha-test ts/__tests__/TallyVotes.test.ts", "test:ceremonyParams": "pnpm run mocha-test ts/__tests__/CeremonyParams.test.ts", - "test:incrementalQuinaryTree": "pnpm run mocha-test ts/__tests__/IncrementalQuinaryTree.test.ts" + "test:incrementalQuinaryTree": "pnpm run mocha-test ts/__tests__/IncrementalQuinaryTree.test.ts", + "test:pollJoining": "pnpm run mocha-test ts/__tests__/PollJoining.test.ts" }, "dependencies": { "@zk-kit/circuits": "^0.4.0", diff --git a/packages/circuits/ts/__tests__/CeremonyParams.test.ts b/packages/circuits/ts/__tests__/CeremonyParams.test.ts index a85b942352..7d215b6702 100644 --- a/packages/circuits/ts/__tests__/CeremonyParams.test.ts +++ b/packages/circuits/ts/__tests__/CeremonyParams.test.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { type WitnessTester } from "circomkit"; import { MaciState, Poll, STATE_TREE_ARITY, VOTE_OPTION_TREE_ARITY, MESSAGE_BATCH_SIZE } from "maci-core"; -import { hash5, IncrementalQuinTree } from "maci-crypto"; +import { hash5, IncrementalQuinTree, poseidon } from "maci-crypto"; import { PrivKey, Keypair, PCommand, Message, Ballot } from "maci-domainobjs"; import { IProcessMessagesInputs, ITallyVotesInputs } from "../types"; @@ -85,9 +85,7 @@ describe("Ceremony param tests", () => { before(() => { // Sign up and publish const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), @@ -102,17 +100,26 @@ describe("Ceremony param tests", () => { // update the state poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); + // First command (valid) const command = new PCommand( stateIndex, // BigInt(1), - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, voteWeight, // vote weight BigInt(2), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -131,7 +138,7 @@ describe("Ceremony param tests", () => { BigInt(1), // nonce BigInt(pollId), ); - const signature2 = command2.sign(userKeypair.privKey); + const signature2 = command2.sign(pollPrivKey); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -148,11 +155,11 @@ describe("Ceremony param tests", () => { const ballotTree = new IncrementalQuinTree(params.stateTreeDepth, emptyBallot.hash(), STATE_TREE_ARITY, hash5); ballotTree.insert(emptyBallot.hash()); - poll.stateLeaves.forEach(() => { + poll.pollStateLeaves.forEach(() => { ballotTree.insert(emptyBallotHash); }); - const currentStateRoot = poll.stateTree?.root; + const currentStateRoot = poll.pollStateLeaves?.root; const currentBallotRoot = ballotTree.root; const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; @@ -163,7 +170,7 @@ describe("Ceremony param tests", () => { // 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 newStateRoot = poll.pollStateLeaves?.root; const newBallotRoot = poll.ballotTree?.root; expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); @@ -222,9 +229,7 @@ describe("Ceremony param tests", () => { const commands: PCommand[] = []; // Sign up and publish const userKeypair = new Keypair(); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), @@ -239,17 +244,26 @@ describe("Ceremony param tests", () => { // update the state poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); + // First command (valid) const command = new PCommand( stateIndex, - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, voteWeight, // vote weight BigInt(1), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); diff --git a/packages/circuits/ts/__tests__/PollJoining.test.ts b/packages/circuits/ts/__tests__/PollJoining.test.ts new file mode 100644 index 0000000000..677f9c7932 --- /dev/null +++ b/packages/circuits/ts/__tests__/PollJoining.test.ts @@ -0,0 +1,153 @@ +import { expect } from "chai"; +import { type WitnessTester } from "circomkit"; +import { MaciState, Poll } from "maci-core"; +import { poseidon } from "maci-crypto"; +import { Keypair, Message, PCommand } from "maci-domainobjs"; + +import { IPollJoiningInputs } from "../types"; + +import { + STATE_TREE_DEPTH, + duration, + maxValues, + messageBatchSize, + treeDepths, + voiceCreditBalance, +} from "./utils/constants"; +import { circomkitInstance } from "./utils/utils"; + +describe("Poll Joining circuit", function test() { + this.timeout(900000); + const NUM_USERS = 50; + + const coordinatorKeypair = new Keypair(); + + type PollJoiningCircuitInputs = [ + "privKey", + "pollPrivKey", + "pollPubKey", + "stateLeaf", + "siblings", + "indices", + "nullifier", + "credits", + "stateRoot", + "actualStateTreeDepth", + "inputHash", + ]; + + let circuit: WitnessTester; + + before(async () => { + circuit = await circomkitInstance.WitnessTester("pollJoining", { + file: "./core/qv/pollJoining", + template: "PollJoining", + params: [STATE_TREE_DEPTH], + }); + }); + + describe(`${NUM_USERS} users, 1 join`, () => { + const maciState = new MaciState(STATE_TREE_DEPTH); + let pollId: bigint; + let poll: Poll; + let users: Keypair[]; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + const messages: Message[] = []; + const commands: PCommand[] = []; + + before(() => { + // Sign up + users = new Array(NUM_USERS).fill(0).map(() => new Keypair()); + + users.forEach((userKeypair) => { + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); + }); + + 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)); + + // Join the poll + const { privKey } = users[0]; + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + const stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); + + // First command (valid) + const command = new PCommand( + stateIndex, + pollPubKey, + BigInt(0), // voteOptionIndex, + BigInt(9), // vote weight + BigInt(1), // nonce + BigInt(pollId), + ); + + const signature = command.sign(pollPrivKey); + + const ecdhKeypair = new Keypair(); + const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); + const message = command.encrypt(signature, sharedKey); + messages.push(message); + commands.push(command); + + poll.publishMessage(message, ecdhKeypair.pubKey); + + // Process messages + poll.processMessages(pollId); + }); + + it("should produce a proof", async () => { + const privateKey = users[0].privKey; + const stateLeafIndex = BigInt(1); + const credits = BigInt(10); + + const inputs = poll.joiningCircuitInputs({ + maciPrivKey: privateKey, + stateLeafIndex, + credits, + pollPrivKey, + pollPubKey, + }) as unknown as IPollJoiningInputs; + const witness = await circuit.calculateWitness(inputs); + await circuit.expectConstraintPass(witness); + }); + + it("should fail for fake witness", async () => { + const privateKey = users[0].privKey; + const stateLeafIndex = BigInt(1); + const credits = BigInt(10); + + const inputs = poll.joiningCircuitInputs({ + maciPrivKey: privateKey, + stateLeafIndex, + credits, + pollPrivKey, + pollPubKey, + }) as unknown as IPollJoiningInputs; + const witness = await circuit.calculateWitness(inputs); + + const fakeWitness = Array(witness.length).fill(1n) as bigint[]; + await circuit.expectConstraintFail(fakeWitness); + }); + + it("should fail for improper credits", () => { + const privateKey = users[0].privKey; + const stateLeafIndex = BigInt(1); + const credits = BigInt(105); + + expect(() => + poll.joiningCircuitInputs({ maciPrivKey: privateKey, stateLeafIndex, credits, pollPrivKey, pollPubKey }), + ).to.throw("Credits must be lower than signed up credits"); + }); + }); +}); diff --git a/packages/circuits/ts/__tests__/ProcessMessages.test.ts b/packages/circuits/ts/__tests__/ProcessMessages.test.ts index d95ad85799..89b7ef316c 100644 --- a/packages/circuits/ts/__tests__/ProcessMessages.test.ts +++ b/packages/circuits/ts/__tests__/ProcessMessages.test.ts @@ -1,12 +1,19 @@ import { expect } from "chai"; import { type WitnessTester } from "circomkit"; -import { MaciState, MESSAGE_TREE_ARITY, Poll, STATE_TREE_ARITY } from "maci-core"; -import { IncrementalQuinTree, hash2 } from "maci-crypto"; +import { MaciState, Poll, STATE_TREE_ARITY } from "maci-core"; +import { IncrementalQuinTree, hash2, poseidon } from "maci-crypto"; import { PrivKey, Keypair, PCommand, Message, Ballot, PubKey } from "maci-domainobjs"; import { IProcessMessagesInputs } from "../types"; -import { STATE_TREE_DEPTH, duration, maxValues, 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() { @@ -69,9 +76,11 @@ describe("ProcessMessage circuit", function test() { before(() => { // Sign up and publish const users = new Array(5).fill(0).map(() => new Keypair()); + const pollKeys: Keypair[] = []; users.forEach((userKeypair) => { maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); + pollKeys.push(new Keypair()); }); pollId = maciState.deployPoll( @@ -85,6 +94,17 @@ describe("ProcessMessage circuit", function test() { poll = maciState.polls.get(pollId)!; poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + for (let i = 0; i < users.length; i += 1) { + const { privKey } = users[i]; + const { pubKey: pollPubKey } = pollKeys[i]; + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp); + } + const nothing = new Message([ 8370432830353022751713833565135785980866757267633941821328460903436894336785n, 0n, @@ -108,14 +128,14 @@ describe("ProcessMessage circuit", function test() { // First command (valid) const command = new PCommand( 5n, - users[4].pubKey, + pollKeys[4].pubKey, voteOptionIndex, // voteOptionIndex, voteWeight, // vote weight BigInt(2), // nonce BigInt(pollId), ); - const signature = command.sign(users[4].privKey); + const signature = command.sign(pollKeys[4].privKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -148,9 +168,7 @@ describe("ProcessMessage circuit", function test() { before(() => { // Sign up and publish const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), @@ -163,17 +181,26 @@ describe("ProcessMessage circuit", function test() { poll = maciState.polls.get(pollId)!; poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); + // First command (valid) const command = new PCommand( stateIndex, // BigInt(1), - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, voteWeight, // vote weight BigInt(2), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -186,13 +213,13 @@ describe("ProcessMessage circuit", function test() { // Second command (valid) const command2 = new PCommand( stateIndex, - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, BigInt(1), // vote weight BigInt(1), // nonce BigInt(pollId), ); - const signature2 = command2.sign(userKeypair.privKey); + const signature2 = command2.sign(pollPrivKey); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -214,7 +241,7 @@ describe("ProcessMessage circuit", function test() { ballotTree.insert(emptyBallotHash); }); - const currentStateRoot = poll.stateTree?.root; + const currentStateRoot = poll.pollStateTree?.root; const currentBallotRoot = ballotTree.root; const inputs = poll.processMessages(pollId, false) as unknown as IProcessMessagesInputs; @@ -225,7 +252,7 @@ describe("ProcessMessage circuit", function test() { // 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 newStateRoot = poll.pollStateTree?.root; const newBallotRoot = poll.ballotTree?.root; expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); @@ -268,16 +295,25 @@ describe("ProcessMessage circuit", function test() { poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); + const command = new PCommand( - BigInt(1), - userKeypair.pubKey, + stateIndex, + pollPubKey, BigInt(0), // voteOptionIndex, BigInt(1), // vote weight BigInt(1), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -300,7 +336,7 @@ describe("ProcessMessage circuit", function test() { ballotTree.insert(emptyBallotHash); }); - const currentStateRoot = poll.stateTree?.root; + const currentStateRoot = poll.pollStateTree?.root; const currentBallotRoot = ballotTree.root; const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; @@ -310,7 +346,7 @@ describe("ProcessMessage circuit", function test() { // 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 newStateRoot = poll.pollStateTree?.root; const newBallotRoot = poll.ballotTree?.root; expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); @@ -321,7 +357,6 @@ describe("ProcessMessage circuit", function test() { describe("4) 1 user, key-change", () => { const maciState = new MaciState(STATE_TREE_DEPTH); const voteWeight = BigInt(9); - let stateIndex: number; let pollId: bigint; let poll: Poll; const messages: Message[] = []; @@ -330,9 +365,8 @@ describe("ProcessMessage circuit", function test() { before(() => { // Sign up and publish const userKeypair = new Keypair(new PrivKey(BigInt(123))); - const userKeypair2 = new Keypair(new PrivKey(BigInt(456))); - stateIndex = maciState.signUp( + maciState.signUp( userKeypair.pubKey, voiceCreditBalance, BigInt(1), // BigInt(Math.floor(Date.now() / 1000)), @@ -350,17 +384,27 @@ describe("ProcessMessage circuit", function test() { poll.updatePoll(BigInt(maciState.stateLeaves.length)); + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const stateIndex = poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp); + + const { privKey: pollPrivKey2, pubKey: pollPubKey2 } = new Keypair(); + // Vote for option 0 const command = new PCommand( BigInt(stateIndex), // BigInt(1), - userKeypair.pubKey, + pollPubKey, BigInt(0), // voteOptionIndex, voteWeight, // vote weight BigInt(1), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -373,13 +417,13 @@ describe("ProcessMessage circuit", function test() { // Vote for option 1 const command2 = new PCommand( BigInt(stateIndex), - userKeypair2.pubKey, + pollPubKey, BigInt(1), // voteOptionIndex, voteWeight, // vote weight BigInt(2), // nonce BigInt(pollId), ); - const signature2 = command2.sign(userKeypair2.privKey); + const signature2 = command2.sign(pollPrivKey); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -391,14 +435,14 @@ describe("ProcessMessage circuit", function test() { // Change key const command3 = new PCommand( BigInt(stateIndex), // BigInt(1), - userKeypair2.pubKey, + pollPubKey2, BigInt(1), // voteOptionIndex, BigInt(0), // vote weight BigInt(1), // nonce BigInt(pollId), ); - const signature3 = command3.sign(userKeypair.privKey); + const signature3 = command3.sign(pollPrivKey2); const ecdhKeypair3 = new Keypair(); const sharedKey3 = Keypair.genEcdhSharedKey(ecdhKeypair3.privKey, coordinatorKeypair.pubKey); @@ -420,7 +464,7 @@ describe("ProcessMessage circuit", function test() { ballotTree.insert(emptyBallotHash); }); - const currentStateRoot = poll.stateTree?.root; + const currentStateRoot = poll.pollStateTree?.root; const currentBallotRoot = ballotTree.root; const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; @@ -430,7 +474,8 @@ describe("ProcessMessage circuit", function test() { // 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 newStateRoot = poll.pollStateTree?.root; const newBallotRoot = poll.ballotTree?.root; expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); @@ -447,7 +492,7 @@ describe("ProcessMessage circuit", function test() { before(() => { const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); // Sign up and publish pollId = maciState.deployPoll( @@ -462,19 +507,28 @@ describe("ProcessMessage circuit", function test() { poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp); + // 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, + pollPubKey, BigInt(i), // vote option index BigInt(1), // vote weight BigInt(numMessages - i), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -506,10 +560,7 @@ describe("ProcessMessage circuit", function test() { before(() => { // Sign up and publish const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), maxValues.maxVoteOptions, @@ -521,6 +572,15 @@ describe("ProcessMessage circuit", function test() { poll = maciState.polls.get(pollId)!; poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); + const nothing = new Message([ 8370432830353022751713833565135785980866757267633941821328460903436894336785n, 0n, @@ -544,14 +604,14 @@ describe("ProcessMessage circuit", function test() { // First command (valid) const command = new PCommand( stateIndex, // BigInt(1), - userKeypair.pubKey, + pollPubKey, 1n, // voteOptionIndex, 2n, // vote weight 2n, // nonce pollId, ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -564,13 +624,13 @@ describe("ProcessMessage circuit", function test() { // Second command (valid) const command2 = new PCommand( stateIndex, - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, 9n, // vote weight 9 ** 2 = 81 1n, // nonce pollId, ); - const signature2 = command2.sign(userKeypair.privKey); + const signature2 = command2.sign(pollPrivKey); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -592,7 +652,7 @@ describe("ProcessMessage circuit", function test() { ballotTree.insert(emptyBallotHash); }); - const currentStateRoot = poll.stateTree?.root; + const currentStateRoot = poll.pollStateTree?.root; const currentBallotRoot = ballotTree.root; const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; @@ -603,7 +663,7 @@ describe("ProcessMessage circuit", function test() { // 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 newStateRoot = poll.pollStateTree?.root; const newBallotRoot = poll.ballotTree?.root; expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); @@ -623,9 +683,7 @@ describe("ProcessMessage circuit", function test() { before(() => { // Sign up and publish const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), @@ -638,6 +696,15 @@ describe("ProcessMessage circuit", function test() { poll = maciState.polls.get(pollId)!; poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); + const nothing = new Message([ 8370432830353022751713833565135785980866757267633941821328460903436894336785n, 0n, @@ -661,14 +728,14 @@ describe("ProcessMessage circuit", function test() { // First command (valid) const command = new PCommand( stateIndex, // BigInt(1), - userKeypair.pubKey, + pollPubKey, 1n, // voteOptionIndex, 2n, // vote weight 2n, // nonce pollId, ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -686,13 +753,13 @@ describe("ProcessMessage circuit", function test() { // Second command (valid) in second batch (which is first due to reverse processing) const command2 = new PCommand( stateIndex, - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, 9n, // vote weight 9 ** 2 = 81 1n, // nonce pollId, ); - const signature2 = command2.sign(userKeypair.privKey); + const signature2 = command2.sign(pollPrivKey); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -715,7 +782,7 @@ describe("ProcessMessage circuit", function test() { }); while (poll.hasUnprocessedMessages()) { - const currentStateRoot = poll.stateTree?.root; + const currentStateRoot = poll.pollStateTree?.root; const currentBallotRoot = ballotTree.root; const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; @@ -727,7 +794,7 @@ describe("ProcessMessage circuit", function test() { // 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 newStateRoot = poll.pollStateTree?.root; const newBallotRoot = poll.ballotTree?.root; expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); @@ -748,10 +815,7 @@ describe("ProcessMessage circuit", function test() { before(() => { // Sign up and publish const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), maxValues.maxVoteOptions, @@ -763,6 +827,15 @@ describe("ProcessMessage circuit", function test() { poll = maciState.polls.get(pollId)!; poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); + const nothing = new Message([ 8370432830353022751713833565135785980866757267633941821328460903436894336785n, 0n, @@ -785,14 +858,14 @@ describe("ProcessMessage circuit", function test() { const commandFinal = new PCommand( stateIndex, // BigInt(1), - userKeypair.pubKey, + pollPubKey, 1n, // voteOptionIndex, 1n, // vote weight 3n, // nonce pollId, ); - const signatureFinal = commandFinal.sign(userKeypair.privKey); + const signatureFinal = commandFinal.sign(pollPrivKey); const ecdhKeypairFinal = new Keypair(); const sharedKeyFinal = Keypair.genEcdhSharedKey(ecdhKeypairFinal.privKey, coordinatorKeypair.pubKey); @@ -805,14 +878,14 @@ describe("ProcessMessage circuit", function test() { // First command (valid) const command = new PCommand( stateIndex, // BigInt(1), - userKeypair.pubKey, + pollPubKey, 1n, // voteOptionIndex, 2n, // vote weight 2n, // nonce pollId, ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -830,13 +903,13 @@ describe("ProcessMessage circuit", function test() { // Second command (valid) in second batch (which is first due to reverse processing) const command2 = new PCommand( stateIndex, - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, 9n, // vote weight 9 ** 2 = 81 1n, // nonce pollId, ); - const signature2 = command2.sign(userKeypair.privKey); + const signature2 = command2.sign(pollPrivKey); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -859,7 +932,7 @@ describe("ProcessMessage circuit", function test() { }); while (poll.hasUnprocessedMessages()) { - const currentStateRoot = poll.stateTree?.root; + const currentStateRoot = poll.pollStateTree?.root; const currentBallotRoot = ballotTree.root; const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; @@ -871,7 +944,7 @@ describe("ProcessMessage circuit", function test() { // 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 newStateRoot = poll.pollStateTree?.root; const newBallotRoot = poll.ballotTree?.root; expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); diff --git a/packages/circuits/ts/__tests__/TallyVotes.test.ts b/packages/circuits/ts/__tests__/TallyVotes.test.ts index 1ee38363ac..7003dc56de 100644 --- a/packages/circuits/ts/__tests__/TallyVotes.test.ts +++ b/packages/circuits/ts/__tests__/TallyVotes.test.ts @@ -1,5 +1,6 @@ import { type WitnessTester } from "circomkit"; import { MaciState, Poll } from "maci-core"; +import { poseidon } from "maci-crypto"; import { Keypair, PCommand, Message } from "maci-domainobjs"; import { ITallyVotesInputs } from "../types"; @@ -72,9 +73,7 @@ describe("TallyVotes circuit", function test() { const commands: PCommand[] = []; // Sign up and publish const userKeypair = new Keypair(); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), @@ -85,19 +84,28 @@ describe("TallyVotes circuit", function test() { ); poll = maciState.polls.get(pollId)!; - poll.updatePoll(stateIndex); + poll.updatePoll(BigInt(maciState.stateLeaves.length)); + + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); // First command (valid) const command = new PCommand( stateIndex, - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, voteWeight, // vote weight BigInt(1), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -146,9 +154,7 @@ describe("TallyVotes circuit", function test() { const commands: PCommand[] = []; // Sign up and publish const userKeypair = new Keypair(); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), @@ -159,19 +165,28 @@ describe("TallyVotes circuit", function test() { ); poll = maciState.polls.get(pollId)!; - poll.updatePoll(stateIndex); + poll.updatePoll(BigInt(maciState.stateLeaves.length)); + + // Join the poll + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + stateIndex = BigInt(poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp)); // First command (valid) const command = new PCommand( stateIndex, - userKeypair.pubKey, + pollPubKey, voteOptionIndex, // voteOptionIndex, voteWeight, // vote weight BigInt(1), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -214,12 +229,17 @@ describe("TallyVotes circuit", function test() { it("should produce the correct state root and ballot root", async () => { const maciState = new MaciState(STATE_TREE_DEPTH); const userKeypairs: Keypair[] = []; + const pollKeypairs: Keypair[] = []; + + // Sign up for (let i = 0; i < x; i += 1) { const k = new Keypair(); userKeypairs.push(k); + pollKeypairs.push(new Keypair()); maciState.signUp(k.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000) + duration)); } + // Deploy poll const pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), maxValues.maxVoteOptions, @@ -231,18 +251,29 @@ describe("TallyVotes circuit", function test() { const poll = maciState.polls.get(pollId)!; poll.updatePoll(BigInt(maciState.stateLeaves.length)); + // Join the poll + for (let i = 0; i < x; i += 1) { + const { privKey } = userKeypairs[i]; + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(Math.floor(Date.now() / 1000)); + + poll.joinPoll(nullifier, pollKeypairs[i].pubKey, voiceCreditBalance, timestamp); + } + + // Commands const numMessages = messageBatchSize * NUM_BATCHES; for (let i = 0; i < numMessages; i += 1) { const command = new PCommand( BigInt(i), - userKeypairs[i].pubKey, + pollKeypairs[i].pubKey, BigInt(i), // vote option index BigInt(1), // vote weight BigInt(1), // nonce BigInt(pollId), ); - const signature = command.sign(userKeypairs[i].privKey); + const signature = command.sign(pollKeypairs[i].privKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); diff --git a/packages/circuits/ts/types.ts b/packages/circuits/ts/types.ts index de265390eb..87f2fb24f0 100644 --- a/packages/circuits/ts/types.ts +++ b/packages/circuits/ts/types.ts @@ -40,6 +40,23 @@ export interface IGenProofOptions { silent?: boolean; } +/** + * Inputs for circuit PollJoining + */ +export interface IPollJoiningInputs { + privKey: bigint; + pollPrivKey: bigint; + pollPubKey: bigint[][]; + stateLeaf: bigint[]; + siblings: bigint[][]; + indices: bigint[]; + nullifier: bigint; + credits: bigint; + stateRoot: bigint; + actualStateTreeDepth: bigint; + inputHash: bigint; +} + /** * Inputs for circuit ProcessMessages */ diff --git a/packages/cli/package.json b/packages/cli/package.json index c7bd838da5..7df295c8d4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,6 +35,7 @@ "test:airdrop": "nyc ts-mocha --exit tests/unit/airdrop.test.ts", "test:genPubKey": "ts-mocha --exit tests/unit/genPubKey.test.ts", "test:genKeypair": "ts-mocha --exit tests/unit/genKeyPair.test.ts", + "test:joinPoll": "ts-mocha --exit tests/unit/joinPoll.test.ts", "test:timeTravel": "ts-mocha --exit tests/unit/timeTravel.test.ts", "test:fundWallet": "ts-mocha --exit tests/unit/fundWallet.test.ts", "test:signup": "ts-mocha --exit tests/unit/signup.test.ts", diff --git a/packages/cli/tests/ceremony-params/ceremonyParams.test.ts b/packages/cli/tests/ceremony-params/ceremonyParams.test.ts index c5398cdd87..5de069dc31 100644 --- a/packages/cli/tests/ceremony-params/ceremonyParams.test.ts +++ b/packages/cli/tests/ceremony-params/ceremonyParams.test.ts @@ -43,6 +43,7 @@ import { ceremonyTallyVotesNonQvZkeyPath, ceremonyProcessMessagesNonQvWasmPath, ceremonyTallyVotesNonQvWasmPath, + ceremonyPollJoiningZkeyPath, } from "../constants"; import { clean, isArm } from "../utils"; @@ -66,6 +67,7 @@ describe("Stress tests with ceremony params (6,3,2,20)", function test() { intStateTreeDepth, voteOptionTreeDepth, messageBatchSize, + pollJoiningZkeyPath: ceremonyPollJoiningZkeyPath, processMessagesZkeyPathQv: ceremonyProcessMessagesZkeyPath, tallyVotesZkeyPathQv: ceremonyTallyVotesZkeyPath, processMessagesZkeyPathNonQv: ceremonyProcessMessagesNonQvZkeyPath, diff --git a/packages/cli/tests/constants.ts b/packages/cli/tests/constants.ts index acd1a29b1e..14397f0067 100644 --- a/packages/cli/tests/constants.ts +++ b/packages/cli/tests/constants.ts @@ -22,6 +22,8 @@ export const MESSAGE_BATCH_SIZE = 20; const coordinatorKeypair = new Keypair(); export const coordinatorPubKey = coordinatorKeypair.pubKey.serialize(); export const coordinatorPrivKey = coordinatorKeypair.privKey.serialize(); + +export const pollJoiningTestZkeyPath = "./zkeys/PollJoining_10_test/PollJoining_10_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 = @@ -29,6 +31,7 @@ export const processMessageTestNonQvZkeyPath = 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 testPollJoiningWitnessPath = "./zkeys/PollJoining_10_test/PollJoining_10_test_cpp/PollJoining_10_test"; export const testProcessMessagesWitnessPath = "./zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test_cpp/ProcessMessages_10-20-2_test"; export const testProcessMessagesWitnessDatPath = @@ -37,16 +40,19 @@ 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 testPollJoiningWasmPath = "./zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm"; export const testProcessMessagesWasmPath = "./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`; -export const ceremonyProcessMessagesZkeyPath = "./zkeys/ProcessMessages_14-9-2-3/processmessages_14-9-2-3.zkey"; +export const ceremonyPollJoiningZkeyPath = "./zkeys/PollJoining_10/pollJoining_10.zkey"; +export const ceremonyProcessMessagesZkeyPath = "./zkeys/ProcessMessages_6-9-2-3/processMessages_6-9-2-3.zkey"; export const ceremonyProcessMessagesNonQvZkeyPath = - "./zkeys/ProcessMessagesNonQv_14-9-2-3/processmessagesnonqv_14-9-2-3.zkey"; -export const ceremonyTallyVotesZkeyPath = "./zkeys/TallyVotes_14-5-3/tallyvotes_14-5-3.zkey"; -export const ceremonyTallyVotesNonQvZkeyPath = "./zkeys/TallyVotesNonQv_14-5-3/tallyvotesnonqv_14-5-3.zkey"; + "./zkeys/ProcessMessagesNonQv_6-9-2-3/processMessagesNonQv_6-9-2-3.zkey"; +export const ceremonyTallyVotesZkeyPath = "./zkeys/TallyVotes_6-2-3/tallyVotes_6-2-3.zkey"; +export const ceremonyTallyVotesNonQvZkeyPath = "./zkeys/TallyVotesNonQv_6-2-3/tallyVotesNonQv_6-2-3.zkey"; +export const ceremonyPollJoiningWitnessPath = "./zkeys/PollJoining/PollJoining_10_cpp/PollJoining_10"; export const ceremonyProcessMessagesWitnessPath = "./zkeys/ProcessMessages_14-9-2-3/ProcessMessages_14-9-2-3_cpp/ProcessMessages_14-9-2-3"; export const ceremonyProcessMessagesNonQvWitnessPath = @@ -81,7 +87,9 @@ export const testProcessMessagesNonQvWasmPath = export const testTallyVotesNonQvWasmPath = "./zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm"; -export const pollDuration = 90; +export const pollDuration = 2000; +export const maxMessages = 25; +export const maxVoteOptions = 25; export const setVerifyingKeysArgs: Omit = { quiet: true, @@ -89,6 +97,7 @@ export const setVerifyingKeysArgs: Omit = { intStateTreeDepth: INT_STATE_TREE_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, messageBatchSize: MESSAGE_BATCH_SIZE, + pollJoiningZkeyPath: pollJoiningTestZkeyPath, processMessagesZkeyPathQv: processMessageTestZkeyPath, tallyVotesZkeyPathQv: tallyVotesTestZkeyPath, }; @@ -99,6 +108,7 @@ export const setVerifyingKeysNonQvArgs: Omit = { intStateTreeDepth: INT_STATE_TREE_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, messageBatchSize: MESSAGE_BATCH_SIZE, + pollJoiningZkeyPath: pollJoiningTestZkeyPath, processMessagesZkeyPathNonQv: processMessageTestNonQvZkeyPath, tallyVotesZkeyPathNonQv: tallyVotesTestNonQvZkeyPath, }; @@ -108,6 +118,7 @@ export const checkVerifyingKeysArgs: Omit = { intStateTreeDepth: INT_STATE_TREE_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, messageBatchSize: MESSAGE_BATCH_SIZE, + pollJoiningZkeyPath: pollJoiningTestZkeyPath, processMessagesZkeyPath: processMessageTestZkeyPath, tallyVotesZkeyPath: tallyVotesTestZkeyPath, }; diff --git a/packages/cli/tests/e2e/e2e.test.ts b/packages/cli/tests/e2e/e2e.test.ts index 47a91099a4..b17ffa4374 100644 --- a/packages/cli/tests/e2e/e2e.test.ts +++ b/packages/cli/tests/e2e/e2e.test.ts @@ -23,6 +23,8 @@ import { verify, isRegisteredUser, getGatekeeperTrait, + joinPoll, + isJoinedUser, } from "../../ts/commands"; import { DeployedContracts, GatekeeperTrait, GenProofsArgs } from "../../ts/utils"; import { @@ -33,10 +35,13 @@ import { proveOnChainArgs, verifyArgs, mergeSignupsArgs, + pollJoiningTestZkeyPath, processMessageTestZkeyPath, setVerifyingKeysArgs, tallyVotesTestZkeyPath, + testPollJoiningWasmPath, testProcessMessagesWasmPath, + testPollJoiningWitnessPath, testProcessMessagesWitnessDatPath, testProcessMessagesWitnessPath, testProofsDirPath, @@ -102,6 +107,7 @@ describe("e2e tests", function test() { }); const user = new Keypair(); + const pollKeys = new Keypair(); before(async () => { // deploy the smart contracts @@ -119,9 +125,27 @@ describe("e2e tests", function test() { await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }); }); + it("should join one user", async () => { + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user.privKey.serialize(), + pollPrivKey: pollKeys.privKey.serialize(), + stateIndex: 1n, + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 10n, + quiet: true, + }); + }); + it("should publish one message", async () => { await publish({ - pubkey: user.pubKey.serialize(), + pubkey: pollKeys.pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -129,7 +153,7 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: user.privKey.serialize(), + privateKey: pollKeys.privKey.serialize(), signer, }); }); @@ -153,6 +177,7 @@ describe("e2e tests", function test() { }); const user = new Keypair(); + const pollKeys = new Keypair(); before(async () => { // deploy the smart contracts @@ -165,9 +190,27 @@ describe("e2e tests", function test() { await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }); }); + it("should join one user", async () => { + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user.privKey.serialize(), + pollPrivKey: pollKeys.privKey.serialize(), + stateIndex: 1n, + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 10n, + quiet: true, + }); + }); + it("should publish one message", async () => { await publish({ - pubkey: user.pubKey.serialize(), + pubkey: pollKeys.pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -175,7 +218,7 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: user.privKey.serialize(), + privateKey: pollKeys.privKey.serialize(), signer, }); }); @@ -201,6 +244,7 @@ describe("e2e tests", function test() { }); const users = [new Keypair(), new Keypair(), new Keypair(), new Keypair()]; + const pollKeys = [new Keypair(), new Keypair(), new Keypair(), new Keypair()]; before(async () => { // deploy the smart contracts @@ -217,9 +261,31 @@ describe("e2e tests", function test() { } }); + it("should join four users", async () => { + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < users.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[i].privKey.serialize(), + pollPrivKey: pollKeys[i].privKey.serialize(), + stateIndex: BigInt(i + 1), + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + } + }); + it("should publish eight messages", async () => { await publish({ - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 2n, @@ -227,11 +293,11 @@ describe("e2e tests", function test() { newVoteWeight: 4n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); await publish({ - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 2n, @@ -239,11 +305,11 @@ describe("e2e tests", function test() { newVoteWeight: 3n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); await publish({ - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -251,11 +317,11 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); await publish({ - pubkey: users[1].pubKey.serialize(), + pubkey: pollKeys[1].pubKey.serialize(), stateIndex: 2n, voteOptionIndex: 2n, nonce: 1n, @@ -263,11 +329,11 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[1].privKey.serialize(), + privateKey: pollKeys[1].privKey.serialize(), signer, }); await publish({ - pubkey: users[2].pubKey.serialize(), + pubkey: pollKeys[2].pubKey.serialize(), stateIndex: 3n, voteOptionIndex: 2n, nonce: 1n, @@ -275,11 +341,11 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[2].privKey.serialize(), + privateKey: pollKeys[2].privKey.serialize(), signer, }); await publish({ - pubkey: users[3].pubKey.serialize(), + pubkey: pollKeys[3].pubKey.serialize(), stateIndex: 4n, voteOptionIndex: 2n, nonce: 3n, @@ -287,11 +353,11 @@ describe("e2e tests", function test() { newVoteWeight: 3n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[3].privKey.serialize(), + privateKey: pollKeys[3].privKey.serialize(), signer, }); await publish({ - pubkey: users[3].pubKey.serialize(), + pubkey: pollKeys[3].pubKey.serialize(), stateIndex: 4n, voteOptionIndex: 2n, nonce: 2n, @@ -299,11 +365,11 @@ describe("e2e tests", function test() { newVoteWeight: 2n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[3].privKey.serialize(), + privateKey: pollKeys[3].privKey.serialize(), signer, }); await publish({ - pubkey: users[3].pubKey.serialize(), + pubkey: pollKeys[3].pubKey.serialize(), stateIndex: 4n, voteOptionIndex: 1n, nonce: 1n, @@ -311,7 +377,7 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[3].privKey.serialize(), + privateKey: pollKeys[3].privKey.serialize(), signer, }); }); @@ -325,7 +391,7 @@ describe("e2e tests", function test() { }); }); - describe("5 signups, 1 message", () => { + describe("9 signups, 1 message", () => { after(async () => { await clean(); }); @@ -342,6 +408,18 @@ describe("e2e tests", function test() { new Keypair(), ]; + const pollKeys = [ + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + ]; + before(async () => { // deploy the smart contracts maciAddresses = await deploy({ ...deployArgs, signer }); @@ -357,9 +435,31 @@ describe("e2e tests", function test() { } }); + it("should join nine users", async () => { + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < users.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[i].privKey.serialize(), + pollPrivKey: pollKeys[i].privKey.serialize(), + stateIndex: BigInt(i + 1), + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + } + }); + it("should publish one message", async () => { await publish({ - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -367,7 +467,7 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); }); @@ -387,6 +487,7 @@ describe("e2e tests", function test() { }); const user = new Keypair(); + const pollKeys = new Keypair(); before(async () => { // deploy the smart contracts @@ -402,11 +503,30 @@ describe("e2e tests", function test() { } }); + it("should join user", async () => { + // eslint-disable-next-line no-await-in-loop + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user.privKey.serialize(), + pollPrivKey: pollKeys.privKey.serialize(), + stateIndex: 1n, + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + }); + it("should publish 12 messages with the same nonce", async () => { for (let i = 0; i < 12; i += 1) { // eslint-disable-next-line no-await-in-loop await publish({ - pubkey: user.pubKey.serialize(), + pubkey: pollKeys.pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -414,7 +534,7 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: user.privKey.serialize(), + privateKey: pollKeys.privKey.serialize(), signer, }); } @@ -435,6 +555,7 @@ describe("e2e tests", function test() { }); const users = Array.from({ length: 30 }, () => new Keypair()); + const pollKeys = Array.from({ length: 30 }, () => new Keypair()); before(async () => { // deploy the smart contracts @@ -451,57 +572,79 @@ describe("e2e tests", function test() { } }); + it("should join thirty users", async () => { + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < users.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[i].privKey.serialize(), + pollPrivKey: pollKeys[i].privKey.serialize(), + stateIndex: BigInt(i + 1), + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + } + }); + it("should publish 4 messages", async () => { // publish four different messages await publish({ maciAddress: maciAddresses.maciAddress, - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, pollId: 0n, newVoteWeight: 9n, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); await publish({ maciAddress: maciAddresses.maciAddress, - pubkey: users[1].pubKey.serialize(), + pubkey: pollKeys[1].pubKey.serialize(), stateIndex: 2n, voteOptionIndex: 1n, nonce: 1n, pollId: 0n, newVoteWeight: 9n, salt: genRandomSalt(), - privateKey: users[1].privKey.serialize(), + privateKey: pollKeys[1].privKey.serialize(), signer, }); await publish({ maciAddress: maciAddresses.maciAddress, - pubkey: users[2].pubKey.serialize(), + pubkey: pollKeys[2].pubKey.serialize(), stateIndex: 3n, voteOptionIndex: 2n, nonce: 1n, pollId: 0n, newVoteWeight: 9n, salt: genRandomSalt(), - privateKey: users[2].privKey.serialize(), + privateKey: pollKeys[2].privKey.serialize(), signer, }); await publish({ maciAddress: maciAddresses.maciAddress, - pubkey: users[3].pubKey.serialize(), + pubkey: pollKeys[3].pubKey.serialize(), stateIndex: 4n, voteOptionIndex: 3n, nonce: 1n, pollId: 0n, newVoteWeight: 9n, salt: genRandomSalt(), - privateKey: users[3].privKey.serialize(), + privateKey: pollKeys[3].privKey.serialize(), signer, }); }); @@ -531,6 +674,7 @@ describe("e2e tests", function test() { }); const user = new Keypair(); + const pollKeys = new Keypair(); before(async () => { // deploy the smart contracts @@ -539,6 +683,22 @@ describe("e2e tests", function test() { await deployPoll({ ...deployPollArgs, signer }); // signup await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }); + // joinPoll + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user.privKey.serialize(), + pollPrivKey: pollKeys.privKey.serialize(), + stateIndex: 1n, + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); // publish await publish({ pubkey: user.pubKey.serialize(), @@ -566,9 +726,28 @@ describe("e2e tests", function test() { await deployPoll({ ...deployPollArgs, signer }); }); + it("should join to new poll", async () => { + // joinPoll + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user.privKey.serialize(), + pollPrivKey: pollKeys.privKey.serialize(), + stateIndex: 1n, + pollId: 1n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + }); + it("should publish a new message", async () => { await publish({ - pubkey: user.pubKey.serialize(), + pubkey: pollKeys.pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -576,7 +755,7 @@ describe("e2e tests", function test() { newVoteWeight: 7n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: user.privKey.serialize(), + privateKey: pollKeys.privKey.serialize(), signer, }); }); @@ -596,6 +775,7 @@ describe("e2e tests", function test() { }); const users = Array.from({ length: 4 }, () => new Keypair()); + const pollKeys = Array.from({ length: 4 }, () => new Keypair()); before(async () => { // deploy the smart contracts @@ -604,9 +784,26 @@ describe("e2e tests", function test() { await deployPoll({ ...deployPollArgs, signer }); // signup await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: users[0].pubKey.serialize(), signer }); + // joinPoll + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[0].privKey.serialize(), + pollPrivKey: pollKeys[0].privKey.serialize(), + stateIndex: 1n, + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + // publish await publish({ - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -614,12 +811,31 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); + await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: users[1].pubKey.serialize(), signer }); + await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: users[1].pubKey.serialize(), signer }); + // joinPoll + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[1].privKey.serialize(), + pollPrivKey: pollKeys[1].privKey.serialize(), + stateIndex: 2n, + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + // time travel await timeTravel({ ...timeTravelArgs, signer }); // generate proofs @@ -641,9 +857,44 @@ describe("e2e tests", function test() { await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: users[3].pubKey.serialize(), signer }); }); + it("should join users", async () => { + // joinPoll + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[2].privKey.serialize(), + pollPrivKey: pollKeys[2].privKey.serialize(), + stateIndex: 4n, + pollId: 1n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + // joinPoll + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[3].privKey.serialize(), + pollPrivKey: pollKeys[3].privKey.serialize(), + stateIndex: 5n, + pollId: 1n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + }); + it("should publish a new message from the first poll voter", async () => { await publish({ - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -651,14 +902,14 @@ describe("e2e tests", function test() { newVoteWeight: 7n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); }); it("should publish a new message by the new poll voters", async () => { await publish({ - pubkey: users[1].pubKey.serialize(), + pubkey: pollKeys[1].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -666,7 +917,7 @@ describe("e2e tests", function test() { newVoteWeight: 7n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[1].privKey.serialize(), + privateKey: pollKeys[1].privKey.serialize(), signer, }); }); @@ -690,6 +941,15 @@ describe("e2e tests", function test() { new Keypair(), new Keypair(), ]; + const pollKeys = [ + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + new Keypair(), + ]; after(async () => { await clean(); @@ -721,9 +981,42 @@ describe("e2e tests", function test() { expect(stateIndex).to.not.eq(undefined); } + // join the first poll + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < users.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[i].privKey.serialize(), + pollPrivKey: pollKeys[i].privKey.serialize(), + stateIndex: BigInt(i + 1), + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + // eslint-disable-next-line no-await-in-loop + const { isJoined, pollStateIndex } = await isJoinedUser({ + maciAddress: maciAddresses.maciAddress, + pollId: 0n, + pollPubKey: pollKeys[i].pubKey.serialize(), + signer, + startBlock: 0, + quiet: true, + }); + + expect(isJoined).to.eq(true); + expect(pollStateIndex).to.not.eq(undefined); + } + // publish await publish({ - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -731,7 +1024,7 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); @@ -751,9 +1044,45 @@ describe("e2e tests", function test() { await deployPoll({ ...deployPollArgs, signer }); }); + it("join the second and third polls", async () => { + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let p = 1; p <= 2; p += 1) { + for (let i = 0; i < users.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: users[i].privKey.serialize(), + pollPrivKey: pollKeys[i].privKey.serialize(), + stateIndex: BigInt(i + 1), + pollId: BigInt(p), + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + // eslint-disable-next-line no-await-in-loop + const { isJoined, pollStateIndex } = await isJoinedUser({ + maciAddress: maciAddresses.maciAddress, + pollId: BigInt(p), + pollPubKey: pollKeys[i].pubKey.serialize(), + signer, + startBlock: 0, + quiet: true, + }); + + expect(isJoined).to.eq(true); + expect(pollStateIndex).to.not.eq(undefined); + } + } + }); + it("should publish messages to the second poll", async () => { await publish({ - pubkey: users[0].pubKey.serialize(), + pubkey: pollKeys[0].pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 0n, nonce: 1n, @@ -761,12 +1090,12 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[0].privKey.serialize(), + privateKey: pollKeys[0].privKey.serialize(), signer, }); await publish({ - pubkey: users[1].pubKey.serialize(), + pubkey: pollKeys[1].pubKey.serialize(), stateIndex: 2n, voteOptionIndex: 3n, nonce: 1n, @@ -774,12 +1103,12 @@ describe("e2e tests", function test() { newVoteWeight: 1n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[1].privKey.serialize(), + privateKey: pollKeys[1].privKey.serialize(), signer, }); await publish({ - pubkey: users[2].pubKey.serialize(), + pubkey: pollKeys[2].pubKey.serialize(), stateIndex: 3n, voteOptionIndex: 5n, nonce: 1n, @@ -787,14 +1116,14 @@ describe("e2e tests", function test() { newVoteWeight: 3n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[2].privKey.serialize(), + privateKey: pollKeys[2].privKey.serialize(), signer, }); }); it("should publish messages to the third poll", async () => { await publish({ - pubkey: users[3].pubKey.serialize(), + pubkey: pollKeys[3].pubKey.serialize(), stateIndex: 3n, voteOptionIndex: 5n, nonce: 1n, @@ -802,12 +1131,12 @@ describe("e2e tests", function test() { newVoteWeight: 3n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[3].privKey.serialize(), + privateKey: pollKeys[3].privKey.serialize(), signer, }); await publish({ - pubkey: users[4].pubKey.serialize(), + pubkey: pollKeys[4].pubKey.serialize(), stateIndex: 4n, voteOptionIndex: 7n, nonce: 1n, @@ -815,12 +1144,12 @@ describe("e2e tests", function test() { newVoteWeight: 2n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[4].privKey.serialize(), + privateKey: pollKeys[4].privKey.serialize(), signer, }); await publish({ - pubkey: users[5].pubKey.serialize(), + pubkey: pollKeys[5].pubKey.serialize(), stateIndex: 5n, voteOptionIndex: 5n, nonce: 1n, @@ -828,7 +1157,7 @@ describe("e2e tests", function test() { newVoteWeight: 9n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: users[5].privKey.serialize(), + privateKey: pollKeys[5].privKey.serialize(), signer, }); }); @@ -876,6 +1205,7 @@ describe("e2e tests", function test() { const stateOutPath = "./state.json"; const user = new Keypair(); + const pollKeys = new Keypair(); after(async () => { await clean(); @@ -896,9 +1226,28 @@ describe("e2e tests", function test() { await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }); }); + it("should join one user", async () => { + // joinPoll + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user.privKey.serialize(), + pollPrivKey: pollKeys.privKey.serialize(), + stateIndex: 1n, + pollId: 0n, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 1n, + quiet: true, + }); + }); + it("should publish one message", async () => { await publish({ - pubkey: user.pubKey.serialize(), + pubkey: pollKeys.pubKey.serialize(), stateIndex: 1n, voteOptionIndex: 5n, nonce: 1n, @@ -906,7 +1255,7 @@ describe("e2e tests", function test() { newVoteWeight: 3n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: user.privKey.serialize(), + privateKey: pollKeys.privKey.serialize(), signer, }); }); diff --git a/packages/cli/tests/e2e/keyChange.test.ts b/packages/cli/tests/e2e/keyChange.test.ts index 729e3dc6fd..212bcd5c6a 100644 --- a/packages/cli/tests/e2e/keyChange.test.ts +++ b/packages/cli/tests/e2e/keyChange.test.ts @@ -13,6 +13,7 @@ import { deployPoll, deployVkRegistryContract, genProofs, + joinPoll, mergeSignups, proveOnChain, publish, @@ -42,6 +43,9 @@ import { proveOnChainArgs, verifyArgs, timeTravelArgs, + pollJoiningTestZkeyPath, + testPollJoiningWasmPath, + testPollJoiningWitnessPath, } from "../constants"; import { clean, isArm } from "../utils"; @@ -52,8 +56,6 @@ describe("keyChange tests", function test() { let maciAddresses: DeployedContracts; let signer: Signer; - deployPollArgs.pollDuration = 90; - const genProofsArgs: Omit = { outputDir: testProofsDirPath, tallyFile: testTallyFilePath, @@ -86,8 +88,10 @@ describe("keyChange tests", function test() { await clean(); }); - const keypair1 = new Keypair(); - const keypair2 = new Keypair(); + const user1Keypair = new Keypair(); + const { privKey: pollPrivKey1, pubKey: pollPubKey1 } = new Keypair(); + const { pubKey: pollPubKey2 } = new Keypair(); + const initialNonce = 1n; const initialVoteOption = 0n; const initialVoteAmount = 9n; @@ -102,12 +106,29 @@ describe("keyChange tests", function test() { // deploy a poll contract await deployPoll({ ...deployPollArgs, signer }); stateIndex = BigInt( - await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }).then( - (result) => result.stateIndex, - ), + await signup({ + maciAddress: maciAddresses.maciAddress, + maciPubKey: user1Keypair.pubKey.serialize(), + signer, + }).then((result) => result.stateIndex), ); + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user1Keypair.privKey.serialize(), + pollPrivKey: pollPrivKey1.serialize(), + stateIndex, + pollId, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 99n, + quiet: true, + }); await publish({ - pubkey: keypair1.pubKey.serialize(), + pubkey: pollPubKey1.serialize(), stateIndex, voteOptionIndex: initialVoteOption, nonce: initialNonce, @@ -115,14 +136,14 @@ describe("keyChange tests", function test() { newVoteWeight: initialVoteAmount, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: keypair1.privKey.serialize(), + privateKey: pollPrivKey1.serialize(), signer, }); }); - it("should publish a message to change the user maci key and cast a new vote", async () => { + it("should publish a message to change the poll key and cast a new vote", async () => { await publish({ - pubkey: keypair2.pubKey.serialize(), + pubkey: pollPubKey2.serialize(), stateIndex, voteOptionIndex: initialVoteOption, nonce: initialNonce, @@ -130,7 +151,7 @@ describe("keyChange tests", function test() { newVoteWeight: initialVoteAmount - 1n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: keypair1.privKey.serialize(), + privateKey: pollPrivKey1.serialize(), signer, }); }); @@ -157,8 +178,10 @@ describe("keyChange tests", function test() { await clean(); }); - const keypair1 = new Keypair(); - const keypair2 = new Keypair(); + const user1Keypair = new Keypair(); + const { privKey: pollPrivKey1, pubKey: pollPubKey1 } = new Keypair(); + const { pubKey: pollPubKey2 } = new Keypair(); + const initialNonce = 1n; const initialVoteOption = 0n; const initialVoteAmount = 9n; @@ -173,12 +196,29 @@ describe("keyChange tests", function test() { // deploy a poll contract await deployPoll({ ...deployPollArgs, signer }); stateIndex = BigInt( - await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }).then( - (result) => result.stateIndex, - ), + await signup({ + maciAddress: maciAddresses.maciAddress, + maciPubKey: user1Keypair.pubKey.serialize(), + signer, + }).then((result) => result.stateIndex), ); + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user1Keypair.privKey.serialize(), + pollPrivKey: pollPrivKey1.serialize(), + stateIndex, + pollId, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 99n, + quiet: true, + }); await publish({ - pubkey: keypair1.pubKey.serialize(), + pubkey: pollPubKey1.serialize(), stateIndex, voteOptionIndex: initialVoteOption, nonce: initialNonce, @@ -186,14 +226,14 @@ describe("keyChange tests", function test() { newVoteWeight: initialVoteAmount, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: keypair1.privKey.serialize(), + privateKey: pollPrivKey1.serialize(), signer, }); }); - it("should publish a message to change the user maci key and cast a new vote", async () => { + it("should publish a message to change the poll and cast a new vote", async () => { await publish({ - pubkey: keypair2.pubKey.serialize(), + pubkey: pollPubKey2.serialize(), stateIndex, voteOptionIndex: initialVoteOption + 1n, nonce: initialNonce + 1n, @@ -201,7 +241,7 @@ describe("keyChange tests", function test() { newVoteWeight: initialVoteAmount - 1n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: keypair1.privKey.serialize(), + privateKey: pollPrivKey1.serialize(), signer, }); }); @@ -228,8 +268,10 @@ describe("keyChange tests", function test() { await clean(); }); - const keypair1 = new Keypair(); - const keypair2 = new Keypair(); + const user1Keypair = new Keypair(); + const { privKey: pollPrivKey1, pubKey: pollPubKey1 } = new Keypair(); + const { pubKey: pollPubKey2 } = new Keypair(); + const initialNonce = 1n; const initialVoteOption = 0n; const initialVoteAmount = 9n; @@ -244,12 +286,30 @@ describe("keyChange tests", function test() { // deploy a poll contract await deployPoll({ ...deployPollArgs, signer }); stateIndex = BigInt( - await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }).then( - (result) => result.stateIndex, - ), + await signup({ + maciAddress: maciAddresses.maciAddress, + maciPubKey: user1Keypair.pubKey.serialize(), + signer, + }).then((result) => result.stateIndex), ); + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: user1Keypair.privKey.serialize(), + pollPrivKey: pollPrivKey1.serialize(), + stateIndex, + pollId, + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + signer, + newVoiceCreditBalance: 99n, + quiet: true, + }); + await publish({ - pubkey: keypair1.pubKey.serialize(), + pubkey: pollPubKey1.serialize(), stateIndex, voteOptionIndex: initialVoteOption, nonce: initialNonce, @@ -257,14 +317,14 @@ describe("keyChange tests", function test() { newVoteWeight: initialVoteAmount, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: keypair1.privKey.serialize(), + privateKey: pollPrivKey1.serialize(), signer, }); }); - it("should publish a message to change the user maci key, and a new vote", async () => { + it("should publish a message to change the poll key, and a new vote", async () => { await publish({ - pubkey: keypair2.pubKey.serialize(), + pubkey: pollPubKey2.serialize(), stateIndex, voteOptionIndex: initialVoteOption + 2n, nonce: initialNonce, @@ -272,7 +332,7 @@ describe("keyChange tests", function test() { newVoteWeight: initialVoteAmount - 3n, maciAddress: maciAddresses.maciAddress, salt: genRandomSalt(), - privateKey: keypair1.privKey.serialize(), + privateKey: pollPrivKey1.serialize(), signer, }); }); diff --git a/packages/cli/tests/unit/joinPoll.test.ts b/packages/cli/tests/unit/joinPoll.test.ts new file mode 100644 index 0000000000..6e70e4cff7 --- /dev/null +++ b/packages/cli/tests/unit/joinPoll.test.ts @@ -0,0 +1,138 @@ +import { expect } from "chai"; +import { Signer } from "ethers"; +import { getDefaultSigner } from "maci-contracts"; +import { Keypair } from "maci-domainobjs"; + +import { + deploy, + DeployedContracts, + deployVkRegistryContract, + setVerifyingKeys, + joinPoll, + signup, + deployPoll, + isJoinedUser, +} from "../../ts"; +import { + deployArgs, + deployPollArgs, + setVerifyingKeysArgs, + pollJoiningTestZkeyPath, + testPollJoiningWasmPath, + testRapidsnarkPath, + testPollJoiningWitnessPath, +} from "../constants"; + +describe("joinPoll", function test() { + let signer: Signer; + let maciAddresses: DeployedContracts; + const user = new Keypair(); + const userPrivateKey = user.privKey.serialize(); + const userPublicKey = user.pubKey.serialize(); + + const { privKey: pollPrivateKey, pubKey: pollPublicKey } = new Keypair(); + const mockNewVoiceCreditBalance = 10n; + const mockStateIndex = 1n; + const mockPollId = 9000n; + + this.timeout(900000); + // before all tests we deploy the vk registry contract and set the verifying keys + before(async () => { + signer = await getDefaultSigner(); + + // we deploy the vk registry contract + await deployVkRegistryContract({ signer }); + // we set the verifying keys + await setVerifyingKeys({ ...setVerifyingKeysArgs, signer }); + // deploy the smart contracts + maciAddresses = await deploy({ ...deployArgs, signer }); + // signup the user + await signup({ + maciAddress: maciAddresses.maciAddress, + maciPubKey: userPublicKey, + signer, + }); + + await deployPoll({ ...deployPollArgs, signer }); + }); + + it("should allow to join the poll and return the user data", async () => { + const startBlock = await signer.provider?.getBlockNumber(); + + await joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: userPrivateKey, + stateIndex: 1n, + signer, + pollId: 0n, + pollPrivKey: pollPrivateKey.serialize(), + pollJoiningZkey: pollJoiningTestZkeyPath, + useWasm: true, + pollWasm: testPollJoiningWasmPath, + pollWitgen: testPollJoiningWitnessPath, + rapidsnark: testRapidsnarkPath, + newVoiceCreditBalance: mockNewVoiceCreditBalance, + quiet: true, + }); + + const registeredUserData = await isJoinedUser({ + maciAddress: maciAddresses.maciAddress, + pollId: 0n, + pollPubKey: pollPublicKey.serialize(), + signer, + startBlock: startBlock || 0, + quiet: true, + }); + + expect(registeredUserData.isJoined).to.eq(true); + expect(BigInt(registeredUserData.pollStateIndex!)).to.eq(1); + }); + + it("should throw error if poll does not exist", async () => { + await expect( + joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: userPrivateKey, + stateIndex: mockStateIndex, + signer, + pollId: mockPollId, + pollPrivKey: pollPrivateKey.serialize(), + pollJoiningZkey: pollJoiningTestZkeyPath, + newVoiceCreditBalance: mockNewVoiceCreditBalance, + quiet: true, + }), + ).eventually.rejectedWith("PollDoesNotExist(9000)"); + }); + + it("should throw error if state index is invalid", async () => { + await expect( + joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: userPrivateKey, + stateIndex: -1n, + signer, + pollId: 0n, + pollPrivKey: pollPrivateKey.serialize(), + pollJoiningZkey: pollJoiningTestZkeyPath, + newVoiceCreditBalance: mockNewVoiceCreditBalance, + quiet: true, + }), + ).eventually.rejectedWith("Invalid state index"); + }); + + it("should throw error if current poll id is invalid", async () => { + await expect( + joinPoll({ + maciAddress: maciAddresses.maciAddress, + privateKey: userPrivateKey, + stateIndex: mockStateIndex, + signer, + pollId: -1n, + pollPrivKey: pollPrivateKey.serialize(), + pollJoiningZkey: pollJoiningTestZkeyPath, + newVoiceCreditBalance: mockNewVoiceCreditBalance, + quiet: true, + }), + ).eventually.rejectedWith("Invalid poll id"); + }); +}); diff --git a/packages/cli/tests/unit/poll.test.ts b/packages/cli/tests/unit/poll.test.ts index bf5fc70d91..be0bc2e980 100644 --- a/packages/cli/tests/unit/poll.test.ts +++ b/packages/cli/tests/unit/poll.test.ts @@ -16,7 +16,9 @@ import { DeployedContracts, PollContracts } from "../../ts/utils"; import { deployPollArgs, setVerifyingKeysArgs, deployArgs } from "../constants"; import { clean } from "../utils"; -describe("poll", () => { +describe("poll", function test() { + this.timeout(900000); + let maciAddresses: DeployedContracts; let pollAddresses: PollContracts; let signer: Signer; diff --git a/packages/cli/tests/unit/publish.test.ts b/packages/cli/tests/unit/publish.test.ts index 1ba9a6601c..02a04ad376 100644 --- a/packages/cli/tests/unit/publish.test.ts +++ b/packages/cli/tests/unit/publish.test.ts @@ -17,7 +17,9 @@ import { import { DeployedContracts, IPublishBatchArgs, IPublishMessage, PollContracts } from "../../ts/utils"; import { deployPollArgs, setVerifyingKeysArgs, deployArgs } from "../constants"; -describe("publish", () => { +describe("publish", function test() { + this.timeout(900000); + let maciAddresses: DeployedContracts; let pollAddresses: PollContracts; let signer: Signer; diff --git a/packages/cli/tests/unit/signup.test.ts b/packages/cli/tests/unit/signup.test.ts index 14d4ec65a0..017a2d8b8a 100644 --- a/packages/cli/tests/unit/signup.test.ts +++ b/packages/cli/tests/unit/signup.test.ts @@ -13,7 +13,9 @@ import { } from "../../ts"; import { deployArgs, setVerifyingKeysArgs } from "../constants"; -describe("signup", () => { +describe("signup", function test() { + this.timeout(900000); + let signer: Signer; let maciAddresses: DeployedContracts; const user = new Keypair(); diff --git a/packages/cli/ts/commands/checkVerifyingKeys.ts b/packages/cli/ts/commands/checkVerifyingKeys.ts index bb42ccca8a..805dbd8a9a 100644 --- a/packages/cli/ts/commands/checkVerifyingKeys.ts +++ b/packages/cli/ts/commands/checkVerifyingKeys.ts @@ -31,6 +31,7 @@ export const checkVerifyingKeys = async ({ messageBatchSize, processMessagesZkeyPath, tallyVotesZkeyPath, + pollJoiningZkeyPath, vkRegistry, signer, useQuadraticVoting = true, @@ -68,11 +69,14 @@ export const checkVerifyingKeys = async ({ // extract the verification keys from the zkey files const processVk = VerifyingKey.fromObj(await extractVk(processMessagesZkeyPath)); const tallyVk = VerifyingKey.fromObj(await extractVk(tallyVotesZkeyPath)); + const pollVk = VerifyingKey.fromObj(await extractVk(pollJoiningZkeyPath)); try { logYellow(quiet, info("Retrieving verifying keys from the contract...")); // retrieve the verifying keys from the contract + const pollVkOnChain = await vkRegistryContractInstance.getPollVk(stateTreeDepth, voteOptionTreeDepth); + const processVkOnChain = await vkRegistryContractInstance.getProcessVk( stateTreeDepth, voteOptionTreeDepth, @@ -88,6 +92,10 @@ export const checkVerifyingKeys = async ({ ); // do the actual validation + if (!compareVks(pollVk, pollVkOnChain)) { + logError("Poll verifying keys do not match"); + } + if (!compareVks(processVk, processVkOnChain)) { logError("Process verifying keys do not match"); } diff --git a/packages/cli/ts/commands/extractVkToFile.ts b/packages/cli/ts/commands/extractVkToFile.ts index 54af523e53..64d7a67fc6 100644 --- a/packages/cli/ts/commands/extractVkToFile.ts +++ b/packages/cli/ts/commands/extractVkToFile.ts @@ -16,14 +16,19 @@ export const extractVkToFile = async ({ tallyVotesZkeyPathQv, processMessagesZkeyPathNonQv, tallyVotesZkeyPathNonQv, + pollJoiningZkeyPath, outputFilePath, }: ExtractVkToFileArgs): Promise => { - const [processVkQv, tallyVkQv, processVkNonQv, tallyVkNonQv] = await Promise.all([ + const [processVkQv, tallyVkQv, processVkNonQv, tallyVkNonQv, pollVk] = await Promise.all([ extractVk(processMessagesZkeyPathQv), extractVk(tallyVotesZkeyPathQv), extractVk(processMessagesZkeyPathNonQv), extractVk(tallyVotesZkeyPathNonQv), + extractVk(pollJoiningZkeyPath), ]); - await fs.promises.writeFile(outputFilePath, JSON.stringify({ processVkQv, tallyVkQv, processVkNonQv, tallyVkNonQv })); + await fs.promises.writeFile( + outputFilePath, + JSON.stringify({ processVkQv, tallyVkQv, processVkNonQv, tallyVkNonQv, pollVk }), + ); }; diff --git a/packages/cli/ts/commands/genLocalState.ts b/packages/cli/ts/commands/genLocalState.ts index 219f234087..fda6905183 100644 --- a/packages/cli/ts/commands/genLocalState.ts +++ b/packages/cli/ts/commands/genLocalState.ts @@ -80,7 +80,7 @@ export const genLocalState = async ({ const defaultEndBlock = await Promise.all([ pollContract - .queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock) + .queryFilter(pollContract.filters.MergeState(stateRoot, numSignups), fromBlock) .then((events) => events[events.length - 1]?.blockNumber), ]).then((blocks) => Math.max(...blocks)); diff --git a/packages/cli/ts/commands/genProofs.ts b/packages/cli/ts/commands/genProofs.ts index 400a66f43d..e302a10c63 100644 --- a/packages/cli/ts/commands/genProofs.ts +++ b/packages/cli/ts/commands/genProofs.ts @@ -1,3 +1,4 @@ +import { type BigNumberish } from "ethers"; import { extractVk, genProof, verifyProof } from "maci-circuits"; import { MACI__factory as MACIFactory, Poll__factory as PollFactory, genMaciStateFromContract } from "maci-contracts"; import { type CircuitInputs, type IJsonMaciState, MaciState } from "maci-core"; @@ -7,8 +8,6 @@ import { Keypair, PrivKey } from "maci-domainobjs"; import fs from "fs"; import path from "path"; -import type { BigNumberish } from "ethers"; - import { asHex, banner, @@ -184,7 +183,7 @@ export const genProofs = async ({ const defaultEndBlock = await Promise.all([ pollContract - .queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock) + .queryFilter(pollContract.filters.MergeState(stateRoot, numSignups), fromBlock) .then((events) => events[events.length - 1]?.blockNumber), ]).then((blocks) => Math.max(...blocks)); diff --git a/packages/cli/ts/commands/index.ts b/packages/cli/ts/commands/index.ts index 7099e6c610..d1ac5d93d1 100644 --- a/packages/cli/ts/commands/index.ts +++ b/packages/cli/ts/commands/index.ts @@ -1,6 +1,7 @@ export { deploy } from "./deploy"; export { deployPoll } from "./deployPoll"; export { getPoll } from "./poll"; +export { joinPoll, isJoinedUser } from "./joinPoll"; export { deployVkRegistryContract } from "./deployVkRegistry"; export { genKeyPair } from "./genKeyPair"; export { genMaciPubKey } from "./genPubKey"; diff --git a/packages/cli/ts/commands/mergeSignups.ts b/packages/cli/ts/commands/mergeSignups.ts index c836c186de..a73d6e5790 100644 --- a/packages/cli/ts/commands/mergeSignups.ts +++ b/packages/cli/ts/commands/mergeSignups.ts @@ -51,7 +51,7 @@ export const mergeSignups = async ({ pollId, maciAddress, signer, quiet = true } if (!(await pollContract.stateMerged())) { // go and merge the state tree logYellow(quiet, info("Calculating root and storing on Poll...")); - const tx = await pollContract.mergeMaciState(); + const tx = await pollContract.mergeState(); const receipt = await tx.wait(); if (receipt?.status !== 1) { diff --git a/packages/cli/ts/commands/publish.ts b/packages/cli/ts/commands/publish.ts index 197624ba55..5c6d205c99 100644 --- a/packages/cli/ts/commands/publish.ts +++ b/packages/cli/ts/commands/publish.ts @@ -41,7 +41,7 @@ export const publish = async ({ logError("invalid MACI public key"); } // deserialize - const userMaciPubKey = PubKey.deserialize(pubkey); + const pollPubKey = PubKey.deserialize(pubkey); if (!(await contractExists(signer.provider!, maciAddress))) { logError("MACI contract does not exist"); @@ -51,7 +51,7 @@ export const publish = async ({ logError("Invalid MACI private key"); } - const userMaciPrivKey = PrivKey.deserialize(privateKey); + const pollPrivKey = PrivKey.deserialize(privateKey); // validate args if (voteOptionIndex < 0) { @@ -104,7 +104,7 @@ export const publish = async ({ // create the command object const command: PCommand = new PCommand( stateIndex, - userMaciPubKey, + pollPubKey, voteOptionIndex, newVoteWeight, nonce, @@ -112,8 +112,8 @@ export const publish = async ({ userSalt, ); - // sign the command with the user private key - const signature = command.sign(userMaciPrivKey); + // sign the command with the poll private key + const signature = command.sign(pollPrivKey); // encrypt the command using a shared key between the user and the coordinator const message = command.encrypt(signature, Keypair.genEcdhSharedKey(encKeypair.privKey, coordinatorPubKey)); diff --git a/packages/cli/ts/commands/setVerifyingKeys.ts b/packages/cli/ts/commands/setVerifyingKeys.ts index 7decc3a622..12d06f8d0e 100644 --- a/packages/cli/ts/commands/setVerifyingKeys.ts +++ b/packages/cli/ts/commands/setVerifyingKeys.ts @@ -1,6 +1,6 @@ import { extractVk } from "maci-circuits"; import { type IVerifyingKeyStruct, VkRegistry__factory as VkRegistryFactory, EMode } from "maci-contracts"; -import { genProcessVkSig, genTallyVkSig } from "maci-core"; +import { genPollVkSig, genProcessVkSig, genTallyVkSig } from "maci-core"; import { VerifyingKey } from "maci-domainobjs"; import fs from "fs"; @@ -28,6 +28,7 @@ export const setVerifyingKeys = async ({ intStateTreeDepth, voteOptionTreeDepth, messageBatchSize, + pollJoiningZkeyPath, processMessagesZkeyPathQv, tallyVotesZkeyPathQv, processMessagesZkeyPathNonQv, @@ -49,11 +50,15 @@ export const setVerifyingKeys = async ({ } // check if zKey files exist + if (pollJoiningZkeyPath && !fs.existsSync(pollJoiningZkeyPath)) { + logError(`${pollJoiningZkeyPath} does not exist.`); + } + const isProcessMessagesZkeyPathQvExists = processMessagesZkeyPathQv ? fs.existsSync(processMessagesZkeyPathQv) : false; - if (useQuadraticVoting && processMessagesZkeyPathQv && !isProcessMessagesZkeyPathQvExists) { + if (useQuadraticVoting && !isProcessMessagesZkeyPathQvExists) { logError(`${processMessagesZkeyPathQv} does not exist.`); } @@ -78,6 +83,7 @@ export const setVerifyingKeys = async ({ } // extract the vks + const pollVk = pollJoiningZkeyPath && VerifyingKey.fromObj(await extractVk(pollJoiningZkeyPath)); const processVkQv = processMessagesZkeyPathQv && VerifyingKey.fromObj(await extractVk(processMessagesZkeyPathQv)); const tallyVkQv = tallyVotesZkeyPathQv && VerifyingKey.fromObj(await extractVk(tallyVotesZkeyPathQv)); const processVkNonQv = @@ -94,6 +100,7 @@ export const setVerifyingKeys = async ({ } checkZkeyFilepaths({ + pollJoiningZkeyPath: pollJoiningZkeyPath!, processMessagesZkeyPath: processMessagesZkeyPathQv!, tallyVotesZkeyPath: tallyVotesZkeyPathQv!, stateTreeDepth, @@ -103,7 +110,7 @@ export const setVerifyingKeys = async ({ }); checkZkeyFilepaths({ - processMessagesZkeyPath: processMessagesZkeyPathNonQv!, + pollJoiningZkeyPath: pollJoiningZkeyPath!, tallyVotesZkeyPath: tallyVotesZkeyPathNonQv!, stateTreeDepth, messageBatchSize, @@ -119,6 +126,12 @@ export const setVerifyingKeys = async ({ // connect to VkRegistry contract const vkRegistryContract = VkRegistryFactory.connect(vkRegistryAddress, signer); + // check if the poll vk was already set + const pollVkSig = genPollVkSig(stateTreeDepth, voteOptionTreeDepth); + if (await vkRegistryContract.isPollVkSet(pollVkSig)) { + logError("This poll verifying key is already set in the contract"); + } + // check if the process messages vk was already set const processVkSig = genProcessVkSig(stateTreeDepth, voteOptionTreeDepth, messageBatchSize); @@ -144,6 +157,7 @@ export const setVerifyingKeys = async ({ // actually set those values try { logYellow(quiet, info("Setting verifying keys...")); + const pollZkeys = (pollVk as VerifyingKey).asContractParam() as IVerifyingKeyStruct; const processZkeys = [processVkQv, processVkNonQv] .filter(Boolean) @@ -168,6 +182,7 @@ export const setVerifyingKeys = async ({ voteOptionTreeDepth, messageBatchSize, modes, + pollZkeys, processZkeys, tallyZkeys, ); @@ -182,6 +197,8 @@ export const setVerifyingKeys = async ({ // confirm that they were actually set correctly if (useQuadraticVoting) { + const pollVkOnChain = await vkRegistryContract.getPollVk(stateTreeDepth, voteOptionTreeDepth); + const processVkOnChain = await vkRegistryContract.getProcessVk( stateTreeDepth, voteOptionTreeDepth, @@ -196,6 +213,10 @@ export const setVerifyingKeys = async ({ EMode.QV, ); + if (!compareVks(pollVk as VerifyingKey, pollVkOnChain)) { + logError("pollVk mismatch"); + } + if (!compareVks(processVkQv as VerifyingKey, processVkOnChain)) { logError("processVk mismatch"); } @@ -204,6 +225,8 @@ export const setVerifyingKeys = async ({ logError("tallyVk mismatch"); } } else { + const pollVkOnChain = await vkRegistryContract.getPollVk(stateTreeDepth, voteOptionTreeDepth); + const processVkOnChain = await vkRegistryContract.getProcessVk( stateTreeDepth, voteOptionTreeDepth, @@ -218,6 +241,10 @@ export const setVerifyingKeys = async ({ EMode.NON_QV, ); + if (!compareVks(pollVk as VerifyingKey, pollVkOnChain)) { + logError("pollVk mismatch"); + } + if (!compareVks(processVkNonQv as VerifyingKey, processVkOnChain)) { logError("processVk mismatch"); } @@ -238,11 +265,13 @@ interface ICheckZkeyFilepathsArgs { messageBatchSize: number; voteOptionTreeDepth: number; intStateTreeDepth: number; + pollJoiningZkeyPath?: string; processMessagesZkeyPath?: string; tallyVotesZkeyPath?: string; } function checkZkeyFilepaths({ + pollJoiningZkeyPath, processMessagesZkeyPath, tallyVotesZkeyPath, stateTreeDepth, @@ -250,11 +279,20 @@ function checkZkeyFilepaths({ voteOptionTreeDepth, intStateTreeDepth, }: ICheckZkeyFilepathsArgs): void { - if (!processMessagesZkeyPath || !tallyVotesZkeyPath) { + if (!pollJoiningZkeyPath || !processMessagesZkeyPath || !tallyVotesZkeyPath) { return; } // Check the pm zkey filename against specified params + const pjMatch = pollJoiningZkeyPath.match(/.+_(\d+)/); + + if (!pjMatch) { + logError(`${pollJoiningZkeyPath} has an invalid filename`); + return; + } + + const pjStateTreeDepth = Number(pjMatch[1]); + const pmMatch = processMessagesZkeyPath.match(/.+_(\d+)-(\d+)-(\d+)/); if (!pmMatch) { @@ -278,6 +316,7 @@ function checkZkeyFilepaths({ const tvVoteOptionTreeDepth = Number(tvMatch[3]); if ( + stateTreeDepth !== pjStateTreeDepth || stateTreeDepth !== pmStateTreeDepth || messageBatchSize !== pmMsgBatchSize || voteOptionTreeDepth !== pmVoteOptionTreeDepth || diff --git a/packages/cli/ts/index.ts b/packages/cli/ts/index.ts index 388830eeb7..056a0d2e80 100644 --- a/packages/cli/ts/index.ts +++ b/packages/cli/ts/index.ts @@ -29,6 +29,8 @@ import { checkVerifyingKeys, genLocalState, extractVkToFile, + joinPoll, + isJoinedUser, } from "./commands"; import { TallyData, logError, promptSensitiveValue, readContractAddress } from "./utils"; @@ -102,6 +104,10 @@ program "-t, --tally-votes-zkey ", "the tally votes 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)", ) + .requiredOption( + "-pj, --poll-joining-zkey ", + "the poll join 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)", + ) .action(async (cmdOptions) => { try { const signer = await getSigner(); @@ -113,6 +119,7 @@ program messageBatchSize: cmdOptions.msgBatchSize, processMessagesZkeyPath: cmdOptions.processMessagesZkey, tallyVotesZkeyPath: cmdOptions.tallyVotesZkey, + pollJoiningZkeyPath: cmdOptions.pollJoiningZkey, vkRegistry: cmdOptions.vkContract, quiet: cmdOptions.quiet, useQuadraticVoting: cmdOptions.useQuadraticVoting, @@ -204,6 +211,65 @@ program program.error((error as Error).message, { exitCode: 1 }); } }); +program + .command("joinPoll") + .description("join the poll") + .requiredOption("-sk, --priv-key ", "the private key") + .option("-i, --state-index ", "the user's state index", BigInt) + .requiredOption("-esk, --poll-priv-key ", "the user ephemeral private key for the poll") + .option( + "-nv, --new-voice-credit-balance ", + "the voice credit balance of the user for the poll", + BigInt, + ) + .requiredOption("-pid, --poll-id ", "the id of the poll", BigInt) + .option("-x, --maci-address ", "the MACI contract address") + .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) + .option("-st, --state-file ", "the path to the state file containing the serialized maci state") + .option("-sb, --start-block ", "the block number to start looking for events from", parseInt) + .option("-eb, --end-block ", "the block number to end looking for events from", parseInt) + .option("-bb, --blocks-per-batch ", "the number of blocks to process per batch", parseInt) + .option("-tx, --transaction-hash ", "transaction hash of MACI contract creation") + .option("-pw, --poll-wasm ", "the path to the poll witness generation wasm binary") + .requiredOption( + "-pj, --poll-joining-zkey ", + "the poll join 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)", + ) + .option("-w, --wasm", "whether to use the wasm binaries") + .option("-r, --rapidsnark ", "the path to the rapidsnark binary") + .option("-wp, --poll-witnessgen ", "the path to the poll witness generation binary") + .action(async (cmdObj) => { + try { + const signer = await getSigner(); + const network = await signer.provider?.getNetwork(); + + const maciAddress = cmdObj.maciAddress || readContractAddress("MACI", network?.name); + const privateKey = cmdObj.privKey || (await promptSensitiveValue("Insert your MACI private key")); + + await joinPoll({ + maciAddress, + privateKey, + pollPrivKey: cmdObj.pollPrivKey, + stateIndex: cmdObj.stateIndex || null, + newVoiceCreditBalance: cmdObj.newVoiceCreditBalance || null, + stateFile: cmdObj.stateFile, + pollId: cmdObj.pollId, + signer, + startBlock: cmdObj.startBlock, + endBlock: cmdObj.endBlock, + blocksPerBatch: cmdObj.blocksPerBatch, + transactionHash: cmdObj.transactionHash, + pollJoiningZkey: cmdObj.pollJoiningZkey, + pollWasm: cmdObj.pollWasm, + quiet: cmdObj.quiet, + useWasm: cmdObj.wasm, + rapidsnark: cmdObj.rapidsnark, + pollWitgen: cmdObj.pollWitnessgen, + }); + } catch (error) { + program.error((error as Error).message, { exitCode: 1 }); + } + }); program .command("setVerifyingKeys") .description("set the verifying keys") @@ -211,6 +277,10 @@ program .requiredOption("-i, --int-state-tree-depth ", "the intermediate state tree depth", parseInt) .requiredOption("-v, --vote-option-tree-depth ", "the vote option tree depth", parseInt) .requiredOption("-b, --msg-batch-size ", "the message batch size", parseInt) + .option( + "-pj, --poll-joining-zkey ", + "the poll join 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)", + ) .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)", @@ -245,6 +315,7 @@ program intStateTreeDepth: cmdObj.intStateTreeDepth, voteOptionTreeDepth: cmdObj.voteOptionTreeDepth, messageBatchSize: cmdObj.msgBatchSize, + pollJoiningZkeyPath: cmdObj.pollJoiningZkey, processMessagesZkeyPathQv: cmdObj.processMessagesZkeyQv, tallyVotesZkeyPathQv: cmdObj.tallyVotesZkeyQv, processMessagesZkeyPathNonQv: cmdObj.processMessagesZkeyNonQv, @@ -342,6 +413,10 @@ program program .command("extractVkToFile") .description("extract vkey to json file") + .requiredOption( + "-pj, --poll-joining-zkey ", + "the poll join 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)", + ) .requiredOption( "-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)", @@ -366,6 +441,7 @@ program tallyVotesZkeyPathQv: cmdObj.tallyVotesZkeyQv, processMessagesZkeyPathNonQv: cmdObj.processMessagesZkeyNonQv, tallyVotesZkeyPathNonQv: cmdObj.tallyVotesZkeyNonQv, + pollJoiningZkeyPath: cmdObj.pollJoiningZkey, outputFilePath: cmdObj.outputFile, }); } catch (error) { @@ -423,6 +499,36 @@ program program.error((error as Error).message, { exitCode: 1 }); } }); +program + .command("isJoinedUser") + .description("Checks if user is joined to the poll with public key") + .requiredOption("-p, --pubkey ", "the MACI public key") + .option("-x, --maci-address ", "the MACI contract address") + .requiredOption("-o, --poll-id ", "the poll id", BigInt) + .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) + .option("-sb, --start-block ", "the block number to start looking for events from", parseInt) + .option("-eb, --end-block ", "the block number to end looking for events from", parseInt) + .option("-bb, --blocks-per-batch ", "the number of blocks to process per batch", parseInt) + .action(async (cmdObj) => { + try { + const signer = await getSigner(); + const network = await signer.provider?.getNetwork(); + + const maciAddress = cmdObj.maciAddress || readContractAddress("MACI", network?.name); + + await isJoinedUser({ + pollPubKey: cmdObj.pubkey, + startBlock: cmdObj.startBlock!, + maciAddress, + pollId: cmdObj.pollId, + signer, + quiet: cmdObj.quiet, + }); + } catch (error) { + program.error((error as Error).message, { exitCode: 1 }); + } + }); + program .command("getPoll") .description("Get deployed poll from MACI contract") @@ -514,6 +620,7 @@ program .option("-pd, --process-witnessdat ", "the path to the process witness dat file") .option("-wt, --tally-witnessgen ", "the path to the tally witness generation binary") .option("-td, --tally-witnessdat ", "the path to the tally witness dat file") + .requiredOption("-zpj, --poll-joining-zkey ", "the path to the poll join zkey") .requiredOption("-zp, --process-zkey ", "the path to the process zkey") .requiredOption("-zt, --tally-zkey ", "the path to the tally zkey") .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) @@ -656,6 +763,8 @@ export { isRegisteredUser, timeTravel, verify, + joinPoll, + isJoinedUser, } from "./commands"; export type { diff --git a/packages/cli/ts/utils/constants.ts b/packages/cli/ts/utils/constants.ts index 07522cc7a0..b3d36b4992 100644 --- a/packages/cli/ts/utils/constants.ts +++ b/packages/cli/ts/utils/constants.ts @@ -8,3 +8,5 @@ export const oldContractAddressStoreName = "contractAddresses.old.json"; export const contractAddressesStore = path.resolve(__dirname, "..", "..", contractAddressStoreName); // local file path where we are storing a previous deployment's contract addresses export const oldContractAddressesStore = path.resolve(__dirname, "..", "..", oldContractAddressStoreName); + +export const BLOCKS_STEP = 1000; diff --git a/packages/cli/ts/utils/index.ts b/packages/cli/ts/utils/index.ts index 7f854dbfbc..1b72094616 100644 --- a/packages/cli/ts/utils/index.ts +++ b/packages/cli/ts/utils/index.ts @@ -4,6 +4,7 @@ export { oldContractAddressStoreName, contractAddressesStore, oldContractAddressesStore, + BLOCKS_STEP, } from "./constants"; export { contractExists, currentBlockTimestamp } from "./contracts"; export { @@ -27,6 +28,10 @@ export type { TimeTravelArgs, SignupArgs, ISignupData, + IJoinPollArgs, + IJoinPollData, + IJoinedUserArgs, + IParsePollJoinEventsArgs, SetVerifyingKeysArgs, MergeSignupsArgs, ProveOnChainArgs, diff --git a/packages/cli/ts/utils/interfaces.ts b/packages/cli/ts/utils/interfaces.ts index eaf9d883b1..f1054c0b83 100644 --- a/packages/cli/ts/utils/interfaces.ts +++ b/packages/cli/ts/utils/interfaces.ts @@ -2,7 +2,7 @@ import { MACI } from "maci-contracts/typechain-types"; import { PubKey } from "maci-domainobjs"; import type { Provider, Signer } from "ethers"; -import type { SnarkProof } from "maci-contracts"; +import type { Poll, SnarkProof } from "maci-contracts"; import type { CircuitInputs } from "maci-core"; import type { IMessageContractParams } from "maci-domainobjs"; import type { Groth16Proof, PublicSignals } from "snarkjs"; @@ -174,6 +174,11 @@ export interface CheckVerifyingKeysArgs { */ messageBatchSize: number; + /** + * The path to the poll zkey + */ + pollJoiningZkeyPath: string; + /** * The path to the process messages zkey */ @@ -315,6 +320,150 @@ export interface DeployPollArgs { useQuadraticVoting?: boolean; } +/** + * Interface for the arguments to the isJoinedUser command + */ +export interface IJoinedUserArgs { + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * The id of the poll + */ + pollId: bigint; + + /** + * Poll public key for the poll + */ + pollPubKey: string; + + /** + * A signer object + */ + signer: Signer; + + /** + * The start block number + */ + startBlock: number; + + /** + * Whether to log the output + */ + quiet: boolean; +} +/** + * Interface for the arguments to the joinPoll command + */ +export interface IJoinPollArgs { + /** + * A signer object + */ + signer: Signer; + + /** + * The private key of the user + */ + privateKey: string; + + /** + * User's credit balance for voting within this poll + */ + newVoiceCreditBalance: bigint | null; + + /** + * The id of the poll + */ + pollId: bigint; + + /** + * The index of the state leaf + */ + stateIndex: bigint | null; + + /** + * Whether to log the output + */ + quiet: boolean; + + /** + * Path to the state file with MACI state + */ + stateFile?: string; + + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * The end block number + */ + endBlock?: number; + + /** + * The start block number + */ + startBlock?: number; + + /** + * The number of blocks to fetch per batch + */ + blocksPerBatch?: number; + + /** + * The transaction hash of the first transaction + */ + transactionHash?: string; + + /** + * The path to the poll zkey file + */ + pollJoiningZkey: string; + + /** + * Whether to use wasm or rapidsnark + */ + useWasm?: boolean; + + /** + * The path to the rapidsnark binary + */ + rapidsnark?: string; + + /** + * The path to the poll witnessgen binary + */ + pollWitgen?: string; + + /** + * The path to the poll wasm file + */ + pollWasm?: string; + + /** + * Poll private key for the poll + */ + pollPrivKey: string; +} + +/** + * Interface for the return data to the joinPoll command + */ +export interface IJoinPollData { + /** + * The poll state index of the joined user + */ + pollStateIndex: string; + + /** + * The join poll transaction hash + */ + hash: string; +} + /** * Interface for the arguments to the genLocalState command * Generate a local MACI state from the smart contracts events @@ -571,12 +720,12 @@ export interface ProveOnChainArgs { */ export interface PublishArgs extends IPublishMessage { /** - * The public key of the user + * The poll public key */ pubkey: string; /** - * The private key of the user + * The poll private key */ privateKey: string; @@ -715,6 +864,11 @@ export interface SetVerifyingKeysArgs { */ messageBatchSize: number; + /** + * The path to the poll zkey + */ + pollJoiningZkeyPath?: string; + /** * The path to the process messages qv zkey */ @@ -1017,6 +1171,11 @@ export interface DeployVkRegistryArgs { } export interface ExtractVkToFileArgs { + /** + * File path for poll zkey + */ + pollJoiningZkeyPath: string; + /** * File path for processMessagesQv zkey */ @@ -1043,6 +1202,31 @@ export interface ExtractVkToFileArgs { outputFilePath: string; } +/** + * Interface for the arguments to the parsePollJoinEvents function + */ +export interface IParsePollJoinEventsArgs { + /** + * The MACI contract + */ + pollContract: Poll; + + /** + * The start block + */ + startBlock: number; + + /** + * The current block + */ + currentBlock: number; + + /** + * The public key + */ + pollPublicKey: PubKey; +} + /** * Interface for the arguments to the parseSignupEvents function */ diff --git a/packages/contracts/contracts/MACI.sol b/packages/contracts/contracts/MACI.sol index ce295847d2..f05ecbd0f5 100644 --- a/packages/contracts/contracts/MACI.sol +++ b/packages/contracts/contracts/MACI.sol @@ -13,7 +13,7 @@ import { Params } from "./utilities/Params.sol"; import { Utilities } from "./utilities/Utilities.sol"; import { DomainObjs } from "./utilities/DomainObjs.sol"; import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol"; -import { InternalLazyIMT, LazyIMTData } from "./trees/LazyIMT.sol"; +import { InternalLeanIMT, LeanIMTData } from "./trees/LeanIMT.sol"; /// @title MACI - Minimum Anti-Collusion Infrastructure Version 1 /// @notice A contract which allows users to sign up, and deploy new polls @@ -56,7 +56,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @notice The state tree. Represents a mapping between each user's public key /// and their voice credit balance. - LazyIMTData public lazyIMTData; + LeanIMTData public leanIMTData; /// @notice Address of the SignUpGatekeeper, a contract which determines whether a /// user may sign up to vote @@ -66,6 +66,10 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// balance per user InitialVoiceCreditProxy public immutable initialVoiceCreditProxy; + /// @notice The array of the state tree roots for each sign up + /// For the N'th sign up, the state tree root will be stored at the index N + uint256[] public stateRootsOnSignUp; + /// @notice A struct holding the addresses of poll, mp and tally struct PollContracts { address poll; @@ -79,7 +83,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { uint256 indexed _userPubKeyX, uint256 indexed _userPubKeyY, uint256 _voiceCreditBalance, - uint256 _timestamp + uint256 _timestamp, + uint256 _stateLeaf ); event DeployPoll( uint256 _pollId, @@ -113,8 +118,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { uint256[5] memory _emptyBallotRoots ) payable { // initialize and insert the blank leaf - InternalLazyIMT._init(lazyIMTData, _stateTreeDepth); - InternalLazyIMT._insert(lazyIMTData, BLANK_STATE_LEAF_HASH); + InternalLeanIMT._insert(leanIMTData, BLANK_STATE_LEAF_HASH); + stateRootsOnSignUp.push(BLANK_STATE_LEAF_HASH); pollFactory = _pollFactory; messageProcessorFactory = _messageProcessorFactory; @@ -164,9 +169,13 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { // Create a state leaf and insert it into the tree. uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, voiceCreditBalance, timestamp)); - InternalLazyIMT._insert(lazyIMTData, stateLeaf); + InternalLeanIMT._insert(leanIMTData, stateLeaf); + + // Store the current state tree root in the array + uint256 stateRoot = InternalLeanIMT._root(leanIMTData); + stateRootsOnSignUp.push(stateRoot); - emit SignUp(lazyIMTData.numberOfLeaves - 1, _pubKey.x, _pubKey.y, voiceCreditBalance, timestamp); + emit SignUp(leanIMTData.size - 1, _pubKey.x, _pubKey.y, voiceCreditBalance, timestamp, stateLeaf); } /// @notice Deploy a new Poll contract. @@ -209,6 +218,12 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { vkRegistry: IVkRegistry(_vkRegistry) }); + ExtContracts memory extContracts = ExtContracts({ + maci: IMACI(address(this)), + verifier: IVerifier(_verifier), + vkRegistry: IVkRegistry(_vkRegistry) + }); + address p = pollFactory.deploy( _duration, maxVoteOptions, @@ -231,7 +246,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @inheritdoc IMACI function getStateTreeRoot() public view returns (uint256 root) { - root = InternalLazyIMT._root(lazyIMTData); + root = InternalLeanIMT._root(leanIMTData); } /// @notice Get the Poll details @@ -244,6 +259,11 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @inheritdoc IMACI function numSignUps() public view returns (uint256 signUps) { - signUps = lazyIMTData.numberOfLeaves; + signUps = leanIMTData.size; + } + + /// @inheritdoc IMACI + function getStateRootOnIndexedSignUp(uint256 _index) external view returns (uint256) { + return stateRootsOnSignUp[_index]; } } diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index d3ef94cede..6bf6d5377c 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -5,8 +5,11 @@ 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 { LazyIMTData, InternalLazyIMT } from "./trees/LazyIMT.sol"; import { IMACI } from "./interfaces/IMACI.sol"; +import { IPoll } from "./interfaces/IPoll.sol"; +import { IVerifier } from "./interfaces/IVerifier.sol"; +import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; import { Utilities } from "./utilities/Utilities.sol"; import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol"; @@ -78,6 +81,16 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { /// @notice flag for batch padding bool public isBatchHashesPadded; + /// @notice Poll state tree for anonymous joining + LazyIMTData public pollStateTree; + + /// @notice The hash of a blank state leaf + uint256 internal constant BLANK_STATE_LEAF_HASH = + uint256(6769006970205099520508948723718471724660867171122235270773600567925038008762); + + /// @notice Poll voting nullifier + mapping(uint256 => bool) private pollNullifier; + error VotingPeriodOver(); error VotingPeriodNotOver(); error PollAlreadyInit(); @@ -86,9 +99,19 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { error StateAlreadyMerged(); error InvalidBatchLength(); error BatchHashesAlreadyPadded(); + error UserAlreadyJoined(); + error InvalidPollProof(); event PublishMessage(Message _message, PubKey _encPubKey); - event MergeMaciState(uint256 indexed _stateRoot, uint256 indexed _numSignups); + event MergeState(uint256 indexed _stateRoot, uint256 indexed _numSignups); + event PollJoined( + uint256 indexed _pollPubKeyX, + uint256 indexed _pollPubKeyY, + uint256 _newVoiceCreditBalance, + uint256 _timestamp, + uint256 _nullifier, + uint256 _pollStateIndex + ); /// @notice Each MACI instance can have multiple Polls. /// When a Poll is deployed, its voting period starts immediately. @@ -172,6 +195,9 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { batchHashes.push(NOTHING_UP_MY_SLEEVE); updateChainHash(placeholderLeaf); + InternalLazyIMT._init(pollStateTree, extContracts.maci.stateTreeDepth()); + InternalLazyIMT._insert(pollStateTree, BLANK_STATE_LEAF_HASH); + emit PublishMessage(_message, _padKey); } @@ -242,8 +268,75 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { } } + /// @notice Join the poll for voting + /// @param _nullifier Hashed user's private key to check whether user has already voted + /// @param _pubKey Poll user's public key + /// @param _newVoiceCreditBalance User's credit balance for voting within this poll + /// @param _stateRootIndex Index of the MACI's stateRootOnSignUp when the user signed up + /// @param _proof The zk-SNARK proof + function joinPoll( + uint256 _nullifier, + PubKey memory _pubKey, + uint256 _newVoiceCreditBalance, + uint256 _stateRootIndex, + uint256[8] memory _proof + ) external { + // Whether the user has already joined + if (pollNullifier[_nullifier]) { + revert UserAlreadyJoined(); + } + + // Verify user's proof + if (!verifyPollProof(_nullifier, _newVoiceCreditBalance, _stateRootIndex, _pubKey, _proof)) { + revert InvalidPollProof(); + } + + // Store user in the pollStateTree + uint256 timestamp = block.timestamp; + uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, _newVoiceCreditBalance, timestamp)); + InternalLazyIMT._insert(pollStateTree, stateLeaf); + + // Set nullifier for user's private key + pollNullifier[_nullifier] = true; + + uint256 pollStateIndex = pollStateTree.numberOfLeaves - 1; + emit PollJoined(_pubKey.x, _pubKey.y, _newVoiceCreditBalance, timestamp, _nullifier, pollStateIndex); + } + + /// @notice Verify the proof for Poll + /// @param _nullifier Hashed user's private key to check whether user has already voted + /// @param _voiceCreditBalance User's credit balance for voting + /// @param _index Index of the MACI's stateRootOnSignUp when the user signed up + /// @param _pubKey Poll user's public key + /// @param _proof The zk-SNARK proof + /// @return isValid Whether the proof is valid + function verifyPollProof( + uint256 _nullifier, + uint256 _voiceCreditBalance, + uint256 _index, + PubKey memory _pubKey, + uint256[8] memory _proof + ) internal returns (bool isValid) { + // Get the verifying key from the VkRegistry + VerifyingKey memory vk = extContracts.vkRegistry.getPollVk( + extContracts.maci.stateTreeDepth(), + treeDepths.voteOptionTreeDepth + ); + + // Generate the circuit public input + uint256[] memory input = new uint256[](5); + input[0] = _nullifier; + input[1] = _voiceCreditBalance; + input[2] = extContracts.maci.getStateRootOnIndexedSignUp(_index); + input[3] = _pubKey.x; + input[4] = _pubKey.y; + uint256 publicInputHash = sha256Hash(input); + + isValid = extContracts.verifier.verify(_proof, vk, publicInputHash); + } + /// @inheritdoc IPoll - function mergeMaciState() public isAfterVotingDeadline { + function mergeState() public isAfterVotingDeadline { // This function can only be called once per Poll after the voting // deadline if (stateMerged) revert StateAlreadyMerged(); @@ -251,8 +344,7 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { // set merged to true so it cannot be called again stateMerged = true; - uint256 _mergedStateRoot = extContracts.maci.getStateTreeRoot(); - mergedStateRoot = _mergedStateRoot; + mergedStateRoot = InternalLazyIMT._root(pollStateTree); // Set currentSbCommitment uint256[3] memory sb; @@ -262,8 +354,8 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { currentSbCommitment = hash3(sb); - // get number of signups and cache in a var for later use - uint256 _numSignups = extContracts.maci.numSignUps(); + // get number of joined users and cache in a var for later use + uint256 _numSignups = pollStateTree.numberOfLeaves; numSignups = _numSignups; // dynamically determine the actual depth of the state tree @@ -274,7 +366,7 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { actualStateTreeDepth = depth; - emit MergeMaciState(_mergedStateRoot, _numSignups); + emit MergeState(mergedStateRoot, numSignups); } /// @inheritdoc IPoll diff --git a/packages/contracts/contracts/VkRegistry.sol b/packages/contracts/contracts/VkRegistry.sol index d69c17eb6c..e65fc0d942 100644 --- a/packages/contracts/contracts/VkRegistry.sol +++ b/packages/contracts/contracts/VkRegistry.sol @@ -17,11 +17,17 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry mapping(Mode => mapping(uint256 => VerifyingKey)) internal tallyVks; mapping(Mode => mapping(uint256 => bool)) internal tallyVkSet; + mapping(uint256 => VerifyingKey) internal pollVks; + mapping(uint256 => bool) internal pollVkSet; + + event PollVkSet(uint256 _sig); event ProcessVkSet(uint256 _sig, Mode _mode); event TallyVkSet(uint256 _sig, Mode _mode); + error PollVkAlreadySet(); error ProcessVkAlreadySet(); error TallyVkAlreadySet(); + error PollVkNotSet(); error ProcessVkNotSet(); error TallyVkNotSet(); error SubsidyVkNotSet(); @@ -31,6 +37,12 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry // solhint-disable-next-line no-empty-blocks constructor() payable {} + /// @notice Check if the poll verifying key is set + /// @param _sig The signature + /// @return isSet whether the verifying key is set + function isPollVkSet(uint256 _sig) public view returns (bool isSet) { + isSet = pollVkSet[_sig]; + } /// @notice Check if the process verifying key is set /// @param _sig The signature /// @param _mode QV or Non-QV @@ -47,6 +59,12 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry isSet = tallyVkSet[_mode][_sig]; } + /// @notice generate the signature for the poll verifying key + /// @param _stateTreeDepth The state tree depth + /// @param _voteOptionTreeDepth The vote option tree depth + function genPollVkSig(uint256 _stateTreeDepth, uint256 _voteOptionTreeDepth) public pure returns (uint256 sig) { + sig = (_stateTreeDepth << 64) + _voteOptionTreeDepth; + } /// @notice generate the signature for the process verifying key /// @param _stateTreeDepth The state tree depth /// @param _voteOptionTreeDepth The vote option tree depth @@ -79,6 +97,7 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry /// @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) + /// @param _pollVk The poll verifying key /// @param _processVks The process verifying keys (must have the same length as modes) /// @param _tallyVks The tally verifying keys (must have the same length as modes) function setVerifyingKeysBatch( @@ -87,6 +106,7 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry uint256 _voteOptionTreeDepth, uint8 _messageBatchSize, Mode[] calldata _modes, + VerifyingKey calldata _pollVk, VerifyingKey[] calldata _processVks, VerifyingKey[] calldata _tallyVks ) public onlyOwner { @@ -96,6 +116,8 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry uint256 length = _modes.length; + setPollVkKey(_stateTreeDepth, _voteOptionTreeDepth, _pollVk); + for (uint256 index = 0; index < length; ) { setVerifyingKeys( _stateTreeDepth, @@ -114,7 +136,6 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry } /// @notice Set the process and tally verifying keys for a certain combination - /// of parameters /// @param _stateTreeDepth The state tree depth /// @param _intStateTreeDepth The intermediate state tree depth /// @param _voteOptionTreeDepth The vote option tree depth @@ -130,15 +151,28 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry Mode _mode, VerifyingKey calldata _processVk, VerifyingKey calldata _tallyVk + ) public onlyOwner { + setProcessVkKey(_stateTreeDepth, _voteOptionTreeDepth, _messageBatchSize, _mode, _processVk); + setTallyVkKey(_stateTreeDepth, _intStateTreeDepth, _voteOptionTreeDepth, _mode, _tallyVk); + } + + /// @notice Set the process verifying key for a certain combination of parameters + /// @param _stateTreeDepth The state tree depth + /// @param _voteOptionTreeDepth The vote option tree depth + /// @param _messageBatchSize The message batch size + /// @param _mode QV or Non-QV + /// @param _processVk The process verifying key + function setProcessVkKey( + uint256 _stateTreeDepth, + uint256 _voteOptionTreeDepth, + uint8 _messageBatchSize, + Mode _mode, + VerifyingKey calldata _processVk ) public onlyOwner { uint256 processVkSig = genProcessVkSig(_stateTreeDepth, _voteOptionTreeDepth, _messageBatchSize); if (processVkSet[_mode][processVkSig]) revert ProcessVkAlreadySet(); - uint256 tallyVkSig = genTallyVkSig(_stateTreeDepth, _intStateTreeDepth, _voteOptionTreeDepth); - - if (tallyVkSet[_mode][tallyVkSig]) revert TallyVkAlreadySet(); - VerifyingKey storage processVk = processVks[_mode][processVkSig]; processVk.alpha1 = _processVk.alpha1; processVk.beta2 = _processVk.beta2; @@ -156,6 +190,26 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry processVkSet[_mode][processVkSig] = true; + emit ProcessVkSet(processVkSig, _mode); + } + + /// @notice Set the tally verifying key for a certain combination of parameters + /// @param _stateTreeDepth The state tree depth + /// @param _intStateTreeDepth The intermediate state tree depth + /// @param _voteOptionTreeDepth The vote option tree depth + /// @param _mode QV or Non-QV + /// @param _tallyVk The tally verifying key + function setTallyVkKey( + uint256 _stateTreeDepth, + uint256 _intStateTreeDepth, + uint256 _voteOptionTreeDepth, + Mode _mode, + VerifyingKey calldata _tallyVk + ) public onlyOwner { + uint256 tallyVkSig = genTallyVkSig(_stateTreeDepth, _intStateTreeDepth, _voteOptionTreeDepth); + + if (tallyVkSet[_mode][tallyVkSig]) revert TallyVkAlreadySet(); + VerifyingKey storage tallyVk = tallyVks[_mode][tallyVkSig]; tallyVk.alpha1 = _tallyVk.alpha1; tallyVk.beta2 = _tallyVk.beta2; @@ -174,7 +228,39 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry tallyVkSet[_mode][tallyVkSig] = true; emit TallyVkSet(tallyVkSig, _mode); - emit ProcessVkSet(processVkSig, _mode); + } + + /// @notice Set the poll verifying key for a certain combination of parameters + /// @param _stateTreeDepth The state tree depth + /// @param _voteOptionTreeDepth The vote option tree depth + /// @param _pollVk The poll verifying key + function setPollVkKey( + uint256 _stateTreeDepth, + uint256 _voteOptionTreeDepth, + VerifyingKey calldata _pollVk + ) public onlyOwner { + uint256 pollVkSig = genPollVkSig(_stateTreeDepth, _voteOptionTreeDepth); + + if (pollVkSet[pollVkSig]) revert PollVkAlreadySet(); + + VerifyingKey storage pollVk = pollVks[pollVkSig]; + pollVk.alpha1 = _pollVk.alpha1; + pollVk.beta2 = _pollVk.beta2; + pollVk.gamma2 = _pollVk.gamma2; + pollVk.delta2 = _pollVk.delta2; + + uint256 pollIcLength = _pollVk.ic.length; + for (uint256 i = 0; i < pollIcLength; ) { + pollVk.ic.push(_pollVk.ic[i]); + + unchecked { + i++; + } + } + + pollVkSet[pollVkSig] = true; + + emit PollVkSet(pollVkSig); } /// @notice Check if the process verifying key is set @@ -253,4 +339,23 @@ contract VkRegistry is Ownable(msg.sender), DomainObjs, SnarkCommon, IVkRegistry vk = getTallyVkBySig(sig, _mode); } + + /// @notice Get the poll verifying key by signature + /// @param _sig The signature + /// @return vk The verifying key + function getPollVkBySig(uint256 _sig) public view returns (VerifyingKey memory vk) { + if (!pollVkSet[_sig]) revert PollVkNotSet(); + + vk = pollVks[_sig]; + } + + /// @inheritdoc IVkRegistry + function getPollVk( + uint256 _stateTreeDepth, + uint256 _voteOptionTreeDepth + ) public view returns (VerifyingKey memory vk) { + uint256 sig = genPollVkSig(_stateTreeDepth, _voteOptionTreeDepth); + + vk = getPollVkBySig(sig); + } } diff --git a/packages/contracts/contracts/interfaces/IMACI.sol b/packages/contracts/contracts/interfaces/IMACI.sol index 050c780503..eb4fc09edb 100644 --- a/packages/contracts/contracts/interfaces/IMACI.sol +++ b/packages/contracts/contracts/interfaces/IMACI.sol @@ -1,6 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import { IVerifier } from "./IVerifier.sol"; +import { IVkRegistry } from "./IVkRegistry.sol"; +import { DomainObjs } from "../utilities/DomainObjs.sol"; + /// @title IMACI /// @notice MACI interface interface IMACI { @@ -12,6 +16,11 @@ interface IMACI { /// @return The Merkle root function getStateTreeRoot() external view returns (uint256); + /// @notice Return the state root when the '_index' user signed up + /// @param _index The serial number when the user signed up + /// @return The Merkle root + function getStateRootOnIndexedSignUp(uint256 _index) external view returns (uint256); + /// @notice Get the number of signups /// @return numsignUps The number of signups function numSignUps() external view returns (uint256); diff --git a/packages/contracts/contracts/interfaces/IPoll.sol b/packages/contracts/contracts/interfaces/IPoll.sol index 00a7942ed3..c918791ed0 100644 --- a/packages/contracts/contracts/interfaces/IPoll.sol +++ b/packages/contracts/contracts/interfaces/IPoll.sol @@ -7,6 +7,15 @@ import { IMACI } from "./IMACI.sol"; /// @title IPoll /// @notice Poll interface interface IPoll { + /// @notice Join the poll + function joinPoll( + uint256 _nullifier, + DomainObjs.PubKey memory _pubKey, + uint256 _newVoiceCreditBalance, + uint256 _stateRootIndex, + uint256[8] memory _proof + ) external; + /// @notice The number of messages which have been processed and the number of signups /// @return numSignups The number of signups /// @return numMsgs The number of messages sent by voters @@ -27,10 +36,10 @@ 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. This allows the + /// @notice The second step of merging the poll state. This allows the /// ProcessMessages circuit to access the latest state tree and ballots via /// currentSbCommitment. - function mergeMaciState() external; + function mergeState() external; /// @notice Returns the Poll's deploy time and duration /// @return _deployTime The deployment timestamp diff --git a/packages/contracts/contracts/interfaces/IPollFactory.sol b/packages/contracts/contracts/interfaces/IPollFactory.sol index 8d8351af23..a1e318ba3b 100644 --- a/packages/contracts/contracts/interfaces/IPollFactory.sol +++ b/packages/contracts/contracts/interfaces/IPollFactory.sol @@ -13,7 +13,7 @@ interface IPollFactory { /// @param _treeDepths The depths of the merkle trees /// @param _messageBatchSize The size of message batch /// @param _coordinatorPubKey The coordinator's public key - /// @param _extContracts The external contract interface references + /// @param _extContracts The external contracts interface references /// @return The deployed Poll contract function deploy( uint256 _duration, diff --git a/packages/contracts/contracts/interfaces/IVkRegistry.sol b/packages/contracts/contracts/interfaces/IVkRegistry.sol index 5d0437d972..9b8855a160 100644 --- a/packages/contracts/contracts/interfaces/IVkRegistry.sol +++ b/packages/contracts/contracts/interfaces/IVkRegistry.sol @@ -32,4 +32,13 @@ interface IVkRegistry { uint8 _messageBatchSize, DomainObjs.Mode _mode ) external view returns (SnarkCommon.VerifyingKey memory); + + /// @notice Get the poll verifying key + /// @param _stateTreeDepth The state tree depth + /// @param _voteOptionTreeDepth The vote option tree depth + /// @return The verifying key + function getPollVk( + uint256 _stateTreeDepth, + uint256 _voteOptionTreeDepth + ) external view returns (SnarkCommon.VerifyingKey memory); } diff --git a/packages/contracts/contracts/trees/LeanIMT.sol b/packages/contracts/contracts/trees/LeanIMT.sol new file mode 100644 index 0000000000..c82f8d7a0b --- /dev/null +++ b/packages/contracts/contracts/trees/LeanIMT.sol @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import { PoseidonT3 } from "../crypto/PoseidonT3.sol"; + +struct LeanIMTData { + // Tracks the current number of leaves in the tree. + uint256 size; + // Represents the current depth of the tree, which can increase as new leaves are inserted. + uint256 depth; + // A mapping from each level of the tree to the node value of the last even position at that level. + // Used for efficient inserts, updates and root calculations. + mapping(uint256 => uint256) sideNodes; + // A mapping from leaf values to their respective indices in the tree. + // This facilitates checks for leaf existence and retrieval of leaf positions. + mapping(uint256 => uint256) leaves; +} + +error WrongSiblingNodes(); +error LeafGreaterThanSnarkScalarField(); +error LeafCannotBeZero(); +error LeafAlreadyExists(); +error LeafDoesNotExist(); + +/// @title Lean Incremental binary Merkle tree. +/// @dev The LeanIMT is an optimized version of the BinaryIMT. +/// This implementation eliminates the use of zeroes, and make the tree depth dynamic. +/// When a node doesn't have the right child, instead of using a zero hash as in the BinaryIMT, +/// the node's value becomes that of its left child. Furthermore, rather than utilizing a static tree depth, +/// it is updated based on the number of leaves in the tree. This approach +/// results in the calculation of significantly fewer hashes, making the tree more efficient. +library InternalLeanIMT { + /// @notice The scalar field + uint256 internal constant SNARK_SCALAR_FIELD = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + /// @dev Inserts a new leaf into the incremental merkle tree. + /// The function ensures that the leaf is valid according to the + /// constraints of the tree and then updates the tree's structure accordingly. + /// @param self: A storage reference to the 'LeanIMTData' struct. + /// @param leaf: The value of the new leaf to be inserted into the tree. + /// @return The new hash of the node after the leaf has been inserted. + function _insert(LeanIMTData storage self, uint256 leaf) internal returns (uint256) { + if (leaf >= SNARK_SCALAR_FIELD) { + revert LeafGreaterThanSnarkScalarField(); + } else if (leaf == 0) { + revert LeafCannotBeZero(); + } else if (_has(self, leaf)) { + revert LeafAlreadyExists(); + } + + uint256 index = self.size; + + // Cache tree depth to optimize gas + uint256 treeDepth = self.depth; + + // A new insertion can increase a tree's depth by at most 1, + // and only if the number of leaves supported by the current + // depth is less than the number of leaves to be supported after insertion. + if (2 ** treeDepth < index + 1) { + ++treeDepth; + } + + self.depth = treeDepth; + + uint256 node = leaf; + + for (uint256 level = 0; level < treeDepth; ) { + if ((index >> level) & 1 == 1) { + node = PoseidonT3.poseidon([self.sideNodes[level], node]); + } else { + self.sideNodes[level] = node; + } + + unchecked { + ++level; + } + } + + self.size = ++index; + + self.sideNodes[treeDepth] = node; + self.leaves[leaf] = index; + + return node; + } + + /// @dev Inserts many leaves into the incremental merkle tree. + /// The function ensures that the leaves are valid according to the + /// constraints of the tree and then updates the tree's structure accordingly. + /// @param self: A storage reference to the 'LeanIMTData' struct. + /// @param leaves: The values of the new leaves to be inserted into the tree. + /// @return The root after the leaves have been inserted. + function _insertMany(LeanIMTData storage self, uint256[] calldata leaves) internal returns (uint256) { + // Cache tree size to optimize gas + uint256 treeSize = self.size; + + // Check that all the new values are correct to be added. + for (uint256 i = 0; i < leaves.length; ) { + if (leaves[i] >= SNARK_SCALAR_FIELD) { + revert LeafGreaterThanSnarkScalarField(); + } else if (leaves[i] == 0) { + revert LeafCannotBeZero(); + } else if (_has(self, leaves[i])) { + revert LeafAlreadyExists(); + } + + self.leaves[leaves[i]] = treeSize + 1 + i; + + unchecked { + ++i; + } + } + + // Array to save the nodes that will be used to create the next level of the tree. + uint256[] memory currentLevelNewNodes; + + currentLevelNewNodes = leaves; + + // Cache tree depth to optimize gas + uint256 treeDepth = self.depth; + + // Calculate the depth of the tree after adding the new values. + // Unlike the 'insert' function, we need a while here as + // N insertions can increase the tree's depth more than once. + while (2 ** treeDepth < treeSize + leaves.length) { + ++treeDepth; + } + + self.depth = treeDepth; + + // First index to change in every level. + uint256 currentLevelStartIndex = treeSize; + + // Size of the level used to create the next level. + uint256 currentLevelSize = treeSize + leaves.length; + + // The index where changes begin at the next level. + uint256 nextLevelStartIndex = currentLevelStartIndex >> 1; + + // The size of the next level. + uint256 nextLevelSize = ((currentLevelSize - 1) >> 1) + 1; + + for (uint256 level = 0; level < treeDepth; ) { + // The number of nodes for the new level that will be created, + // only the new values, not the entire level. + uint256 numberOfNewNodes = nextLevelSize - nextLevelStartIndex; + uint256[] memory nextLevelNewNodes = new uint256[](numberOfNewNodes); + for (uint256 i = 0; i < numberOfNewNodes; ) { + uint256 leftNode; + + // Assign the left node using the saved path or the position in the array. + if ((i + nextLevelStartIndex) * 2 < currentLevelStartIndex) { + leftNode = self.sideNodes[level]; + } else { + leftNode = currentLevelNewNodes[(i + nextLevelStartIndex) * 2 - currentLevelStartIndex]; + } + + uint256 rightNode; + + // Assign the right node if the value exists. + if ((i + nextLevelStartIndex) * 2 + 1 < currentLevelSize) { + rightNode = currentLevelNewNodes[(i + nextLevelStartIndex) * 2 + 1 - currentLevelStartIndex]; + } + + uint256 parentNode; + + // Assign the parent node. + // If it has a right child the result will be the hash(leftNode, rightNode) if not, + // it will be the leftNode. + if (rightNode != 0) { + parentNode = PoseidonT3.poseidon([leftNode, rightNode]); + } else { + parentNode = leftNode; + } + + nextLevelNewNodes[i] = parentNode; + + unchecked { + ++i; + } + } + + // Update the `sideNodes` variable. + // If `currentLevelSize` is odd, the saved value will be the last value of the array + // if it is even and there are more than 1 element in `currentLevelNewNodes`, the saved value + // will be the value before the last one. + // If it is even and there is only one element, there is no need to save anything because + // the correct value for this level was already saved before. + if (currentLevelSize & 1 == 1) { + self.sideNodes[level] = currentLevelNewNodes[currentLevelNewNodes.length - 1]; + } else if (currentLevelNewNodes.length > 1) { + self.sideNodes[level] = currentLevelNewNodes[currentLevelNewNodes.length - 2]; + } + + currentLevelStartIndex = nextLevelStartIndex; + + // Calculate the next level startIndex value. + // It is the position of the parent node which is pos/2. + nextLevelStartIndex >>= 1; + + // Update the next array that will be used to calculate the next level. + currentLevelNewNodes = nextLevelNewNodes; + + currentLevelSize = nextLevelSize; + + // Calculate the size of the next level. + // The size of the next level is (currentLevelSize - 1) / 2 + 1. + nextLevelSize = ((nextLevelSize - 1) >> 1) + 1; + + unchecked { + ++level; + } + } + + // Update tree size + self.size = treeSize + leaves.length; + + // Update tree root + self.sideNodes[treeDepth] = currentLevelNewNodes[0]; + + return currentLevelNewNodes[0]; + } + + /// @dev Updates the value of an existing leaf and recalculates hashes + /// to maintain tree integrity. + /// @param self: A storage reference to the 'LeanIMTData' struct. + /// @param oldLeaf: The value of the leaf that is to be updated. + /// @param newLeaf: The new value that will replace the oldLeaf in the tree. + /// @param siblingNodes: An array of sibling nodes that are necessary to recalculate the path to the root. + /// @return The new hash of the updated node after the leaf has been updated. + function _update( + LeanIMTData storage self, + uint256 oldLeaf, + uint256 newLeaf, + uint256[] calldata siblingNodes + ) internal returns (uint256) { + if (newLeaf >= SNARK_SCALAR_FIELD) { + revert LeafGreaterThanSnarkScalarField(); + } else if (!_has(self, oldLeaf)) { + revert LeafDoesNotExist(); + } else if (_has(self, newLeaf)) { + revert LeafAlreadyExists(); + } + + uint256 index = _indexOf(self, oldLeaf); + uint256 node = newLeaf; + uint256 oldRoot = oldLeaf; + + uint256 lastIndex = self.size - 1; + uint256 i = 0; + + // Cache tree depth to optimize gas + uint256 treeDepth = self.depth; + + for (uint256 level = 0; level < treeDepth; ) { + if ((index >> level) & 1 == 1) { + if (siblingNodes[i] >= SNARK_SCALAR_FIELD) { + revert LeafGreaterThanSnarkScalarField(); + } + + node = PoseidonT3.poseidon([siblingNodes[i], node]); + oldRoot = PoseidonT3.poseidon([siblingNodes[i], oldRoot]); + + unchecked { + ++i; + } + } else { + if (index >> level != lastIndex >> level) { + if (siblingNodes[i] >= SNARK_SCALAR_FIELD) { + revert LeafGreaterThanSnarkScalarField(); + } + + node = PoseidonT3.poseidon([node, siblingNodes[i]]); + oldRoot = PoseidonT3.poseidon([oldRoot, siblingNodes[i]]); + + unchecked { + ++i; + } + } else { + self.sideNodes[i] = node; + } + } + + unchecked { + ++level; + } + } + + if (oldRoot != _root(self)) { + revert WrongSiblingNodes(); + } + + self.sideNodes[treeDepth] = node; + + if (newLeaf != 0) { + self.leaves[newLeaf] = self.leaves[oldLeaf]; + } + + self.leaves[oldLeaf] = 0; + + return node; + } + + /// @dev Removes a leaf from the tree by setting its value to zero. + /// This function utilizes the update function to set the leaf's value + /// to zero and update the tree's state accordingly. + /// @param self: A storage reference to the 'LeanIMTData' struct. + /// @param oldLeaf: The value of the leaf to be removed. + /// @param siblingNodes: An array of sibling nodes required for updating the path to the root after removal. + /// @return The new root hash of the tree after the leaf has been removed. + function _remove( + LeanIMTData storage self, + uint256 oldLeaf, + uint256[] calldata siblingNodes + ) internal returns (uint256) { + return _update(self, oldLeaf, 0, siblingNodes); + } + + /// @dev Checks if a leaf exists in the tree. + /// @param self: A storage reference to the 'LeanIMTData' struct. + /// @param leaf: The value of the leaf to check for existence. + /// @return A boolean value indicating whether the leaf exists in the tree. + function _has(LeanIMTData storage self, uint256 leaf) internal view returns (bool) { + return self.leaves[leaf] != 0; + } + + /// @dev Retrieves the index of a given leaf in the tree. + /// @param self: A storage reference to the 'LeanIMTData' struct. + /// @param leaf: The value of the leaf whose index is to be found. + /// @return The index of the specified leaf within the tree. If the leaf is not present, the function + /// reverts with a custom error. + function _indexOf(LeanIMTData storage self, uint256 leaf) internal view returns (uint256) { + if (self.leaves[leaf] == 0) { + revert LeafDoesNotExist(); + } + + return self.leaves[leaf] - 1; + } + + /// @dev Retrieves the root of the tree from the 'sideNodes' mapping using the + /// current tree depth. + /// @param self: A storage reference to the 'LeanIMTData' struct. + /// @return The root hash of the tree. + function _root(LeanIMTData storage self) internal view returns (uint256) { + return self.sideNodes[self.depth]; + } +} diff --git a/packages/contracts/package.json b/packages/contracts/package.json index f382bd8891..01aee349f4 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -169,6 +169,8 @@ "@openzeppelin/contracts": "^5.1.0", "@openzeppelin/merkle-tree": "^1.0.7", "@pcd/util": "^0.8.0", + "@zk-kit/imt.sol": "2.0.0-beta.12", + "@zk-kit/lean-imt": "^2.1.0", "circomlibjs": "^0.1.7", "ethers": "^6.13.4", "hardhat": "^2.22.15", diff --git a/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts b/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts index 4826526c57..1a45c11a0c 100644 --- a/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts +++ b/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts @@ -33,6 +33,7 @@ deployment.deployTask(EDeploySteps.VkRegistry, "Deploy Vk Registry and set keys" const intStateTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "intStateTreeDepth"); const messageBatchDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageBatchDepth"); const voteOptionTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "voteOptionTreeDepth"); + const pollJoiningTestZkeyPath = deployment.getDeployConfigField(EContracts.VkRegistry, "zkeys.pollZkey"); const processMessagesZkeyPathQv = deployment.getDeployConfigField( EContracts.VkRegistry, "zkeys.qv.processMessagesZkey", @@ -60,11 +61,12 @@ deployment.deployTask(EDeploySteps.VkRegistry, "Deploy Vk Registry and set keys" throw new Error("Non-QV zkeys are not set"); } - const [qvProcessVk, qvTallyVk, nonQvProcessVk, nonQvTallyQv] = await Promise.all([ + const [qvProcessVk, qvTallyVk, nonQvProcessVk, nonQvTallyQv, pollVk] = await Promise.all([ processMessagesZkeyPathQv && extractVk(processMessagesZkeyPathQv), tallyVotesZkeyPathQv && extractVk(tallyVotesZkeyPathQv), processMessagesZkeyPathNonQv && extractVk(processMessagesZkeyPathNonQv), tallyVotesZkeyPathNonQv && extractVk(tallyVotesZkeyPathNonQv), + pollJoiningTestZkeyPath && extractVk(pollJoiningTestZkeyPath), ]).then((vks) => vks.map( (vk: IVkObjectParams | "" | undefined) => @@ -77,6 +79,7 @@ deployment.deployTask(EDeploySteps.VkRegistry, "Deploy Vk Registry and set keys" signer: deployer, }); + const pollZkeys = pollVk as IVerifyingKeyStruct; const processZkeys = [qvProcessVk, nonQvProcessVk].filter(Boolean) as IVerifyingKeyStruct[]; const tallyZkeys = [qvTallyVk, nonQvTallyQv].filter(Boolean) as IVerifyingKeyStruct[]; const modes: EMode[] = []; @@ -96,6 +99,7 @@ deployment.deployTask(EDeploySteps.VkRegistry, "Deploy Vk Registry and set keys" voteOptionTreeDepth, 5 ** messageBatchDepth, modes, + pollZkeys, processZkeys, tallyZkeys, ) diff --git a/packages/contracts/tasks/helpers/TreeMerger.ts b/packages/contracts/tasks/helpers/TreeMerger.ts index 729ca6c81d..3d0646ff0c 100644 --- a/packages/contracts/tasks/helpers/TreeMerger.ts +++ b/packages/contracts/tasks/helpers/TreeMerger.ts @@ -55,7 +55,7 @@ export class TreeMerger { if (!(await this.pollContract.stateMerged())) { // go and merge the state tree console.log("Merging subroots to a main state root..."); - const receipt = await this.pollContract.mergeMaciState().then((tx) => tx.wait()); + const receipt = await this.pollContract.mergeState().then((tx) => tx.wait()); if (receipt?.status !== 1) { throw new Error("Error merging signup state subroots"); diff --git a/packages/contracts/tests/MACI.test.ts b/packages/contracts/tests/MACI.test.ts index bbcc411b41..3fa14ac8b0 100644 --- a/packages/contracts/tests/MACI.test.ts +++ b/packages/contracts/tests/MACI.test.ts @@ -306,16 +306,6 @@ describe("MACI", function test() { expect(receipt?.status).to.eq(1); }); - it("should have the correct state root on chain after calculating the root on chain", async () => { - maciState.polls.get(pollId)?.updatePoll(await pollContract.numSignups()); - expect(await maciContract.getStateTreeRoot()).to.eq(maciState.polls.get(pollId)?.stateTree?.root.toString()); - }); - - it("should get the correct state root with getStateTreeRoot", async () => { - const onChainStateRoot = await maciContract.getStateTreeRoot(); - expect(onChainStateRoot.toString()).to.eq(maciState.polls.get(pollId)?.stateTree?.root.toString()); - }); - it("should allow a user to signup after the state tree root was calculated", async () => { const tx = await maciContract.signUp( users[0].pubKey.asContractParam(), diff --git a/packages/contracts/tests/MessageProcessor.test.ts b/packages/contracts/tests/MessageProcessor.test.ts index 4cfe01175a..3c1d499dc4 100644 --- a/packages/contracts/tests/MessageProcessor.test.ts +++ b/packages/contracts/tests/MessageProcessor.test.ts @@ -129,7 +129,7 @@ describe("MessageProcessor", () => { generatedInputs = poll.processMessages(pollId); // set the verification keys on the vk smart contract - vkRegistryContract.setVerifyingKeys( + await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, treeDepths.voteOptionTreeDepth, diff --git a/packages/contracts/tests/Poll.test.ts b/packages/contracts/tests/Poll.test.ts index dfb246749b..c9678c2a10 100644 --- a/packages/contracts/tests/Poll.test.ts +++ b/packages/contracts/tests/Poll.test.ts @@ -1,12 +1,14 @@ +/* eslint-disable no-await-in-loop */ /* eslint-disable no-underscore-dangle */ import { expect } from "chai"; -import { Signer } from "ethers"; +import { AbiCoder, Signer } from "ethers"; import { EthereumProvider } from "hardhat/types"; import { MaciState } from "maci-core"; import { NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { Keypair, Message, PCommand, PubKey } from "maci-domainobjs"; import { EMode } from "../ts/constants"; +import { IVerifyingKeyStruct } from "../ts/types"; import { getDefaultSigner } from "../ts/utils"; import { Poll__factory as PollFactory, MACI, Poll as PollContract, Verifier, VkRegistry } from "../typechain-types"; @@ -16,6 +18,9 @@ import { initialVoiceCreditBalance, maxVoteOptions, messageBatchSize, + testPollVk, + testProcessVk, + testTallyVk, treeDepths, } from "./constants"; import { timeTravel, deployTestContracts } from "./utils"; @@ -34,6 +39,8 @@ describe("Poll", () => { const keypair = new Keypair(); + const NUM_USERS = 2; + describe("deployment", () => { before(async () => { signer = await getDefaultSigner(); @@ -46,6 +53,19 @@ describe("Poll", () => { verifierContract = r.mockVerifierContract as Verifier; vkRegistryContract = r.vkRegistryContract; + for (let i = 0; i < NUM_USERS; i += 1) { + const timestamp = Math.floor(Date.now() / 1000); + const user = new Keypair(); + maciState.signUp(user.pubKey, BigInt(initialVoiceCreditBalance), BigInt(timestamp), BigInt(0)); + + // eslint-disable-next-line no-await-in-loop + await maciContract.signUp( + user.pubKey.asContractParam(), + AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), + AbiCoder.defaultAbiCoder().encode(["uint256"], [0]), + ); + } + // deploy on chain poll const tx = await maciContract.deployPoll( duration, @@ -88,6 +108,25 @@ describe("Poll", () => { BigInt("19824078218392094440610104313265183977899662750282163392862422243483260492317"), ]); maciState.polls.get(pollId)?.publishMessage(message, padKey); + + // set the verification keys on the vk smart contract + await vkRegistryContract.setPollVkKey( + STATE_TREE_DEPTH, + treeDepths.voteOptionTreeDepth, + testPollVk.asContractParam() as IVerifyingKeyStruct, + { gasLimit: 10000000 }, + ); + + await vkRegistryContract.setVerifyingKeys( + STATE_TREE_DEPTH, + treeDepths.intStateTreeDepth, + treeDepths.voteOptionTreeDepth, + messageBatchSize, + EMode.QV, + testProcessVk.asContractParam() as IVerifyingKeyStruct, + testTallyVk.asContractParam() as IVerifyingKeyStruct, + { gasLimit: 10000000 }, + ); }); it("should not be possible to init the Poll contract twice", async () => { @@ -269,4 +308,75 @@ describe("Poll", () => { expect(await pollContract.getBatchHashes()).to.deep.eq(maciState.polls.get(pollId)?.batchHashes); }); }); + + describe("Poll join", () => { + it("The users have joined the poll", async () => { + const iface = pollContract.interface; + const pubkey = keypair.pubKey.asContractParam(); + const mockProof = [0, 0, 0, 0, 0, 0, 0, 0]; + + for (let i = 0; i < NUM_USERS; i += 1) { + const mockNullifier = AbiCoder.defaultAbiCoder().encode(["uint256"], [i]); + const voiceCreditBalance = AbiCoder.defaultAbiCoder().encode(["uint256"], [i]); + + const response = await pollContract.joinPoll(mockNullifier, pubkey, voiceCreditBalance, i, mockProof); + const receipt = await response.wait(); + const logs = receipt!.logs[0]; + const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { + args: { _pollStateIndex: bigint }; + }; + const index = event.args._pollStateIndex; + + expect(receipt!.status).to.eq(1); + + const block = await signer.provider!.getBlock(receipt!.blockHash); + const { timestamp } = block!; + + const expectedIndex = maciState.polls + .get(pollId) + ?.joinPoll(BigInt(mockNullifier), keypair.pubKey, BigInt(voiceCreditBalance), BigInt(timestamp)); + + expect(index).to.eq(expectedIndex); + } + }); + + it("Poll state tree size after user's joining", async () => { + const pollStateTree = await pollContract.pollStateTree(); + const size = Number(pollStateTree.numberOfLeaves); + expect(size).to.eq(maciState.polls.get(pollId)?.pollStateLeaves.length); + }); + + it("The first user has been rejected for the second join", async () => { + const mockNullifier = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]); + const pubkey = keypair.pubKey.asContractParam(); + const voiceCreditBalance = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]); + const mockProof = [0, 0, 0, 0, 0, 0, 0, 0]; + + await expect( + pollContract.joinPoll(mockNullifier, pubkey, voiceCreditBalance, 0, mockProof), + ).to.be.revertedWithCustomError(pollContract, "UserAlreadyJoined"); + }); + + it("should allow a Poll contract to merge the state tree (calculate the state root)", async () => { + await timeTravel(signer.provider as unknown as EthereumProvider, Number(duration) + 1); + + const tx = await pollContract.mergeState({ + gasLimit: 3000000, + }); + + const receipt = await tx.wait(); + expect(receipt?.status).to.eq(1); + }); + + it("should get the correct numSignUps", async () => { + const numSignUps = await pollContract.numSignups(); + expect(numSignUps).to.be.eq(maciState.polls.get(pollId)?.pollStateLeaves.length); + maciState.polls.get(pollId)?.updatePoll(numSignUps); + }); + + it("should get the correct mergedStateRoot", async () => { + const mergedStateRoot = await pollContract.mergedStateRoot(); + expect(mergedStateRoot.toString()).to.eq(maciState.polls.get(pollId)?.pollStateTree?.root.toString()); + }); + }); }); diff --git a/packages/contracts/tests/PollFactory.test.ts b/packages/contracts/tests/PollFactory.test.ts index 3cd8e4f3b7..af65c4b9e7 100644 --- a/packages/contracts/tests/PollFactory.test.ts +++ b/packages/contracts/tests/PollFactory.test.ts @@ -5,13 +5,21 @@ import { Keypair } from "maci-domainobjs"; import { deployPollFactory, getDefaultSigner } from "../ts"; import { MACI, PollFactory, Verifier, VkRegistry } from "../typechain-types"; -import { messageBatchSize, initialVoiceCreditBalance, maxVoteOptions, STATE_TREE_DEPTH, treeDepths } from "./constants"; +import { + messageBatchSize, + initialVoiceCreditBalance, + maxVoteOptions, + STATE_TREE_DEPTH, + treeDepths, + ExtContractsStruct, +} from "./constants"; import { deployTestContracts } from "./utils"; describe("pollFactory", () => { let maciContract: MACI; let verifierContract: Verifier; let vkRegistryContract: VkRegistry; + let extContracts: ExtContractsStruct; let pollFactory: PollFactory; let signer: Signer; @@ -23,6 +31,7 @@ describe("pollFactory", () => { maciContract = r.maciContract; verifierContract = r.mockVerifierContract as Verifier; vkRegistryContract = r.vkRegistryContract; + extContracts = { maci: maciContract, verifier: verifierContract, vkRegistry: vkRegistryContract }; pollFactory = (await deployPollFactory(signer, undefined, true)) as BaseContract as PollFactory; }); @@ -35,7 +44,7 @@ describe("pollFactory", () => { treeDepths, messageBatchSize, coordinatorPubKey.asContractParam(), - { maci: maciContract, verifier: verifierContract, vkRegistry: vkRegistryContract }, + extContracts, ); const receipt = await tx.wait(); expect(receipt?.status).to.eq(1); @@ -50,7 +59,7 @@ describe("pollFactory", () => { treeDepths, messageBatchSize, coordinatorPubKey.asContractParam(), - { maci: maciContract, verifier: verifierContract, vkRegistry: vkRegistryContract }, + extContracts, ), ).to.be.revertedWithCustomError(pollFactory, "InvalidMaxVoteOptions"); }); diff --git a/packages/contracts/tests/Tally.test.ts b/packages/contracts/tests/Tally.test.ts index 32666a77df..5083a6a3ab 100644 --- a/packages/contracts/tests/Tally.test.ts +++ b/packages/contracts/tests/Tally.test.ts @@ -27,6 +27,7 @@ import { initialVoiceCreditBalance, maxVoteOptions, messageBatchSize, + testPollVk, testProcessVk, testTallyVk, treeDepths, @@ -44,6 +45,7 @@ describe("TallyVotes", () => { const coordinator = new Keypair(); let users: Keypair[]; + let pollKeys: Keypair[]; let maciState: MaciState; let pollId: bigint; @@ -174,7 +176,7 @@ describe("TallyVotes", () => { let tallyGeneratedInputs: ITallyCircuitInputs; before(async () => { - await pollContract.mergeMaciState(); + await pollContract.mergeState(); tallyGeneratedInputs = poll.tallyVotes(); }); @@ -210,6 +212,7 @@ describe("TallyVotes", () => { before(async () => { // create 24 users (total 25 - 24 + 1 nothing up my sleeve) users = Array.from({ length: 24 }, () => new Keypair()); + pollKeys = Array.from({ length: 24 }, () => new Keypair()); maciState = new MaciState(STATE_TREE_DEPTH); const updatedDuration = 5000000; @@ -311,7 +314,13 @@ describe("TallyVotes", () => { // update the poll state poll.updatePoll(BigInt(maciState.stateLeaves.length)); - // set the verification keys on the vk smart contract + await vkRegistryContract.setPollVkKey( + STATE_TREE_DEPTH, + treeDepths.voteOptionTreeDepth, + testPollVk.asContractParam() as IVerifyingKeyStruct, + { gasLimit: 10000000 }, + ); + await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, intStateTreeDepth, @@ -322,8 +331,26 @@ describe("TallyVotes", () => { testTallyVk.asContractParam() as IVerifyingKeyStruct, ); + // join all user to the Poll + for (let i = 0; i < users.length; i += 1) { + const timestamp = Math.floor(Date.now() / 1000); + // join locally + const nullifier = poseidon([BigInt(users[i].privKey.rawPrivKey)]); + poll.joinPoll(nullifier, pollKeys[i].pubKey, BigInt(initialVoiceCreditBalance), BigInt(timestamp)); + + // join on chain + // eslint-disable-next-line no-await-in-loop + await pollContract.joinPoll( + nullifier, + pollKeys[i].pubKey.asContractParam(), + BigInt(initialVoiceCreditBalance), + i, + [0, 0, 0, 0, 0, 0, 0, 0], + ); + } + await timeTravel(signer.provider! as unknown as EthereumProvider, updatedDuration); - await pollContract.mergeMaciState(); + await pollContract.mergeState(); const processMessagesInputs = poll.processMessages(pollId); @@ -507,6 +534,8 @@ describe("TallyVotes", () => { before(async () => { // create 25 users (and thus 26 ballots) (total 26 - 25 + 1 nothing up my sleeve) users = Array.from({ length: 25 }, () => new Keypair()); + pollKeys = Array.from({ length: 25 }, () => new Keypair()); + maciState = new MaciState(STATE_TREE_DEPTH); const updatedDuration = 5000000; @@ -609,6 +638,13 @@ describe("TallyVotes", () => { poll.updatePoll(BigInt(maciState.stateLeaves.length)); // set the verification keys on the vk smart contract + await vkRegistryContract.setPollVkKey( + STATE_TREE_DEPTH, + treeDepths.voteOptionTreeDepth, + testPollVk.asContractParam() as IVerifyingKeyStruct, + { gasLimit: 10000000 }, + ); + await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, intStateTreeDepth, @@ -619,8 +655,26 @@ describe("TallyVotes", () => { testTallyVk.asContractParam() as IVerifyingKeyStruct, ); + // join all user to the Poll + for (let i = 0; i < users.length; i += 1) { + const timestamp = Math.floor(Date.now() / 1000); + // join locally + const nullifier = poseidon([BigInt(users[i].privKey.rawPrivKey)]); + poll.joinPoll(nullifier, pollKeys[i].pubKey, BigInt(initialVoiceCreditBalance), BigInt(timestamp)); + + // join on chain + // eslint-disable-next-line no-await-in-loop + await pollContract.joinPoll( + nullifier, + pollKeys[i].pubKey.asContractParam(), + BigInt(initialVoiceCreditBalance), + i, + [0, 0, 0, 0, 0, 0, 0, 0], + ); + } + await timeTravel(signer.provider! as unknown as EthereumProvider, updatedDuration); - await pollContract.mergeMaciState(); + await pollContract.mergeState(); const processMessagesInputs = poll.processMessages(pollId); diff --git a/packages/contracts/tests/TallyNonQv.test.ts b/packages/contracts/tests/TallyNonQv.test.ts index 3125a76ec8..bcb464b85c 100644 --- a/packages/contracts/tests/TallyNonQv.test.ts +++ b/packages/contracts/tests/TallyNonQv.test.ts @@ -169,7 +169,7 @@ describe("TallyVotesNonQv", () => { describe("after messages processing", () => { let tallyGeneratedInputs: ITallyCircuitInputs; before(async () => { - await pollContract.mergeMaciState(); + await pollContract.mergeState(); tallyGeneratedInputs = poll.tallyVotes(); }); diff --git a/packages/contracts/tests/VkRegistry.test.ts b/packages/contracts/tests/VkRegistry.test.ts index e587f5e096..63e2098e32 100644 --- a/packages/contracts/tests/VkRegistry.test.ts +++ b/packages/contracts/tests/VkRegistry.test.ts @@ -6,6 +6,7 @@ import { EMode } from "../ts/constants"; import { messageBatchSize, + testPollVk, testProcessVk, testProcessVkNonQv, testTallyVk, @@ -31,8 +32,32 @@ describe("VkRegistry", () => { }); }); + describe("setPollVkKey", () => { + it("should set the poll vk", async () => { + const tx = await vkRegistryContract.setPollVkKey( + stateTreeDepth + 1, + treeDepths.voteOptionTreeDepth, + testPollVk.asContractParam() as IVerifyingKeyStruct, + { gasLimit: 1000000 }, + ); + const receipt = await tx.wait(); + expect(receipt?.status).to.eq(1); + }); + + it("should throw when trying to set another vk for the same params", async () => { + await expect( + vkRegistryContract.setPollVkKey( + stateTreeDepth + 1, + treeDepths.voteOptionTreeDepth, + testPollVk.asContractParam() as IVerifyingKeyStruct, + { gasLimit: 1000000 }, + ), + ).to.be.revertedWithCustomError(vkRegistryContract, "PollVkAlreadySet"); + }); + }); + describe("setVerifyingKeys", () => { - it("should set the process and tally vks", async () => { + it("should set the process, tally vks", async () => { const tx = await vkRegistryContract.setVerifyingKeys( stateTreeDepth, treeDepths.intStateTreeDepth, @@ -90,13 +115,14 @@ describe("VkRegistry", () => { }); describe("setVerifyingKeysBatch", () => { - it("should set the process and tally vks", async () => { + it("should set the process, tally, poll vks", async () => { const tx = await vkRegistryContract.setVerifyingKeysBatch( stateTreeDepth, treeDepths.intStateTreeDepth, treeDepths.voteOptionTreeDepth, messageBatchSize, [EMode.NON_QV], + testPollVk.asContractParam() as IVerifyingKeyStruct, [testProcessVkNonQv.asContractParam() as IVerifyingKeyStruct], [testTallyVkNonQv.asContractParam() as IVerifyingKeyStruct], ); @@ -113,6 +139,7 @@ describe("VkRegistry", () => { treeDepths.voteOptionTreeDepth, messageBatchSize, [EMode.QV], + testPollVk.asContractParam() as IVerifyingKeyStruct, [ testProcessVk.asContractParam() as IVerifyingKeyStruct, testProcessVkNonQv.asContractParam() as IVerifyingKeyStruct, @@ -174,6 +201,14 @@ describe("VkRegistry", () => { }); describe("genSignatures", () => { + describe("genPollVkSig", () => { + it("should generate a valid signature", async () => { + const sig = await vkRegistryContract.genPollVkSig(stateTreeDepth, treeDepths.voteOptionTreeDepth); + const vk = await vkRegistryContract.getPollVkBySig(sig); + compareVks(testPollVk, vk); + }); + }); + describe("genProcessVkSig", () => { it("should generate a valid signature", async () => { const sig = await vkRegistryContract.genProcessVkSig( diff --git a/packages/contracts/tests/constants.ts b/packages/contracts/tests/constants.ts index 3758375f44..855b0e102e 100644 --- a/packages/contracts/tests/constants.ts +++ b/packages/contracts/tests/constants.ts @@ -1,7 +1,14 @@ +import { AddressLike } from "ethers"; import { TreeDepths, STATE_TREE_ARITY } from "maci-core"; import { G1Point, G2Point } from "maci-crypto"; import { VerifyingKey } from "maci-domainobjs"; +export interface ExtContractsStruct { + maci: AddressLike; + verifier: AddressLike; + vkRegistry: AddressLike; +} + export const duration = 2_000; export const STATE_TREE_DEPTH = 10; @@ -9,6 +16,14 @@ export const MESSAGE_TREE_DEPTH = 2; export const MESSAGE_TREE_SUBDEPTH = 1; export const messageBatchSize = 20; +export const testPollVk = new VerifyingKey( + new G1Point(BigInt(0), BigInt(1)), + new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), + new G2Point([BigInt(6), BigInt(7)], [BigInt(8), BigInt(9)]), + new G2Point([BigInt(10), BigInt(11)], [BigInt(12), BigInt(13)]), + [new G1Point(BigInt(14), BigInt(15)), new G1Point(BigInt(16), BigInt(17))], +); + export const testProcessVk = new VerifyingKey( new G1Point(BigInt(0), BigInt(1)), new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), diff --git a/packages/contracts/ts/genMaciState.ts b/packages/contracts/ts/genMaciState.ts index f6f90136c0..a23b80f882 100644 --- a/packages/contracts/ts/genMaciState.ts +++ b/packages/contracts/ts/genMaciState.ts @@ -78,6 +78,7 @@ export const genMaciStateFromContract = async ( pubKey: new PubKey([BigInt(event.args._userPubKeyX), BigInt(event.args._userPubKeyY)]), voiceCreditBalance: Number(event.args._voiceCreditBalance), timestamp: Number(event.args._timestamp), + stateLeaf: BigInt(event.args._stateLeaf), }, }); }); @@ -144,6 +145,33 @@ export const genMaciStateFromContract = async ( // eslint-disable-next-line no-await-in-loop const publishMessageLogs = await pollContract.queryFilter(pollContract.filters.PublishMessage(), i, toBlock); + // eslint-disable-next-line no-await-in-loop + const joinPollLogs = await pollContract.queryFilter(pollContract.filters.PollJoined(), i, toBlock); + + joinPollLogs.forEach((event) => { + assert(!!event); + + const nullifier = BigInt(event.args._nullifier); + + const pubKeyX = BigInt(event.args._pollPubKeyX); + const pubKeyY = BigInt(event.args._pollPubKeyY); + const timestamp = Number(event.args._timestamp); + + const newVoiceCreditBalance = BigInt(event.args._newVoiceCreditBalance); + + actions.push({ + type: "PollJoined", + blockNumber: event.blockNumber, + transactionIndex: event.transactionIndex, + data: { + pubKey: new PubKey([pubKeyX, pubKeyY]), + newVoiceCreditBalance, + timestamp, + nullifier, + }, + }); + }); + publishMessageLogs.forEach((event) => { assert(!!event); @@ -172,9 +200,9 @@ export const genMaciStateFromContract = async ( sortActions(actions).forEach((action) => { switch (true) { case action.type === "SignUp": { - const { pubKey, voiceCreditBalance, timestamp } = action.data; + const { pubKey, voiceCreditBalance, timestamp, stateLeaf } = action.data; - maciState.signUp(pubKey!, BigInt(voiceCreditBalance!), BigInt(timestamp!)); + maciState.signUp(pubKey!, BigInt(voiceCreditBalance!), BigInt(timestamp!), stateLeaf); break; } @@ -200,6 +228,12 @@ export const genMaciStateFromContract = async ( break; } + case action.type === "PollJoined": { + const { pubKey, newVoiceCreditBalance, timestamp, nullifier } = action.data; + maciState.polls.get(pollId)?.joinPoll(nullifier!, pubKey!, newVoiceCreditBalance!, BigInt(timestamp!)); + break; + } + default: break; } @@ -215,9 +249,6 @@ export const genMaciStateFromContract = async ( // set the number of signups poll.updatePoll(numSignUpsAndMessages[0]); - // we need to ensure that the stateRoot is correct - assert(poll.stateTree?.root.toString() === (await pollContract.mergedStateRoot()).toString()); - maciState.polls.set(pollId, poll); return maciState; diff --git a/packages/contracts/ts/genSignUpTree.ts b/packages/contracts/ts/genSignUpTree.ts new file mode 100644 index 0000000000..598c47b795 --- /dev/null +++ b/packages/contracts/ts/genSignUpTree.ts @@ -0,0 +1,69 @@ +/* eslint-disable no-underscore-dangle */ +import { LeanIMT, LeanIMTHashFunction } from "@zk-kit/lean-imt"; +import { hashLeanIMT } from "maci-crypto"; +import { PubKey, StateLeaf, blankStateLeaf, blankStateLeafHash } from "maci-domainobjs"; + +import { assert } from "console"; + +import { MACI__factory as MACIFactory } from "../typechain-types"; + +import { IGenSignUpTreeArgs, IGenSignUpTree } from "./types"; +import { sleep } from "./utils"; + +/** + * Generate a State tree object from the events of a MACI smart contracts + * @param provider - the ethereum provider + * @param address - the address of the MACI contract + * @param fromBlock - the block number from which to start fetching events + * @param blocksPerRequest - the number of blocks to fetch in each request + * @param endBlock - the block number at which to stop fetching events + * @param sleepAmount - the amount of time to sleep between each request + * @returns State tree + */ +export const genSignUpTree = async ({ + provider, + address, + fromBlock = 0, + blocksPerRequest = 50, + endBlock, + sleepAmount, +}: IGenSignUpTreeArgs): Promise => { + const lastBlock = endBlock || (await provider.getBlockNumber()); + + const maciContract = MACIFactory.connect(address, provider); + const signUpTree = new LeanIMT(hashLeanIMT as LeanIMTHashFunction); + signUpTree.insert(blankStateLeafHash); + const stateLeaves: StateLeaf[] = [blankStateLeaf]; + + // Fetch event logs in batches (lastBlock inclusive) + for (let i = fromBlock; i <= lastBlock; i += blocksPerRequest + 1) { + // the last block batch will be either current iteration block + blockPerRequest + // or the end block if it is set + const toBlock = i + blocksPerRequest >= lastBlock ? lastBlock : i + blocksPerRequest; + + // eslint-disable-next-line no-await-in-loop + const signUpLogs = await maciContract.queryFilter(maciContract.filters.SignUp(), i, toBlock); + signUpLogs.forEach((event) => { + assert(!!event); + const pubKeyX = event.args._userPubKeyX; + const pubKeyY = event.args._userPubKeyY; + const voiceCreditBalance = event.args._voiceCreditBalance; + const timestamp = event.args._timestamp; + + const pubKey = new PubKey([pubKeyX, pubKeyY]); + const stateLeaf = new StateLeaf(pubKey, voiceCreditBalance, timestamp); + + stateLeaves.push(stateLeaf); + signUpTree.insert(event.args._stateLeaf); + }); + + if (sleepAmount) { + // eslint-disable-next-line no-await-in-loop + await sleep(sleepAmount); + } + } + return { + signUpTree, + stateLeaves, + }; +}; diff --git a/packages/contracts/ts/index.ts b/packages/contracts/ts/index.ts index abf906236b..1de6bcd054 100644 --- a/packages/contracts/ts/index.ts +++ b/packages/contracts/ts/index.ts @@ -17,6 +17,7 @@ export { } from "./deploy"; export { genMaciStateFromContract } from "./genMaciState"; export { genEmptyBallotRoots } from "./genEmptyBallotRoots"; +export { genSignUpTree } from "./genSignUpTree"; export { formatProofForVerifierContract, getDefaultSigner, getDefaultNetwork, getSigners } from "./utils"; export { EMode } from "./constants"; export { EDeploySteps } from "../tasks/helpers/constants"; @@ -38,5 +39,5 @@ export { } from "../tasks/helpers/types"; export { linkPoseidonLibraries } from "../tasks/helpers/abi"; -export type { IVerifyingKeyStruct, SnarkProof, Groth16Proof, Proof } from "./types"; +export type { IVerifyingKeyStruct, SnarkProof, Groth16Proof, Proof, IGenSignUpTree } from "./types"; export * from "../typechain-types"; diff --git a/packages/contracts/ts/types.ts b/packages/contracts/ts/types.ts index 81c0ecd4f2..61435de2f3 100644 --- a/packages/contracts/ts/types.ts +++ b/packages/contracts/ts/types.ts @@ -1,3 +1,5 @@ +import { LeanIMT } from "@zk-kit/lean-imt"; + import type { ConstantInitialVoiceCreditProxy, FreeForAllGatekeeper, @@ -10,9 +12,9 @@ import type { PoseidonT6, VkRegistry, } from "../typechain-types"; -import type { BigNumberish, ContractFactory, Signer } from "ethers"; +import type { BigNumberish, Provider, Signer, ContractFactory } from "ethers"; import type { CircuitInputs } from "maci-core"; -import type { Message, PubKey } from "maci-domainobjs"; +import type { Message, PubKey, StateLeaf } from "maci-domainobjs"; import type { PublicSignals } from "snarkjs"; /** @@ -106,11 +108,13 @@ export interface Action { message: Message; voiceCreditBalance: number; timestamp: number; + nullifier: bigint; + newVoiceCreditBalance: bigint; stateIndex: number; numSrQueueOps: number; pollId: bigint; pollAddr: string; - stateRoot: bigint; + stateLeaf: bigint; messageRoot: bigint; }>; blockNumber: number; @@ -185,3 +189,53 @@ export interface IDeployedMaci { poseidonT6: string; }; } + +/** + * An interface that represents arguments of generation sign up tree and state leaves + */ +export interface IGenSignUpTreeArgs { + /** + * The etherum provider + */ + provider: Provider; + + /** + * The address of MACI contract + */ + address: string; + + /** + * The block number from which to start fetching events + */ + fromBlock?: number; + + /** + * The number of blocks to fetch in each request + */ + blocksPerRequest?: number; + + /** + * The block number at which to stop fetching events + */ + endBlock?: number; + + /** + * The amount of time to sleep between each request + */ + sleepAmount?: number; +} + +/** + * An interface that represents sign up tree and state leaves + */ +export interface IGenSignUpTree { + /** + * Sign up tree + */ + signUpTree: LeanIMT; + + /** + * State leaves + */ + stateLeaves: StateLeaf[]; +} diff --git a/packages/core/package.json b/packages/core/package.json index 489983b42e..a10cf36a4a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -23,7 +23,8 @@ }, "dependencies": { "maci-crypto": "^2.5.0", - "maci-domainobjs": "^2.5.0" + "maci-domainobjs": "^2.5.0", + "@zk-kit/lean-imt": "^2.2.1" }, "devDependencies": { "@types/chai": "^4.3.11", diff --git a/packages/core/ts/MaciState.ts b/packages/core/ts/MaciState.ts index e62e840656..82df27cda7 100644 --- a/packages/core/ts/MaciState.ts +++ b/packages/core/ts/MaciState.ts @@ -1,3 +1,4 @@ +import { hash4, IncrementalQuinTree } from "maci-crypto"; import { type PubKey, type Keypair, StateLeaf, blankStateLeaf } from "maci-domainobjs"; import type { IJsonMaciState, IJsonPoll, IMaciState, TreeDepths } from "./utils/types"; @@ -18,6 +19,9 @@ export class MaciState implements IMaciState { // how deep the state tree is stateTreeDepth: number; + // state tree + stateTree?: IncrementalQuinTree; + numSignUps = 0; // to keep track if a poll is currently being processed @@ -44,13 +48,18 @@ export class MaciState implements IMaciState { * @param pubKey - The public key of the user. * @param initialVoiceCreditBalance - The initial voice credit balance of the user. * @param timestamp - The timestamp of the sign-up. + * @param stateLeaf - The hash state leaf. * @returns The index of the newly signed-up user in the state tree. */ - signUp(pubKey: PubKey, initialVoiceCreditBalance: bigint, timestamp: bigint): number { + signUp(pubKey: PubKey, initialVoiceCreditBalance: bigint, timestamp: bigint, stateLeaf?: bigint): number { this.numSignUps += 1; - const stateLeaf = new StateLeaf(pubKey, initialVoiceCreditBalance, timestamp); + const stateLeafObj = new StateLeaf(pubKey, initialVoiceCreditBalance, timestamp); - return this.stateLeaves.push(stateLeaf.copy()) - 1; + const pubKeyAsArray = pubKey.asArray(); + const stateLeafHash = + stateLeaf || hash4([pubKeyAsArray[0], pubKeyAsArray[1], initialVoiceCreditBalance, timestamp]); + this.stateTree?.insert(stateLeafHash); + return this.stateLeaves.push(stateLeafObj.copy()) - 1; } /** diff --git a/packages/core/ts/Poll.ts b/packages/core/ts/Poll.ts index 91f5800be2..c647274cf6 100644 --- a/packages/core/ts/Poll.ts +++ b/packages/core/ts/Poll.ts @@ -1,3 +1,4 @@ +import { LeanIMT, LeanIMTHashFunction } from "@zk-kit/lean-imt"; import { IncrementalQuinTree, genRandomSalt, @@ -9,6 +10,8 @@ import { stringifyBigInts, genTreeCommitment, hash2, + poseidon, + hashLeanIMT, } from "maci-crypto"; import { PCommand, @@ -17,8 +20,8 @@ import { PubKey, PrivKey, Message, + StateLeaf, blankStateLeaf, - type StateLeaf, type IMessageContractParams, type IJsonPCommand, blankStateLeafHash, @@ -26,7 +29,6 @@ import { import assert from "assert"; -import type { MaciState } from "./MaciState"; import type { CircuitInputs, TreeDepths, @@ -36,7 +38,10 @@ import type { IProcessMessagesOutput, ITallyCircuitInputs, IProcessMessagesCircuitInputs, -} from "./utils/types"; + IPollJoiningCircuitInputs, + IJoiningCircuitArgs, +} from "./index"; +import type { MaciState } from "./MaciState"; import type { PathElements } from "maci-crypto"; import { STATE_TREE_ARITY, VOTE_OPTION_TREE_ARITY } from "./utils/constants"; @@ -78,7 +83,7 @@ export class Poll implements IPoll { stateLeaves: StateLeaf[] = [blankStateLeaf]; - stateTree?: IncrementalQuinTree; + stateTree?: LeanIMT; // For message processing numBatchesProcessed = 0; @@ -118,6 +123,15 @@ export class Poll implements IPoll { // batch chain hashes batchHashes = [NOTHING_UP_MY_SLEEVE]; + // Poll state tree leaves + pollStateLeaves: StateLeaf[] = [blankStateLeaf]; + + // Poll state tree + pollStateTree?: IncrementalQuinTree; + + // Poll voting nullifier + pollNullifiers: Map; + // how many users signed up private numSignups = 0n; @@ -149,6 +163,8 @@ export class Poll implements IPoll { this.actualStateTreeDepth = maciStateRef.stateTreeDepth; this.currentMessageBatchIndex = 0; + this.pollNullifiers = new Map(); + this.tallyResult = new Array(this.maxVoteOptions).fill(0n) as bigint[]; this.perVOSpentVoiceCredits = new Array(this.maxVoteOptions).fill(0n) as bigint[]; @@ -157,6 +173,33 @@ export class Poll implements IPoll { this.ballots.push(this.emptyBallot); } + /** + * Check if user has already joined the poll by checking if the nullifier is registered + */ + hasJoined = (nullifier: bigint): boolean => this.pollNullifiers.get(nullifier) != null; + + /** + * Join the anonymous user to the Poll (to the tree) + * @param nullifier - Hashed private key used as nullifier + * @param pubKey - The poll public key. + * @param newVoiceCreditBalance - New voice credit balance of the user. + * @param timestamp - The timestamp of the sign-up. + * @returns The index of added state leaf + */ + joinPoll = (nullifier: bigint, pubKey: PubKey, newVoiceCreditBalance: bigint, timestamp: bigint): number => { + const stateLeaf = new StateLeaf(pubKey, newVoiceCreditBalance, timestamp); + + if (this.hasJoined(nullifier)) { + throw new Error("UserAlreadyJoined"); + } + + this.pollNullifiers.set(nullifier, true); + this.pollStateLeaves.push(stateLeaf.copy()); + this.pollStateTree?.insert(stateLeaf.hash()); + + return this.pollStateLeaves.length - 1; + }; + /** * Update a Poll with data from MaciState. * This is the step where we copy the state from the MaciState instance, @@ -178,13 +221,24 @@ export class Poll implements IPoll { // ensure we have the correct actual state tree depth value this.actualStateTreeDepth = Math.max(1, Math.ceil(Math.log2(Number(this.numSignups)))); - // create a new state tree - this.stateTree = new IncrementalQuinTree(this.actualStateTreeDepth, blankStateLeafHash, STATE_TREE_ARITY, hash2); + this.stateTree = new LeanIMT(hashLeanIMT as LeanIMTHashFunction); // add all leaves this.stateLeaves.forEach((stateLeaf) => { this.stateTree?.insert(stateLeaf.hash()); }); + // create a poll state tree + this.pollStateTree = new IncrementalQuinTree( + this.actualStateTreeDepth, + blankStateLeafHash, + STATE_TREE_ARITY, + hash2, + ); + + this.pollStateLeaves.forEach((stateLeaf) => { + this.pollStateTree?.insert(stateLeaf.hash()); + }); + // Create as many ballots as state leaves this.emptyBallotHash = this.emptyBallot.hash(); this.ballotTree = new IncrementalQuinTree(this.stateTreeDepth, this.emptyBallotHash, STATE_TREE_ARITY, hash2); @@ -218,13 +272,13 @@ export class Poll implements IPoll { if ( stateLeafIndex >= BigInt(this.ballots.length) || stateLeafIndex < 1n || - stateLeafIndex >= BigInt(this.stateTree?.nextIndex || -1) + stateLeafIndex >= BigInt(this.pollStateTree?.nextIndex || -1) ) { throw new ProcessMessageError(ProcessMessageErrors.InvalidStateLeafIndex); } // The user to update (or not) - const stateLeaf = this.stateLeaves[Number(stateLeafIndex)]; + const stateLeaf = this.pollStateLeaves[Number(stateLeafIndex)]; // The ballot to update (or not) const ballot = this.ballots[Number(stateLeafIndex)]; @@ -283,7 +337,7 @@ export class Poll implements IPoll { // calculate the path elements for the state tree given the original state tree (before any changes) // changes could effectively be made by this new vote - either a key change or vote change // would result in a different state leaf - const originalStateLeafPathElements = this.stateTree?.genProof(Number(stateLeafIndex)).pathElements; + const originalStateLeafPathElements = this.pollStateTree?.genProof(Number(stateLeafIndex)).pathElements; // calculate the path elements for the ballot tree given the original ballot tree (before any changes) // changes could effectively be made by this new ballot const originalBallotPathElements = this.ballotTree?.genProof(Number(stateLeafIndex)).pathElements; @@ -372,6 +426,82 @@ export class Poll implements IPoll { } }; + /** + * Create circuit input for pollJoining + * @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 + */ + joiningCircuitInputs = ({ + maciPrivKey, + stateLeafIndex, + credits, + pollPrivKey, + pollPubKey, + }: IJoiningCircuitArgs): IPollJoiningCircuitInputs => { + // Get the state leaf on the index position + const stateLeaf = this.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 } = this.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 < this.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 = this.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; + }; + /** * Pad last unclosed batch */ @@ -494,10 +624,10 @@ export class Poll implements IPoll { currentBallotsPathElements.unshift(r.originalBallotPathElements!); // update the state leaves with the new state leaf (result of processing the message) - this.stateLeaves[index] = r.newStateLeaf!.copy(); + this.pollStateLeaves[index] = r.newStateLeaf!.copy(); // we also update the state tree with the hash of the new state leaf - this.stateTree?.update(index, r.newStateLeaf!.hash()); + this.pollStateTree?.update(index, r.newStateLeaf!.hash()); // store the new ballot this.ballots[index] = r.newBallot!; @@ -532,9 +662,9 @@ export class Poll implements IPoll { const stateLeafIndex = command.stateIndex; // if the state leaf index is valid then use it - if (stateLeafIndex < this.stateLeaves.length) { - currentStateLeaves.unshift(this.stateLeaves[Number(stateLeafIndex)].copy()); - currentStateLeavesPathElements.unshift(this.stateTree!.genProof(Number(stateLeafIndex)).pathElements); + if (stateLeafIndex < this.pollStateLeaves.length) { + currentStateLeaves.unshift(this.pollStateLeaves[Number(stateLeafIndex)].copy()); + currentStateLeavesPathElements.unshift(this.pollStateTree!.genProof(Number(stateLeafIndex)).pathElements); // copy the ballot const ballot = this.ballots[Number(stateLeafIndex)].copy(); @@ -584,8 +714,8 @@ export class Poll implements IPoll { } } else { // just use state leaf index 0 - currentStateLeaves.unshift(this.stateLeaves[0].copy()); - currentStateLeavesPathElements.unshift(this.stateTree!.genProof(0).pathElements); + currentStateLeaves.unshift(this.pollStateLeaves[0].copy()); + currentStateLeavesPathElements.unshift(this.pollStateTree!.genProof(0).pathElements); currentBallots.unshift(this.ballots[0].copy()); currentBallotsPathElements.unshift(this.ballotTree!.genProof(0).pathElements); @@ -609,8 +739,8 @@ export class Poll implements IPoll { } } else { // Since we don't have a command at that position, use a blank state leaf - currentStateLeaves.unshift(this.stateLeaves[0].copy()); - currentStateLeavesPathElements.unshift(this.stateTree!.genProof(0).pathElements); + currentStateLeaves.unshift(this.pollStateLeaves[0].copy()); + currentStateLeavesPathElements.unshift(this.pollStateTree!.genProof(0).pathElements); // since the command is invliad we use the blank ballot currentBallots.unshift(this.ballots[0].copy()); currentBallotsPathElements.unshift(this.ballotTree!.genProof(0).pathElements); @@ -659,7 +789,7 @@ export class Poll implements IPoll { // store the salt in the circuit inputs circuitInputs.newSbSalt = newSbSalt; - const newStateRoot = this.stateTree!.root; + const newStateRoot = this.pollStateTree!.root; const newBallotRoot = this.ballotTree!.root; // create a commitment to the state and ballot tree roots // this will be the hash of the roots with a salt @@ -734,7 +864,7 @@ export class Poll implements IPoll { encPubKeys = encPubKeys.slice((index - 1) * messageBatchSize, index * messageBatchSize); // cache tree roots - const currentStateRoot = this.stateTree!.root; + const currentStateRoot = this.pollStateTree!.root; const currentBallotRoot = this.ballotTree!.root; // calculate the current state and ballot root // commitment which is the hash of the state tree @@ -771,7 +901,7 @@ export class Poll implements IPoll { * @returns The state leaves and ballots of the poll */ processAllMessages = (): { stateLeaves: StateLeaf[]; ballots: Ballot[] } => { - const stateLeaves = this.stateLeaves.map((x) => x.copy()); + const stateLeaves = this.pollStateLeaves.map((x) => x.copy()); const ballots = this.ballots.map((x) => x.copy()); // process all messages in one go (batch by batch but without manual intervention) @@ -927,7 +1057,7 @@ export class Poll implements IPoll { ]); // cache vars - const stateRoot = this.stateTree!.root; + const stateRoot = this.pollStateTree!.root; const ballotRoot = this.ballotTree!.root; const sbSalt = this.sbSalts[this.currentMessageBatchIndex]; const sbCommitment = hash3([stateRoot, ballotRoot, sbSalt]); @@ -1064,7 +1194,7 @@ export class Poll implements IPoll { const newTallyCommitment = hashLeftRight(newResultsCommitment, newSpentVoiceCreditsCommitment); // cache vars - const stateRoot = this.stateTree!.root; + const stateRoot = this.pollStateTree!.root; const ballotRoot = this.ballotTree!.root; const sbSalt = this.sbSalts[this.currentMessageBatchIndex]; const sbCommitment = hash3([stateRoot, ballotRoot, sbSalt]); @@ -1178,6 +1308,7 @@ export class Poll implements IPoll { ); copied.stateLeaves = this.stateLeaves.map((x) => x.copy()); + copied.pollStateLeaves = this.pollStateLeaves.map((x) => x.copy()); copied.messages = this.messages.map((x) => x.copy()); copied.commands = this.commands.map((x) => x.copy()); copied.ballots = this.ballots.map((x) => x.copy()); @@ -1274,6 +1405,7 @@ export class Poll implements IPoll { encPubKeys: this.encPubKeys.map((encPubKey) => encPubKey.serialize()), currentMessageBatchIndex: this.currentMessageBatchIndex, stateLeaves: this.stateLeaves.map((leaf) => leaf.toJSON()), + pollStateLeaves: this.pollStateLeaves.map((leaf) => leaf.toJSON()), results: this.tallyResult.map((result) => result.toString()), numBatchesProcessed: this.numBatchesProcessed, numSignups: this.numSignups.toString(), @@ -1299,6 +1431,7 @@ export class Poll implements IPoll { ); // set all properties + poll.pollStateLeaves = json.pollStateLeaves.map((leaf) => StateLeaf.fromJSON(leaf)); poll.ballots = json.ballots.map((ballot) => Ballot.fromJSON(ballot)); poll.encPubKeys = json.encPubKeys.map((key: string) => PubKey.deserialize(key)); poll.messages = json.messages.map((message) => Message.fromJSON(message as IMessageContractParams)); diff --git a/packages/core/ts/__tests__/Poll.test.ts b/packages/core/ts/__tests__/Poll.test.ts index af7de7739c..a8378c453d 100644 --- a/packages/core/ts/__tests__/Poll.test.ts +++ b/packages/core/ts/__tests__/Poll.test.ts @@ -1,4 +1,5 @@ import { expect } from "chai"; +import { poseidon } from "maci-crypto"; import { PCommand, Keypair, StateLeaf, PrivKey, Ballot } from "maci-domainobjs"; import { MaciState } from "../MaciState"; @@ -31,27 +32,31 @@ describe("Poll", function test() { const user1Keypair = new Keypair(); // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); + maciState.signUp(user1Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); // copy the state from the MaciState ref poll.updatePoll(BigInt(maciState.stateLeaves.length)); + const { privKey } = user1Keypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const stateIndex = poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp); + it("should throw if a message has an invalid state index", () => { const command = new PCommand( // invalid state index as it is one more than the number of state leaves - BigInt(user1StateIndex + 1), - user1Keypair.pubKey, + BigInt(stateIndex + 1), + pollPubKey, 0n, 1n, 0n, BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -64,9 +69,9 @@ describe("Poll", function test() { }); it("should throw if a message has an invalid nonce", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 0n, 0n, BigInt(pollId)); + const command = new PCommand(BigInt(stateIndex), pollPubKey, 0n, 0n, 0n, BigInt(pollId)); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -80,7 +85,7 @@ describe("Poll", function test() { }); it("should throw if a message has an invalid signature", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 0n, 0n, BigInt(pollId)); + const command = new PCommand(BigInt(stateIndex), pollPubKey, 0n, 0n, 0n, BigInt(pollId)); const signature = command.sign(new PrivKey(0n)); const ecdhKeypair = new Keypair(); @@ -96,8 +101,8 @@ describe("Poll", function test() { it("should throw if a message consumes more than the available voice credits for a user", () => { const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, + BigInt(stateIndex), + pollPubKey, 0n, // voice credits spent would be this value ** this value BigInt(Math.sqrt(Number.parseInt(voiceCreditBalance.toString(), 10)) + 1), @@ -105,7 +110,7 @@ describe("Poll", function test() { BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -120,8 +125,8 @@ describe("Poll", function test() { it("should throw if a message has an invalid vote option index (>= max vote options)", () => { const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, + BigInt(stateIndex), + pollPubKey, BigInt(maxValues.maxVoteOptions), // voice credits spent would be this value ** this value 1n, @@ -129,7 +134,7 @@ describe("Poll", function test() { BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -143,9 +148,9 @@ describe("Poll", function test() { }); it("should throw if a message has an invalid vote option index (< 0)", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, -1n, 1n, 1n, BigInt(pollId)); + const command = new PCommand(BigInt(stateIndex), pollPubKey, -1n, 1n, 1n, BigInt(pollId)); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -159,9 +164,9 @@ describe("Poll", function test() { }); it("should throw when passed a message that cannot be decrypted (wrong encPubKey)", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 1n, BigInt(pollId)); + const command = new PCommand(BigInt(stateIndex), pollPubKey, 0n, 1n, 1n, BigInt(pollId)); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(new Keypair().privKey, coordinatorKeypair.pubKey); @@ -170,17 +175,17 @@ describe("Poll", function test() { poll.publishMessage(message, ecdhKeypair.pubKey); expect(() => { - poll.processMessage(message, user1Keypair.pubKey); + poll.processMessage(message, pollPubKey); }).to.throw("failed decryption due to either wrong encryption public key or corrupted ciphertext"); }); it("should throw when passed a corrupted message", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 1n, BigInt(pollId)); + const command = new PCommand(BigInt(stateIndex), pollPubKey, 0n, 1n, 1n, BigInt(pollId)); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(user1Keypair.privKey, coordinatorKeypair.pubKey); + const sharedKey = Keypair.genEcdhSharedKey(pollPrivKey, coordinatorKeypair.pubKey); const message = command.encrypt(signature, sharedKey); poll.publishMessage(message, ecdhKeypair.pubKey); @@ -188,22 +193,22 @@ describe("Poll", function test() { message.data[0] = 0n; expect(() => { - poll.processMessage(message, user1Keypair.pubKey); + poll.processMessage(message, pollPubKey); }).to.throw("failed decryption due to either wrong encryption public key or corrupted ciphertext"); }); it("should throw when going over the voice credit limit (non qv)", () => { const command = new PCommand( // invalid state index as it is one more than the number of state leaves - BigInt(user1StateIndex), - user1Keypair.pubKey, + BigInt(stateIndex), + pollPubKey, 0n, voiceCreditBalance + 1n, 1n, BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -218,15 +223,15 @@ describe("Poll", function test() { it("should work when submitting a valid message (voteWeight === voiceCreditBalance and non qv)", () => { const command = new PCommand( // invalid state index as it is one more than the number of state leaves - BigInt(user1StateIndex), - user1Keypair.pubKey, + BigInt(stateIndex), + pollPubKey, 0n, voiceCreditBalance, 1n, BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -252,11 +257,15 @@ describe("Poll", function test() { const user1Keypair = new Keypair(); // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); + maciState.signUp(user1Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); + + const { privKey } = user1Keypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const stateIndex = poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp); it("should throw if the state has not been copied prior to calling processMessages", () => { const tmpPoll = maciState.deployPoll( @@ -275,15 +284,15 @@ describe("Poll", function test() { it("should succeed even if we send an invalid message", () => { const command = new PCommand( // we only signed up one user so the state index is invalid - BigInt(user1StateIndex + 1), - user1Keypair.pubKey, + BigInt(stateIndex + 1), + pollPubKey, 0n, 1n, 0n, BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -325,26 +334,30 @@ describe("Poll", function test() { const user1Keypair = new Keypair(); // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); + maciState.signUp(user1Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); poll.updatePoll(BigInt(maciState.stateLeaves.length)); + const { privKey } = user1Keypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const stateIndex = poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp); + it("it should succeed even if send an invalid message", () => { const command = new PCommand( // we only signed up one user so the state index is invalid - BigInt(user1StateIndex + 1), - user1Keypair.pubKey, + BigInt(stateIndex + 1), + pollPubKey, 0n, 1n, 0n, BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -359,9 +372,9 @@ describe("Poll", function test() { }); it("should return the correct state leaves and ballots", () => { - const command = new PCommand(BigInt(user1StateIndex + 1), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); + const command = new PCommand(BigInt(stateIndex + 1), pollPubKey, 0n, 1n, 0n, BigInt(pollId)); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -374,14 +387,16 @@ describe("Poll", function test() { const { stateLeaves, ballots } = poll.processAllMessages(); - stateLeaves.forEach((leaf: StateLeaf, index: number) => expect(leaf.equals(poll.stateLeaves[index])).to.eq(true)); + stateLeaves.forEach((leaf: StateLeaf, index: number) => + expect(leaf.equals(poll.pollStateLeaves[index])).to.eq(true), + ); ballots.forEach((ballot: Ballot, index: number) => expect(ballot.equals(poll.ballots[index])).to.eq(true)); }); it("should have processed all messages", () => { - const command = new PCommand(BigInt(user1StateIndex + 1), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); + const command = new PCommand(BigInt(stateIndex + 1), pollPubKey, 0n, 1n, 0n, BigInt(pollId)); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -415,33 +430,32 @@ describe("Poll", function test() { const user2Keypair = new Keypair(); // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); + maciState.signUp(user1Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); - const user2StateIndex = maciState.signUp( - user2Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); + maciState.signUp(user2Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); poll.updatePoll(BigInt(maciState.stateLeaves.length)); + const { privKey: privKey1 } = user1Keypair; + const { privKey: pollPrivKey1, pubKey: pollPubKey1 } = new Keypair(); + + const nullifier1 = poseidon([BigInt(privKey1.rawPrivKey.toString())]); + const timestamp1 = BigInt(1); + + const stateIndex1 = poll.joinPoll(nullifier1, pollPubKey1, voiceCreditBalance, timestamp1); + + const { privKey: privKey2 } = user2Keypair; + const { privKey: pollPrivKey2, pubKey: pollPubKey2 } = new Keypair(); + + const nullifier2 = poseidon([BigInt(privKey2.rawPrivKey.toString())]); + const timestamp2 = BigInt(1); + const voteWeight = 5n; const voteOption = 0n; - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - voteOption, - voteWeight, - 1n, - BigInt(pollId), - ); + const command = new PCommand(BigInt(stateIndex1), pollPubKey1, voteOption, voteWeight, 1n, BigInt(pollId)); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey1); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -482,19 +496,21 @@ describe("Poll", function test() { const secondPoll = maciState.polls.get(secondPollId)!; secondPoll.updatePoll(BigInt(maciState.stateLeaves.length)); + const stateIndex2 = secondPoll.joinPoll(nullifier2, pollPubKey2, voiceCreditBalance, timestamp2); + const secondVoteWeight = 10n; const secondVoteOption = 1n; const secondCommand = new PCommand( - BigInt(user2StateIndex), - user2Keypair.pubKey, + BigInt(stateIndex2), + pollPubKey2, secondVoteOption, secondVoteWeight, 1n, secondPollId, ); - const secondSignature = secondCommand.sign(user2Keypair.privKey); + const secondSignature = secondCommand.sign(pollPrivKey2); const secondEcdhKeypair = new Keypair(); const secondSharedKey = Keypair.genEcdhSharedKey(secondEcdhKeypair.privKey, coordinatorKeypair.pubKey); diff --git a/packages/core/ts/__tests__/e2e.test.ts b/packages/core/ts/__tests__/e2e.test.ts index 6822225f8f..84fd0e3e6e 100644 --- a/packages/core/ts/__tests__/e2e.test.ts +++ b/packages/core/ts/__tests__/e2e.test.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { hash5, IncrementalQuinTree, hash2 } from "maci-crypto"; +import { hash5, IncrementalQuinTree, hash2, poseidon } from "maci-crypto"; import { PCommand, Keypair, StateLeaf, blankStateLeafHash } from "maci-domainobjs"; import { MaciState } from "../MaciState"; @@ -22,8 +22,6 @@ describe("MaciState/Poll e2e", function test() { describe("key changes", () => { const user1Keypair = new Keypair(); const user2Keypair = new Keypair(); - const user1SecondKeypair = new Keypair(); - const user2SecondKeypair = new Keypair(); let pollId: bigint; let user1StateIndex: number; let user2StateIndex: number; @@ -34,21 +32,29 @@ describe("MaciState/Poll e2e", function test() { const user1NewVoteWeight = 5n; const user2NewVoteWeight = 7n; + const { privKey: privKey1 } = user1Keypair; + const { privKey: pollPrivKey1, pubKey: pollPubKey1 } = new Keypair(); + + const nullifier1 = poseidon([BigInt(privKey1.rawPrivKey.toString())]); + const timestamp1 = BigInt(1); + + const { privKey: privKey2 } = user2Keypair; + const { privKey: pollPrivKey2, pubKey: pollPubKey2 } = new Keypair(); + + const nullifier2 = poseidon([BigInt(privKey2.rawPrivKey.toString())]); + const timestamp2 = BigInt(1); + + const { pubKey: pollPubKey1Second } = new Keypair(); + + const { pubKey: pollPubKey2Second } = new Keypair(); + describe("only user 1 changes key", () => { const maciState: MaciState = new MaciState(STATE_TREE_DEPTH); before(() => { // Sign up - user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - user2StateIndex = maciState.signUp( - user2Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); + maciState.signUp(user1Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); + maciState.signUp(user2Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); // deploy a poll pollId = maciState.deployPoll( @@ -61,19 +67,19 @@ describe("MaciState/Poll e2e", function test() { maciState.polls.get(pollId)?.updatePoll(BigInt(maciState.stateLeaves.length)); }); - it("should submit a vote for each user", () => { const poll = maciState.polls.get(pollId)!; + user1StateIndex = poll.joinPoll(nullifier1, pollPubKey1, voiceCreditBalance, timestamp1); const command1 = new PCommand( BigInt(user1StateIndex), - user1Keypair.pubKey, + pollPubKey1, user1VoteOptionIndex, user1VoteWeight, 1n, BigInt(pollId), ); - const signature1 = command1.sign(user1Keypair.privKey); + const signature1 = command1.sign(pollPrivKey1); const ecdhKeypair1 = new Keypair(); const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); @@ -81,16 +87,17 @@ describe("MaciState/Poll e2e", function test() { const message1 = command1.encrypt(signature1, sharedKey1); poll.publishMessage(message1, ecdhKeypair1.pubKey); + user2StateIndex = poll.joinPoll(nullifier2, pollPubKey2, voiceCreditBalance, timestamp2); const command2 = new PCommand( BigInt(user2StateIndex), - user2Keypair.pubKey, + pollPubKey2, user2VoteOptionIndex, user2VoteWeight, 1n, BigInt(pollId), ); - const signature2 = command2.sign(user2Keypair.privKey); + const signature2 = command2.sign(pollPrivKey2); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -103,14 +110,14 @@ describe("MaciState/Poll e2e", function test() { const poll = maciState.polls.get(pollId)!; const command = new PCommand( BigInt(user1StateIndex), - user1SecondKeypair.pubKey, + pollPubKey1Second, user1VoteOptionIndex, user1NewVoteWeight, 1n, BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey1); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -129,10 +136,10 @@ describe("MaciState/Poll e2e", function test() { it("should confirm that the user key pair was changed (user's 2 one has not)", () => { const poll = maciState.polls.get(pollId)!; - const stateLeaf1 = poll.stateLeaves[user1StateIndex]; - const stateLeaf2 = poll.stateLeaves[user2StateIndex]; - expect(stateLeaf1.pubKey.equals(user1SecondKeypair.pubKey)).to.eq(true); - expect(stateLeaf2.pubKey.equals(user2Keypair.pubKey)).to.eq(true); + const stateLeaf1 = poll.pollStateLeaves[user1StateIndex]; + const stateLeaf2 = poll.pollStateLeaves[user2StateIndex]; + expect(stateLeaf1.pubKey.equals(pollPubKey1Second)).to.eq(true); + expect(stateLeaf2.pubKey.equals(pollPubKey2)).to.eq(true); }); }); @@ -142,16 +149,8 @@ describe("MaciState/Poll e2e", function test() { before(() => { // Sign up - user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - user2StateIndex = maciState.signUp( - user2Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); + maciState.signUp(user1Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); + maciState.signUp(user2Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); // deploy a poll pollId = maciState.deployPoll( @@ -166,16 +165,17 @@ describe("MaciState/Poll e2e", function test() { poll.updatePoll(BigInt(maciState.stateLeaves.length)); }); it("should submit a vote for each user", () => { + user1StateIndex = poll.joinPoll(nullifier1, pollPubKey1, voiceCreditBalance, timestamp1); const command1 = new PCommand( BigInt(user1StateIndex), - user1Keypair.pubKey, + pollPubKey1, user1VoteOptionIndex, user1VoteWeight, 1n, BigInt(pollId), ); - const signature1 = command1.sign(user1Keypair.privKey); + const signature1 = command1.sign(pollPrivKey1); const ecdhKeypair1 = new Keypair(); const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); @@ -183,16 +183,17 @@ describe("MaciState/Poll e2e", function test() { const message1 = command1.encrypt(signature1, sharedKey1); poll.publishMessage(message1, ecdhKeypair1.pubKey); + user2StateIndex = poll.joinPoll(nullifier2, pollPubKey2, voiceCreditBalance, timestamp2); const command2 = new PCommand( BigInt(user2StateIndex), - user2Keypair.pubKey, + pollPubKey2, user2VoteOptionIndex, user2VoteWeight, 1n, BigInt(pollId), ); - const signature2 = command2.sign(user2Keypair.privKey); + const signature2 = command2.sign(pollPrivKey2); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -204,14 +205,14 @@ describe("MaciState/Poll e2e", function test() { it("user1 sends a keychange message with a new vote", () => { const command = new PCommand( BigInt(user1StateIndex), - user1SecondKeypair.pubKey, + pollPubKey1Second, user1VoteOptionIndex, user1NewVoteWeight, 1n, BigInt(pollId), ); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey1); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -223,14 +224,14 @@ describe("MaciState/Poll e2e", function test() { it("user2 sends a keychange message with a new vote", () => { const command = new PCommand( BigInt(user2StateIndex), - user2SecondKeypair.pubKey, + pollPubKey2Second, user2VoteOptionIndex, user2NewVoteWeight, 1n, BigInt(pollId), ); - const signature = command.sign(user2Keypair.privKey); + const signature = command.sign(pollPrivKey2); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -247,10 +248,10 @@ describe("MaciState/Poll e2e", function test() { }); it("should confirm that the users key pairs were changed", () => { - const stateLeaf1 = poll.stateLeaves[user1StateIndex]; - const stateLeaf2 = poll.stateLeaves[user2StateIndex]; - expect(stateLeaf1.pubKey.equals(user1SecondKeypair.pubKey)).to.eq(true); - expect(stateLeaf2.pubKey.equals(user2SecondKeypair.pubKey)).to.eq(true); + const pollStateLeaf1 = poll.pollStateLeaves[user1StateIndex]; + const pollStateLeaf2 = poll.pollStateLeaves[user2StateIndex]; + expect(pollStateLeaf1.pubKey.equals(pollPubKey1Second)).to.eq(true); + expect(pollStateLeaf2.pubKey.equals(pollPubKey2Second)).to.eq(true); }); }); @@ -260,11 +261,7 @@ describe("MaciState/Poll e2e", function test() { before(() => { // Sign up - user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); + maciState.signUp(user1Keypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); // deploy a poll pollId = maciState.deployPoll( @@ -280,16 +277,17 @@ describe("MaciState/Poll e2e", function test() { }); it("should submit a vote for one user in one batch", () => { + user1StateIndex = poll.joinPoll(nullifier1, pollPubKey1, voiceCreditBalance, timestamp1); const command1 = new PCommand( BigInt(user1StateIndex), - user1Keypair.pubKey, + pollPubKey1, user1VoteOptionIndex, user1VoteWeight, 1n, BigInt(pollId), ); - const signature1 = command1.sign(user1Keypair.privKey); + const signature1 = command1.sign(pollPrivKey1); const ecdhKeypair1 = new Keypair(); const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); @@ -300,16 +298,9 @@ describe("MaciState/Poll e2e", function test() { it("should fill the batch with random messages", () => { for (let i = 0; i < messageBatchSize - 1; i += 1) { - const command = new PCommand( - 1n, - user1Keypair.pubKey, - user1VoteOptionIndex, - user1VoteWeight, - 2n, - BigInt(pollId), - ); + const command = new PCommand(1n, pollPubKey1, user1VoteOptionIndex, user1VoteWeight, 2n, BigInt(pollId)); - const signature = command.sign(user1Keypair.privKey); + const signature = command.sign(pollPrivKey1); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -322,14 +313,14 @@ describe("MaciState/Poll e2e", function test() { it("should submit a new message in a new batch", () => { const command1 = new PCommand( BigInt(user1StateIndex), - user1SecondKeypair.pubKey, + pollPubKey1Second, user1VoteOptionIndex, user1NewVoteWeight, 1n, BigInt(pollId), ); - const signature1 = command1.sign(user1Keypair.privKey); + const signature1 = command1.sign(pollPrivKey1); const ecdhKeypair1 = new Keypair(); const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); @@ -345,8 +336,8 @@ describe("MaciState/Poll e2e", function test() { }); it("should confirm that the user key pair was changed", () => { - const stateLeaf1 = poll.stateLeaves[user1StateIndex]; - expect(stateLeaf1.pubKey.equals(user1SecondKeypair.pubKey)).to.eq(true); + const pollStateLeaf1 = poll.pollStateLeaves[user1StateIndex]; + expect(pollStateLeaf1.pubKey.equals(pollPubKey1Second)).to.eq(true); }); }); }); @@ -393,16 +384,17 @@ describe("MaciState/Poll e2e", function test() { }); it("Process a batch of messages (though only 1 message is in the batch)", () => { - const command = new PCommand( - BigInt(stateIndex), - userKeypair.pubKey, - voteOptionIndex, - voteWeight, - 1n, - BigInt(pollId), - ); + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + stateIndex = poll.joinPoll(nullifier, pollPubKey, voiceCreditBalance, timestamp); + + const command = new PCommand(BigInt(stateIndex), pollPubKey, voteOptionIndex, voteWeight, 1n, BigInt(pollId)); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -415,7 +407,7 @@ describe("MaciState/Poll e2e", function test() { // Check the ballot expect(poll.ballots[1].votes[Number(voteOptionIndex)].toString()).to.eq(voteWeight.toString()); // Check the state leaf in the poll - expect(poll.stateLeaves[1].voiceCreditBalance.toString()).to.eq( + expect(poll.pollStateLeaves[1].voiceCreditBalance.toString()).to.eq( (voiceCreditBalance - voteWeight * voteWeight).toString(), ); }); @@ -440,6 +432,8 @@ describe("MaciState/Poll e2e", function test() { const voteWeight = 9n; const users: Keypair[] = []; + const pollKeys: Keypair[] = []; + const stateIndices: number[] = []; before(() => { maciState = new MaciState(STATE_TREE_DEPTH); @@ -448,6 +442,9 @@ describe("MaciState/Poll e2e", function test() { const userKeypair = new Keypair(); users.push(userKeypair); + const pollKeypair = new Keypair(); + pollKeys.push(pollKeypair); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); } @@ -460,23 +457,30 @@ describe("MaciState/Poll e2e", function test() { ); poll = maciState.polls.get(pollId)!; poll.updatePoll(BigInt(maciState.stateLeaves.length)); + + for (let i = 0; i < messageBatchSize - 1; i += 1) { + const nullifier = poseidon([BigInt(pollKeys[i].privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + const stateIndex = poll.joinPoll(nullifier, pollKeys[i].pubKey, voiceCreditBalance, timestamp); + stateIndices.push(stateIndex); + } }); it("should process votes correctly", () => { // 19 valid votes for (let i = 0; i < messageBatchSize - 1; i += 1) { - const userKeypair = users[i]; + const pollKeypair = pollKeys[i]; const command = new PCommand( - BigInt(i + 1), - userKeypair.pubKey, + BigInt(stateIndices[i]), + pollKeypair.pubKey, BigInt(i), // vote option index voteWeight, 1n, BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollKeypair.privKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -488,18 +492,18 @@ describe("MaciState/Poll e2e", function test() { // 19 invalid votes for (let i = 0; i < messageBatchSize - 1; i += 1) { - const userKeypair = users[i]; + const pollKeypair = pollKeys[i]; const command = new PCommand( - BigInt(i + 1), - userKeypair.pubKey, + BigInt(stateIndices[i]), + pollKeypair.pubKey, BigInt(i), // vote option index voiceCreditBalance * 2n, // invalid vote weight 1n, BigInt(pollId), ); - const signature = command.sign(userKeypair.privKey); + const signature = command.sign(pollKeypair.privKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -532,14 +536,14 @@ describe("MaciState/Poll e2e", function test() { // Test processAllMessages const r = poll.processAllMessages(); - expect(r.stateLeaves.length).to.eq(poll.stateLeaves.length); + expect(r.stateLeaves.length).to.eq(poll.pollStateLeaves.length); expect(r.ballots.length).to.eq(poll.ballots.length); expect(r.ballots.length).to.eq(r.stateLeaves.length); for (let i = 0; i < r.stateLeaves.length; i += 1) { - expect(r.stateLeaves[i].equals(poll.stateLeaves[i])).to.eq(true); + expect(r.stateLeaves[i].equals(poll.pollStateLeaves[i])).to.eq(true); expect(r.ballots[i].equals(poll.ballots[i])).to.eq(true); } @@ -604,22 +608,23 @@ describe("MaciState/Poll e2e", function test() { const timestamp = BigInt(Math.floor(Date.now() / 1000)); const stateLeaf = new StateLeaf(userKeypair.pubKey, voiceCreditBalance, timestamp); - stateIndex = maciState.signUp(userKeypair.pubKey, voiceCreditBalance, timestamp); + maciState.signUp(userKeypair.pubKey, voiceCreditBalance, timestamp); stateTree.insert(blankStateLeafHash); stateTree.insert(stateLeaf.hash()); poll.updatePoll(BigInt(maciState.stateLeaves.length)); - const command = new PCommand( - BigInt(stateIndex), - userKeypair.pubKey, - voteOptionIndex, - voteWeight, - 1n, - BigInt(pollId), - ); + const { privKey } = userKeypair; + const { privKey: pollPrivKey, pubKey: pollPubKey } = new Keypair(); + + const pollNullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const pollTimestamp = BigInt(1); + + stateIndex = poll.joinPoll(pollNullifier, pollPubKey, voiceCreditBalance, pollTimestamp); - const signature = command.sign(userKeypair.privKey); + const command = new PCommand(BigInt(stateIndex), pollPubKey, voteOptionIndex, voteWeight, 1n, BigInt(pollId)); + + const signature = command.sign(pollPrivKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -634,7 +639,7 @@ describe("MaciState/Poll e2e", function test() { // Check the ballot expect(poll.ballots[1].votes[Number(voteOptionIndex)].toString()).to.eq(voteWeight.toString()); // Check the state leaf in the poll - expect(poll.stateLeaves[1].voiceCreditBalance.toString()).to.eq((voiceCreditBalance - voteWeight).toString()); + expect(poll.pollStateLeaves[1].voiceCreditBalance.toString()).to.eq((voiceCreditBalance - voteWeight).toString()); }); it("Tally ballots", () => { @@ -669,7 +674,15 @@ describe("MaciState/Poll e2e", function test() { const nonce = 1n; const users = testHarness.createUsers(1); - testHarness.vote(users[0], testHarness.getStateIndex(users[0]), voteOptionIndex, voteWeight, nonce); + + const { privKey } = users[0]; + const pollKeypair = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const pollStateIndex = testHarness.joinPoll(nullifier, pollKeypair.pubKey, voiceCreditBalance, timestamp); + testHarness.vote(pollKeypair, pollStateIndex, voteOptionIndex, voteWeight, nonce); testHarness.finalizePoll(); const messageLengthResult = poll.messages.length; @@ -687,7 +700,15 @@ describe("MaciState/Poll e2e", function test() { const nonce = 1n; const users = testHarness.createUsers(1); - testHarness.vote(users[0], testHarness.getStateIndex(users[0]), voteOptionIndex, voteWeight, nonce); + + const { privKey } = users[0]; + const pollKeypair = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const pollStateIndex = testHarness.joinPoll(nullifier, pollKeypair.pubKey, voiceCreditBalance, timestamp); + testHarness.vote(pollKeypair, pollStateIndex, voteOptionIndex, voteWeight, nonce); poll.updatePoll(BigInt(testHarness.maciState.stateLeaves.length)); poll.processMessages(testHarness.pollId); @@ -719,7 +740,14 @@ describe("MaciState/Poll e2e", function test() { nonce = BigInt(Math.floor(Math.random() * 100) - 50); } while (nonce === 1n); - testHarness.vote(user, testHarness.getStateIndex(user), voteOptionIndex, voteWeight, nonce); + const { privKey } = user; + const pollKeypair = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const pollStateIndex = testHarness.joinPoll(nullifier, pollKeypair.pubKey, voiceCreditBalance, timestamp); + testHarness.vote(pollKeypair, pollStateIndex, voteOptionIndex, voteWeight, nonce); }); testHarness.finalizePoll(); @@ -750,7 +778,15 @@ describe("MaciState/Poll e2e", function test() { voteWeight = BigInt(Math.floor(Math.random() * 100) - 50); } while (voteWeight >= 1n && voteWeight <= 10n); - testHarness.vote(user, testHarness.getStateIndex(user), voteOptionIndex, voteWeight, nonce); + const { privKey } = user; + const pollKeypair = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const pollStateIndex = testHarness.joinPoll(nullifier, pollKeypair.pubKey, voiceCreditBalance, timestamp); + + testHarness.vote(pollKeypair, pollStateIndex, voteOptionIndex, voteWeight, nonce); }); testHarness.finalizePoll(); @@ -774,7 +810,14 @@ describe("MaciState/Poll e2e", function test() { users.forEach((user) => { // generate a bunch of invalid votes with incorrect state tree index - testHarness.vote(user, testHarness.getStateIndex(user) + 1, voteOptionIndex, voteWeight, nonce); + const { privKey } = user; + const pollKeypair = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const pollStateIndex = testHarness.joinPoll(nullifier, pollKeypair.pubKey, voiceCreditBalance, timestamp); + testHarness.vote(pollKeypair, pollStateIndex + 1, voteOptionIndex, voteWeight, nonce); }); testHarness.finalizePoll(); @@ -795,18 +838,28 @@ describe("MaciState/Poll e2e", function test() { const users = testHarness.createUsers(2); - const { command } = testHarness.createCommand( - users[0], - testHarness.getStateIndex(users[0]), - voteOptionIndex, - voteWeight, - nonce, - ); + const { privKey: privKey1 } = users[0]; + const pollKeypair1 = new Keypair(); + + const nullifier1 = poseidon([BigInt(privKey1.rawPrivKey.toString())]); + const timestamp1 = BigInt(1); + + const pollStateIndex1 = testHarness.joinPoll(nullifier1, pollKeypair1.pubKey, voiceCreditBalance, timestamp1); + + const { command } = testHarness.createCommand(pollKeypair1, pollStateIndex1, voteOptionIndex, voteWeight, nonce); + + const { privKey: privKey2 } = users[1]; + const pollKeypair2 = new Keypair(); + + const nullifier2 = poseidon([BigInt(privKey2.rawPrivKey.toString())]); + const timestamp2 = BigInt(1); + + testHarness.joinPoll(nullifier2, pollKeypair2.pubKey, voiceCreditBalance, timestamp2); // create an invalid signature const { signature: invalidSignature } = testHarness.createCommand( - users[1], - testHarness.getStateIndex(users[0]), + pollKeypair2, + pollStateIndex1, voteOptionIndex, voteWeight, nonce, @@ -838,9 +891,17 @@ describe("MaciState/Poll e2e", function test() { const users = testHarness.createUsers(1); + const { privKey } = users[0]; + const pollKeypair = new Keypair(); + + const nullifier = poseidon([BigInt(privKey.rawPrivKey.toString())]); + const timestamp = BigInt(1); + + const pollStateIndex = testHarness.joinPoll(nullifier, pollKeypair.pubKey, voiceCreditBalance, timestamp); + const { command, signature } = testHarness.createCommand( - users[0], - testHarness.getStateIndex(users[0]), + pollKeypair, + pollStateIndex, voteOptionIndex, voteWeight, nonce, diff --git a/packages/core/ts/__tests__/utils/utils.ts b/packages/core/ts/__tests__/utils/utils.ts index cb3cec6ae8..16d2d5f493 100644 --- a/packages/core/ts/__tests__/utils/utils.ts +++ b/packages/core/ts/__tests__/utils/utils.ts @@ -70,6 +70,9 @@ export class TestHarness { return stateIndex; }; + joinPoll = (nullifier: bigint, pubKey: PubKey, newVoiceCreditBalance: bigint, timestamp: bigint): number => + this.poll.joinPoll(nullifier, pubKey, newVoiceCreditBalance, timestamp); + /** * Publishes a message to the MACI poll instance. * @param user - The keypair of the user. diff --git a/packages/core/ts/index.ts b/packages/core/ts/index.ts index e0d71333d5..199ae1cefd 100644 --- a/packages/core/ts/index.ts +++ b/packages/core/ts/index.ts @@ -2,15 +2,20 @@ export { MaciState } from "./MaciState"; export { Poll } from "./Poll"; -export { genProcessVkSig, genTallyVkSig } from "./utils/utils"; +export { genPollVkSig, genProcessVkSig, genTallyVkSig } from "./utils/utils"; export type { + IJoiningCircuitArgs, + IPollJoiningCircuitInputs, ITallyCircuitInputs, IProcessMessagesCircuitInputs, CircuitInputs, TreeDepths, BatchSizes, IJsonMaciState, + IPoll, + IJsonPoll, + IProcessMessagesOutput, } from "./utils/types"; export { STATE_TREE_ARITY, MESSAGE_BATCH_SIZE, VOTE_OPTION_TREE_ARITY } from "./utils/constants"; diff --git a/packages/core/ts/utils/types.ts b/packages/core/ts/utils/types.ts index 2cfbf14101..5496b3d51c 100644 --- a/packages/core/ts/utils/types.ts +++ b/packages/core/ts/utils/types.ts @@ -9,6 +9,7 @@ import type { Keypair, Message, PCommand, + PrivKey, PubKey, StateLeaf, } from "maci-domainobjs"; @@ -43,7 +44,7 @@ export interface BatchSizes { */ export interface IMaciState { // This method is used for signing up users to the state tree. - signUp(pubKey: PubKey, initialVoiceCreditBalance: bigint, timestamp: bigint): number; + signUp(pubKey: PubKey, initialVoiceCreditBalance: bigint, timestamp: bigint, stateRoot: bigint): number; // This method is used for deploying poll. deployPoll( pollEndTimestamp: bigint, @@ -63,6 +64,8 @@ export interface IMaciState { * An interface which represents the public API of the Poll class. */ export interface IPoll { + // Check if nullifier was already used for joining + hasJoined(nullifier: bigint): boolean; // These methods are used for sending a message to the poll from user publishMessage(message: Message, encPubKey: PubKey): void; // These methods are used to generate circuit inputs @@ -94,6 +97,7 @@ export interface IJsonPoll { encPubKeys: string[]; currentMessageBatchIndex: number; stateLeaves: IJsonStateLeaf[]; + pollStateLeaves: IJsonStateLeaf[]; results: string[]; numBatchesProcessed: number; numSignups: string; @@ -129,6 +133,32 @@ export interface IProcessMessagesOutput { command?: PCommand; } +/** + * An interface describing the joiningCircuitInputs function arguments + */ +export interface IJoiningCircuitArgs { + maciPrivKey: PrivKey; + stateLeafIndex: bigint; + credits: bigint; + pollPrivKey: PrivKey; + pollPubKey: PubKey; +} +/** + * An interface describing the circuit inputs to the PollJoining circuit + */ +export interface IPollJoiningCircuitInputs { + privKey: string; + pollPrivKey: string; + pollPubKey: string[]; + stateLeaf: string[]; + siblings: string[][]; + indices: string[]; + nullifier: string; + credits: string; + stateRoot: string; + actualStateTreeDepth: string; + inputHash: string; +} /** * An interface describing the circuit inputs to the ProcessMessage circuit */ diff --git a/packages/core/ts/utils/utils.ts b/packages/core/ts/utils/utils.ts index bab492f212..e0fcaacdb0 100644 --- a/packages/core/ts/utils/utils.ts +++ b/packages/core/ts/utils/utils.ts @@ -1,5 +1,16 @@ /* eslint-disable no-bitwise */ +/** + * This function generates the signature of a ProcessMessage Verifying Key(VK). + * This can be used to check if a ProcessMessages' circuit VK is registered + * in a smart contract that holds several VKs. + * @param stateTreeDepth - The depth of the state tree. + * @param voteOptionTreeDepth - The depth of the vote option tree. + * @returns Returns a signature for querying if a verifying key with the given parameters is already registered in the contract. + */ +export const genPollVkSig = (stateTreeDepth: number, voteOptionTreeDepth: number): bigint => + (BigInt(stateTreeDepth) << 64n) + BigInt(voteOptionTreeDepth); + /** * This function generates the signature of a ProcessMessage Verifying Key(VK). * This can be used to check if a ProcessMessages' circuit VK is registered diff --git a/packages/crypto/ts/hashing.ts b/packages/crypto/ts/hashing.ts index 4f3b5da1b6..c59a62d23c 100644 --- a/packages/crypto/ts/hashing.ts +++ b/packages/crypto/ts/hashing.ts @@ -116,6 +116,7 @@ export const hashN = (numElements: number, elements: Plaintext): bigint => { }; // hash functions +export const hashLeanIMT = (a: bigint, b: bigint): bigint => hashN(2, [a, b]); export const hash2 = (elements: Plaintext): bigint => hashN(2, elements); export const hash3 = (elements: Plaintext): bigint => hashN(3, elements); export const hash4 = (elements: Plaintext): bigint => hashN(4, elements); diff --git a/packages/crypto/ts/index.ts b/packages/crypto/ts/index.ts index e2b4b19403..bfaf8e003c 100644 --- a/packages/crypto/ts/index.ts +++ b/packages/crypto/ts/index.ts @@ -19,7 +19,19 @@ export { export { G1Point, G2Point, genRandomBabyJubValue } from "./babyjub"; -export { sha256Hash, hashLeftRight, hashN, hash2, hash3, hash4, hash5, hash12, hashOne } from "./hashing"; +export { + sha256Hash, + hashLeftRight, + hashN, + hash2, + hash3, + hash4, + hash5, + hash12, + hashOne, + poseidon, + hashLeanIMT, +} from "./hashing"; export { inCurve } from "@zk-kit/baby-jubjub"; diff --git a/packages/integrationTests/ts/__tests__/integration.test.ts b/packages/integrationTests/ts/__tests__/integration.test.ts index a424a0b86b..3f21f4e771 100644 --- a/packages/integrationTests/ts/__tests__/integration.test.ts +++ b/packages/integrationTests/ts/__tests__/integration.test.ts @@ -15,10 +15,12 @@ import { timeTravel, verify, DeployedContracts, + PollContracts, + joinPoll, } from "maci-cli"; import { getDefaultSigner } from "maci-contracts"; import { MaciState, TreeDepths } from "maci-core"; -import { genPubKey, genRandomSalt } from "maci-crypto"; +import { genPubKey, genRandomSalt, poseidon } from "maci-crypto"; import { Keypair, PCommand, PrivKey, PubKey } from "maci-domainobjs"; import fs from "fs"; @@ -72,6 +74,7 @@ describe("Integration tests", function test() { intStateTreeDepth: INT_STATE_TREE_DEPTH, voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, messageBatchSize: MESSAGE_BATCH_SIZE, + pollJoiningZkeyPath: path.resolve(__dirname, "../../../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey"), processMessagesZkeyPathQv: path.resolve( __dirname, "../../../cli/zkeys/ProcessMessages_10-20-2_test/ProcessMessages_10-20-2_test.0.zkey", @@ -124,10 +127,7 @@ describe("Integration tests", function test() { 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, @@ -159,10 +159,12 @@ describe("Integration tests", function test() { data.suites.forEach((testCase) => { it(testCase.description, async () => { const users = genTestUserCommands(testCase.numUsers, testCase.numVotesPerUser, testCase.bribers, testCase.votes); + const pollKeys: Keypair[] = Array.from({ length: testCase.numUsers }, () => new Keypair()); // loop through all users and generate keypair + signup for (let i = 0; i < users.length; i += 1) { const user = users[i]; + const pollKey = pollKeys[i]; const timestamp = Date.now(); // signup const stateIndex = BigInt( @@ -175,9 +177,37 @@ describe("Integration tests", function test() { }).then((result) => result.stateIndex), ); + await joinPoll({ + maciAddress: contracts.maciAddress, + privateKey: user.keypair.privKey.serialize(), + pollPrivKey: pollKey.privKey.serialize(), + stateIndex, + pollId, + pollJoiningZkey: path.resolve(__dirname, "../../../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey"), + useWasm: true, + pollWasm: path.resolve( + __dirname, + "../../../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm", + ), + pollWitgen: path.resolve( + __dirname, + "../../../cli/zkeys/PollJoining_10_test/PollJoining_10_test_cpp/PollJoining_10_test", + ), + rapidsnark: `${homedir()}/rapidsnark/build/prover`, + signer, + newVoiceCreditBalance: BigInt(initialVoiceCredits), + quiet: true, + }); + // signup on local maci state maciState.signUp(user.keypair.pubKey, BigInt(initialVoiceCredits), BigInt(timestamp)); + // join the poll on local + const inputNullifier = BigInt(user.keypair.privKey.asCircuitInputs()); + const nullifier = poseidon([inputNullifier]); + const poll = maciState.polls.get(pollId); + poll?.joinPoll(nullifier, pollKey.pubKey, BigInt(initialVoiceCredits), BigInt(timestamp)); + // publish messages for (let j = 0; j < user.votes.length; j += 1) { const isKeyChange = testCase.changeUsersKeys && j in testCase.changeUsersKeys[i]; @@ -197,7 +227,7 @@ describe("Integration tests", function test() { // actually publish it const encryptionKey = await publish({ - pubkey: user.keypair.pubKey.serialize(), + pubkey: pollKey.pubKey.serialize(), stateIndex, voteOptionIndex: voteOptionIndex!, nonce, @@ -206,7 +236,7 @@ describe("Integration tests", function test() { maciAddress: contracts.maciAddress, salt, // if it's a key change command, then we pass the old private key otherwise just pass the current - privateKey: isKeyChange ? oldKeypair.privKey.serialize() : user.keypair.privKey.serialize(), + privateKey: isKeyChange ? oldKeypair.privKey.serialize() : pollKey.privKey.serialize(), signer, }); @@ -216,14 +246,14 @@ describe("Integration tests", function test() { // create the command to add to the local state const command = new PCommand( stateIndex, - user.keypair.pubKey, + pollKey.pubKey, voteOptionIndex!, newVoteWeight!, nonce, pollId, salt, ); - const signature = command.sign(isKeyChange ? oldKeypair.privKey : user.keypair.privKey); + const signature = command.sign(isKeyChange ? oldKeypair.privKey : pollKey.privKey); const message = command.encrypt(signature, Keypair.genEcdhSharedKey(encPrivKey, coordinatorKeypair.pubKey)); maciState.polls.get(pollId)?.publishMessage(message, encPubKey); } diff --git a/packages/integrationTests/ts/__tests__/utils/constants.ts b/packages/integrationTests/ts/__tests__/utils/constants.ts index 2583b36f16..746d580358 100644 --- a/packages/integrationTests/ts/__tests__/utils/constants.ts +++ b/packages/integrationTests/ts/__tests__/utils/constants.ts @@ -31,7 +31,7 @@ export const votingDurationInSeconds = 3600; export const tallyBatchSize = 4; export const quadVoteTallyBatchSize = 4; export const voteOptionsMaxLeafIndex = 3; -export const duration = 300; +export const duration = 2000; export const intStateTreeDepth = 1; export const STATE_TREE_DEPTH = 10; export const INT_STATE_TREE_DEPTH = 1; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12c53deca6..afced5f4d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -354,6 +354,12 @@ importers: '@pcd/util': specifier: ^0.8.0 version: 0.8.0 + '@zk-kit/imt.sol': + specifier: 2.0.0-beta.12 + version: 2.0.0-beta.12 + '@zk-kit/lean-imt': + specifier: ^2.1.0 + version: 2.2.1 circomlibjs: specifier: ^0.1.7 version: 0.1.7 @@ -427,6 +433,9 @@ importers: packages/core: dependencies: + '@zk-kit/lean-imt': + specifier: ^2.2.1 + version: 2.2.1 maci-crypto: specifier: ^2.5.0 version: link:../crypto @@ -3114,6 +3123,12 @@ packages: '@zk-kit/eddsa-poseidon@1.1.0': resolution: {integrity: sha512-Djc+zOZjd73FpLLf32/XeVZi8GX4ShPQJGS4Iig1QMAR/2CggEi++6Jrkr9N2FM3M4MRCH1qxz2u22DjOLtASg==} + '@zk-kit/imt.sol@2.0.0-beta.12': + resolution: {integrity: sha512-kKgopVO6zlfSiQgv3X9WykaCeyb8jGtthWGqdo1ZD7fY1bH8A7BWhhWxtoCuU5mPEgRbamw1cAoUynuLoEULsg==} + + '@zk-kit/lean-imt@2.2.1': + resolution: {integrity: sha512-Zq5yunUYu+ztp9RR5nuqiG1GpK1wjUoAjC0+x/MB95sI/Ns7zCxpzxo/Om9E0gme74Y3jO9KM5UUh3f9tqU++w==} + '@zk-kit/poseidon-cipher@0.3.2': resolution: {integrity: sha512-Ezz1e0mj/GRDlHdU5m0uhj5iHY72zWJU0BP8DsCCvPubU7LPI2tVaPpxrAjT4JQqatbVRQHLIhixW7F7BPzaFg==} @@ -5136,9 +5151,8 @@ packages: '@codechecks/client': optional: true - ethereum-bloom-filters@1.1.0: - resolution: {integrity: sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==} - deprecated: do not use this package use package versions above as this can miss some topics + ethereum-bloom-filters@1.2.0: + resolution: {integrity: sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==} ethereum-cryptography@0.1.3: resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==} @@ -8109,6 +8123,9 @@ packages: poseidon-lite@0.3.0: resolution: {integrity: sha512-ilJj4MIve4uBEG7SrtPqUUNkvpJ/pLVbndxa0WvebcQqeIhe+h72JR4g0EvwchUzm9sOQDlOjiDNmRAgxNZl4A==} + poseidon-solidity@0.0.5: + resolution: {integrity: sha512-NzrvSwHzvZgT4hvg2GyGqeR+UOU/eLSEt4wAoXEua+VaR7NTKKwx1X9bPlh1VMBEVEno+IWvkRBbidFGzTeAqQ==} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -11718,7 +11735,7 @@ snapshots: '@docusaurus/core': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/logger': 3.5.2 '@docusaurus/mdx-loader': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-common': 3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/types': 3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) @@ -11756,6 +11773,46 @@ snapshots: - webpack-cli '@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + dependencies: + '@docusaurus/core': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/logger': 3.5.2 + '@docusaurus/mdx-loader': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/module-type-aliases': 3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-common': 3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/types': 3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) + '@docusaurus/utils-common': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@docusaurus/utils-validation': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) + '@types/react-router-config': 5.0.11 + combine-promises: 1.2.0 + fs-extra: 11.2.0 + js-yaml: 4.1.0 + lodash: 4.17.21 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.6.3 + utility-types: 3.11.0 + webpack: 5.92.1 + transitivePeerDependencies: + - '@mdx-js/react' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + + '@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': dependencies: '@docusaurus/core': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/logger': 3.5.2 @@ -11973,7 +12030,7 @@ snapshots: dependencies: '@docusaurus/core': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-blog': 3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-pages': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-debug': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-google-analytics': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) @@ -12019,7 +12076,7 @@ snapshots: '@docusaurus/mdx-loader': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/module-type-aliases': 3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/plugin-content-blog': 3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-pages': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-common': 3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-translations': 3.5.2 @@ -12061,7 +12118,7 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-common@3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@docusaurus/theme-common@3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': dependencies: '@docusaurus/mdx-loader': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/module-type-aliases': 3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -12087,12 +12144,38 @@ snapshots: - uglify-js - webpack-cli + '@docusaurus/theme-common@3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + dependencies: + '@docusaurus/mdx-loader': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/module-type-aliases': 3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) + '@docusaurus/utils-common': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@types/history': 4.7.11 + '@types/react': 18.3.12 + '@types/react-router-config': 5.0.11 + clsx: 2.1.1 + parse-numeric-range: 1.3.0 + prism-react-renderer: 2.4.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.6.3 + utility-types: 3.11.0 + transitivePeerDependencies: + - '@docusaurus/types' + - '@swc/core' + - esbuild + - supports-color + - typescript + - uglify-js + - webpack-cli + '@docusaurus/theme-search-algolia@3.5.2(@algolia/client-search@4.23.3)(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.14.0)(typescript@5.6.3)': dependencies: '@docsearch/react': 3.6.0(@algolia/client-search@4.23.3)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.14.0) '@docusaurus/core': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/logger': 3.5.2 - '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(debug@4.3.6)(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-common': 3.5.2(@docusaurus/plugin-content-docs@3.5.2(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(eslint@8.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-translations': 3.5.2 '@docusaurus/utils': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) @@ -14392,6 +14475,14 @@ snapshots: buffer: 6.0.3 poseidon-lite: 0.3.0 + '@zk-kit/imt.sol@2.0.0-beta.12': + dependencies: + poseidon-solidity: 0.0.5 + + '@zk-kit/lean-imt@2.2.1': + dependencies: + '@zk-kit/utils': 1.2.1 + '@zk-kit/poseidon-cipher@0.3.2': dependencies: '@zk-kit/baby-jubjub': 1.0.3 @@ -16698,7 +16789,7 @@ snapshots: - debug - utf-8-validate - ethereum-bloom-filters@1.1.0: + ethereum-bloom-filters@1.2.0: dependencies: '@noble/hashes': 1.4.0 @@ -20657,6 +20748,8 @@ snapshots: poseidon-lite@0.3.0: {} + poseidon-solidity@0.0.5: {} + possible-typed-array-names@1.0.0: {} postcss-calc@9.0.1(postcss@8.4.38): @@ -22915,7 +23008,7 @@ snapshots: dependencies: '@ethereumjs/util': 8.1.0 bn.js: 5.2.1 - ethereum-bloom-filters: 1.1.0 + ethereum-bloom-filters: 1.2.0 ethereum-cryptography: 2.1.3 ethjs-unit: 0.1.6 number-to-bn: 1.7.0 @@ -22925,7 +23018,7 @@ snapshots: web3-utils@1.7.0: dependencies: bn.js: 4.12.0 - ethereum-bloom-filters: 1.1.0 + ethereum-bloom-filters: 1.2.0 ethereumjs-util: 7.1.5 ethjs-unit: 0.1.6 number-to-bn: 1.7.0 From c384a5b8e53706bb00060f684242e8ba9a4d11e8 Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:14:18 -0600 Subject: [PATCH 03/14] feat: rebase anon poll joining with dev - [x] Add rebase missing parts - [x] Remove sha256 input hash --- apps/subgraph/src/utils/constants.ts | 1 - .../subgraph/templates/subgraph.template.yaml | 2 +- apps/subgraph/tests/common.ts | 12 +--- apps/subgraph/tests/maci/maci.test.ts | 31 +++++++-- apps/subgraph/tests/maci/utils.ts | 17 ++++- apps/subgraph/tests/poll/poll.test.ts | 17 ++++- packages/circuits/circom/circuits.json | 2 +- .../circom/core/non-qv/processMessages.circom | 64 ++++++++++------- .../circom/core/qv/pollJoining.circom | 7 +- .../circom/core/qv/processMessages.circom | 68 ++++++++++++------- .../utils/non-qv/messageValidator.circom | 6 -- .../stateLeafAndBallotTransformer.circom | 10 +-- .../circom/utils/qv/messageValidator.circom | 7 -- .../qv/stateLeafAndBallotTransformer.circom | 9 +-- .../ts/__tests__/CeremonyParams.test.ts | 7 +- .../ts/__tests__/MessageToCommand.test.ts | 4 +- .../ts/__tests__/MessageValidator.test.ts | 68 +------------------ .../circuits/ts/__tests__/PollJoining.test.ts | 3 +- packages/circuits/ts/types.ts | 1 - packages/cli/deploy-config.json | 1 + packages/cli/deployed-contracts.json | 1 + packages/cli/tests/constants.ts | 1 + packages/cli/tests/e2e/e2e.nonQv.test.ts | 2 +- packages/cli/tests/e2e/e2e.test.ts | 4 +- packages/cli/tests/unit/poll.test.ts | 45 ++++++++---- packages/cli/tests/unit/utils.test.ts | 13 ++-- packages/cli/ts/commands/deployPoll.ts | 20 +++--- packages/cli/ts/commands/genLocalState.ts | 6 +- packages/cli/ts/commands/genProofs.ts | 12 ++-- packages/cli/ts/commands/joinPoll.ts | 5 +- packages/cli/ts/commands/mergeSignups.ts | 6 +- packages/cli/ts/commands/poll.ts | 5 +- packages/cli/ts/commands/proveOnChain.ts | 40 ++++++++--- packages/cli/ts/commands/publish.ts | 10 +-- packages/cli/ts/commands/showContracts.ts | 6 +- packages/cli/ts/commands/verify.ts | 26 +++++-- packages/cli/ts/index.ts | 23 +++++-- packages/cli/ts/utils/interfaces.ts | 25 +++++++ packages/contracts/contracts/MACI.sol | 8 +-- .../contracts/contracts/MessageProcessor.sol | 10 +-- packages/contracts/contracts/Poll.sol | 44 +++++++----- packages/contracts/contracts/PollFactory.sol | 1 - packages/contracts/contracts/Tally.sol | 2 +- .../contracts/contracts/interfaces/IMACI.sol | 4 -- .../contracts/contracts/interfaces/IPoll.sol | 4 +- .../contracts/contracts/interfaces/ITally.sol | 5 +- .../contracts/contracts/trees/LazyIMT.sol | 2 +- packages/contracts/package.json | 2 +- .../contracts/tasks/helpers/ProofGenerator.ts | 2 +- packages/contracts/tests/MACI.test.ts | 2 +- packages/contracts/tests/PollFactory.test.ts | 14 ---- packages/contracts/tests/Tally.test.ts | 2 +- packages/core/ts/Poll.ts | 17 ++--- packages/core/ts/__tests__/utils.test.ts | 4 +- packages/core/ts/utils/types.ts | 1 - .../ts/__tests__/integration.test.ts | 6 +- .../ts/__tests__/maci-keys.test.ts | 2 +- pnpm-lock.yaml | 10 +-- 58 files changed, 408 insertions(+), 321 deletions(-) create mode 100644 packages/cli/deploy-config.json create mode 100644 packages/cli/deployed-contracts.json diff --git a/apps/subgraph/src/utils/constants.ts b/apps/subgraph/src/utils/constants.ts index 69662db273..cdd436764c 100644 --- a/apps/subgraph/src/utils/constants.ts +++ b/apps/subgraph/src/utils/constants.ts @@ -1,4 +1,3 @@ import { BigInt as GraphBN } from "@graphprotocol/graph-ts"; export const ONE_BIG_INT = GraphBN.fromU32(1); -export const MESSAGE_TREE_ARITY = 5; diff --git a/apps/subgraph/templates/subgraph.template.yaml b/apps/subgraph/templates/subgraph.template.yaml index 76b6ac291c..9999d45fed 100644 --- a/apps/subgraph/templates/subgraph.template.yaml +++ b/apps/subgraph/templates/subgraph.template.yaml @@ -29,7 +29,7 @@ dataSources: - name: Poll file: ./node_modules/maci-contracts/build/artifacts/contracts/Poll.sol/Poll.json eventHandlers: - - event: DeployPoll(uint256,indexed uint256,indexed uint256,uint8) + - event: DeployPoll(uint256,indexed uint256,indexed uint256,(address,address,address),uint8) handler: handleDeployPoll - event: SignUp(uint256,indexed uint256,indexed uint256,uint256,uint256,uint256) handler: handleSignUp diff --git a/apps/subgraph/tests/common.ts b/apps/subgraph/tests/common.ts index dc731d4ac2..859a2de171 100644 --- a/apps/subgraph/tests/common.ts +++ b/apps/subgraph/tests/common.ts @@ -1,4 +1,4 @@ -import { Address, ethereum, BigInt } from "@graphprotocol/graph-ts"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; // eslint-disable-next-line import/no-extraneous-dependencies import { createMockedFunction } from "matchstick-as"; @@ -29,16 +29,10 @@ export function mockMaciContract(): void { createMockedFunction( Address.fromString("0xA16081F360e3847006dB660bae1c6d1b2e17eC2A"), "getPoll", - "getPoll(uint256):((address,address,address))", + "getPoll(uint256):((address))", ) .withArgs([ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1))]) .returns([ - ethereum.Value.fromTuple( - changetype([ - ethereum.Value.fromAddress(DEFAULT_POLL_ADDRESS), - ethereum.Value.fromAddress(DEFAULT_MESSAGE_PROCESSOR_ADDRESS), - ethereum.Value.fromAddress(DEFAULT_TALLY_ADDRESS), - ]), - ), + ethereum.Value.fromTuple(changetype([ethereum.Value.fromAddress(DEFAULT_POLL_ADDRESS)])), ]); } diff --git a/apps/subgraph/tests/maci/maci.test.ts b/apps/subgraph/tests/maci/maci.test.ts index 537b740f67..d41e7c4dff 100644 --- a/apps/subgraph/tests/maci/maci.test.ts +++ b/apps/subgraph/tests/maci/maci.test.ts @@ -4,7 +4,12 @@ import { test, describe, afterEach, clearStore, assert, beforeAll } from "matchs import { Account, MACI, Poll, User } from "../../generated/schema"; import { handleSignUp, handleDeployPoll } from "../../src/maci"; -import { DEFAULT_POLL_ADDRESS, mockMaciContract, mockPollContract } from "../common"; +import { + DEFAULT_MESSAGE_PROCESSOR_ADDRESS, + DEFAULT_POLL_ADDRESS, + DEFAULT_TALLY_ADDRESS, + mockPollContract, +} from "../common"; import { createSignUpEvent, createDeployPollEvent } from "./utils"; @@ -12,7 +17,6 @@ export { handleSignUp, handleDeployPoll }; describe("MACI", () => { beforeAll(() => { - mockMaciContract(); mockPollContract(); }); @@ -48,7 +52,15 @@ describe("MACI", () => { }); test("should handle deploy poll properly (qv)", () => { - const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(0)); + const event = createDeployPollEvent( + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + DEFAULT_POLL_ADDRESS, + DEFAULT_MESSAGE_PROCESSOR_ADDRESS, + DEFAULT_TALLY_ADDRESS, + BigInt.fromI32(0), + ); handleDeployPoll(event); @@ -65,7 +77,15 @@ describe("MACI", () => { }); test("should handle deploy poll properly (non-qv)", () => { - const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1)); + const event = createDeployPollEvent( + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + DEFAULT_POLL_ADDRESS, + DEFAULT_MESSAGE_PROCESSOR_ADDRESS, + DEFAULT_TALLY_ADDRESS, + BigInt.fromI32(1), + ); handleDeployPoll(event); @@ -86,6 +106,9 @@ describe("MACI", () => { BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), + DEFAULT_POLL_ADDRESS, + DEFAULT_MESSAGE_PROCESSOR_ADDRESS, + DEFAULT_TALLY_ADDRESS, BigInt.fromI32(0), ); diff --git a/apps/subgraph/tests/maci/utils.ts b/apps/subgraph/tests/maci/utils.ts index 76e4a79a9d..0f2c2f38e3 100644 --- a/apps/subgraph/tests/maci/utils.ts +++ b/apps/subgraph/tests/maci/utils.ts @@ -1,4 +1,4 @@ -import { BigInt as GraphBN, ethereum } from "@graphprotocol/graph-ts"; +import { Address, BigInt as GraphBN, ethereum } from "@graphprotocol/graph-ts"; // eslint-disable-next-line import/no-extraneous-dependencies import { newMockEvent } from "matchstick-as"; @@ -28,6 +28,9 @@ export function createDeployPollEvent( pollId: GraphBN, coordinatorPubKeyX: GraphBN, coordinatorPubKeyY: GraphBN, + poll: Address, + messageProcessor: Address, + tally: Address, mode: GraphBN, ): DeployPoll { const event = changetype(newMockEvent()); @@ -39,6 +42,18 @@ export function createDeployPollEvent( event.parameters.push( new ethereum.EventParam("_coordinatorPubKeyY", ethereum.Value.fromUnsignedBigInt(coordinatorPubKeyY)), ); + event.parameters.push( + new ethereum.EventParam( + "pollAddr", + ethereum.Value.fromTuple( + changetype([ + ethereum.Value.fromAddress(poll), + ethereum.Value.fromAddress(messageProcessor), + ethereum.Value.fromAddress(tally), + ]), + ), + ), + ); event.parameters.push(new ethereum.EventParam("mode", ethereum.Value.fromUnsignedBigInt(mode))); return event; diff --git a/apps/subgraph/tests/poll/poll.test.ts b/apps/subgraph/tests/poll/poll.test.ts index 20c1f7b9a5..a7468ccb19 100644 --- a/apps/subgraph/tests/poll/poll.test.ts +++ b/apps/subgraph/tests/poll/poll.test.ts @@ -5,7 +5,12 @@ import { test, describe, afterEach, clearStore, assert, beforeEach } from "match import { MACI, Poll } from "../../generated/schema"; import { handleDeployPoll } from "../../src/maci"; import { handleMergeState, handlePublishMessage } from "../../src/poll"; -import { DEFAULT_POLL_ADDRESS, mockPollContract } from "../common"; +import { + DEFAULT_MESSAGE_PROCESSOR_ADDRESS, + DEFAULT_POLL_ADDRESS, + DEFAULT_TALLY_ADDRESS, + mockPollContract, +} from "../common"; import { createDeployPollEvent } from "../maci/utils"; import { createMergeStateEvent, createPublishMessageEvent } from "./utils"; @@ -17,7 +22,15 @@ describe("Poll", () => { mockPollContract(); // mock the deploy poll event with non qv mode set - const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1)); + const event = createDeployPollEvent( + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + DEFAULT_POLL_ADDRESS, + DEFAULT_MESSAGE_PROCESSOR_ADDRESS, + DEFAULT_TALLY_ADDRESS, + BigInt.fromI32(1), + ); handleDeployPoll(event); }); diff --git a/packages/circuits/circom/circuits.json b/packages/circuits/circom/circuits.json index 459f73cd8d..ba9522f44d 100644 --- a/packages/circuits/circom/circuits.json +++ b/packages/circuits/circom/circuits.json @@ -3,7 +3,7 @@ "file": "./core/qv/pollJoining", "template": "PollJoining", "params": [10], - "pubs": ["inputHash"] + "pubs": ["nullifier", "credits", "stateRoot", "pollPubKey"] }, "ProcessMessages_10-20-2_test": { "file": "./core/qv/processMessages", diff --git a/packages/circuits/circom/core/non-qv/processMessages.circom b/packages/circuits/circom/core/non-qv/processMessages.circom index 9dbede9d6d..7efba4f633 100644 --- a/packages/circuits/circom/core/non-qv/processMessages.circom +++ b/packages/circuits/circom/core/non-qv/processMessages.circom @@ -43,9 +43,9 @@ include "../../trees/incrementalQuinaryTree.circom"; var msgTreeZeroValue = 8370432830353022751713833565135785980866757267633941821328460903436894336785; // Number of users that have completed the sign up. - signal numSignUps; + signal input numSignUps; // Number of options for this poll. - signal maxVoteOptions; + signal input maxVoteOptions; // Value of chainHash at beginning of batch signal input inputBatchHash; // Value of chainHash at end of batch @@ -62,10 +62,6 @@ include "../../trees/incrementalQuinaryTree.circom"; // @note it is a public input to ensure fair processing from // the coordinator (no censoring) signal input actualStateTreeDepth; - // The last batch index - signal input batchEndIndex; - // The batch index of current message batch - signal input index; // The coordinator public key hash signal input coordinatorPublicKeyHash; @@ -100,12 +96,12 @@ include "../../trees/incrementalQuinaryTree.circom"; // 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; + signal input index; // The index of the last message in the batch to process, exclusive. - // This value may be less than batchStartIndex + batchSize if this batch is + // This value may be less than index + batchSize if this batch is // the last batch and the total number of messages is not a multiple of the batch size. - signal batchEndIndex; + signal input batchEndIndex; // The history of state and ballot roots and temporary intermediate // signals (for processing purposes). @@ -140,14 +136,14 @@ include "../../trees/incrementalQuinaryTree.circom"; // 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]); + var batchStartIndexValid = SafeLessThan(32)([index + 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 batchEndIndex < batchStartIndex + i, the remaining + // If batchEndIndex < index + 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. @@ -183,6 +179,11 @@ include "../../trees/incrementalQuinaryTree.circom"; var computedCommandsPackedCommandOut[batchSize][PACKED_CMD_LENGTH]; for (var i = 0; i < batchSize; i++) { + var message[MSG_LENGTH]; + for (var j = 0; j < MSG_LENGTH; j++) { + message[j] = msgs[i][j]; + } + ( computedCommandsStateIndex[i], computedCommandsNewPubKey[i], @@ -194,7 +195,7 @@ include "../../trees/incrementalQuinaryTree.circom"; computedCommandsSigR8[i], computedCommandsSigS[i], computedCommandsPackedCommandOut[i] - ) = MessageToCommand()(msgs[i], coordPrivKey, encPubKeys[i]); + ) = MessageToCommand()(message, coordPrivKey, encPubKeys[i]); } // Process messages in reverse order. @@ -277,7 +278,6 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { var BALLOT_NONCE_IDX = 0; // Ballot vote option (VO) root index. var BALLOT_VO_ROOT_IDX = 1; - var maxVoteOptions = MESSAGE_TREE_ARITY ** voteOptionTreeDepth; // Indices for elements within a state leaf. // Public key. @@ -287,10 +287,10 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { var STATE_LEAF_VOICE_CREDIT_BALANCE_IDX = 2; // Timestamp. var STATE_LEAF_TIMESTAMP_IDX = 3; + var N_BITS = 252; // Inputs representing the message and the current state. signal input numSignUps; - signal input maxVoteOptions; // The current value of the state tree root. @@ -338,8 +338,8 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // 1. Transform a state leaf and a ballot with a command. // The result is a new state leaf, a new ballot, and an isValid signal (0 or 1). - var computedNewSlPubKey[2], computedNewBallotNonce, computedIsValid, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid; - (computedNewSlPubKey, computedNewBallotNonce, computedIsValid, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid) = StateLeafAndBallotTransformerNonQv()( + var computedNewSlPubKey[2], computedNewBallotNonce, computedIsValid; + (computedNewSlPubKey, computedNewBallotNonce, computedIsValid) = StateLeafAndBallotTransformerNonQv()( numSignUps, maxVoteOptions, [stateLeaf[STATE_LEAF_PUB_X_IDX], stateLeaf[STATE_LEAF_PUB_Y_IDX]], @@ -359,9 +359,10 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { packedCmd ); - // 2. If computedIsStateLeafIndexValid is equal to zero, generate indices for leaf zero. + // 2. If isValid is equal to zero, generate indices for leaf zero. // Otherwise, generate indices for command.stateIndex. - var stateIndexMux = Mux1()([0, cmdStateIndex], computedIsStateLeafIndexValid); + var stateLeafIndexValid = SafeLessThan(N_BITS)([cmdStateIndex, numSignUps]); + var stateIndexMux = Mux1()([0, cmdStateIndex], stateLeafIndexValid); var computedStateLeafPathIndices[stateTreeDepth] = MerkleGeneratePathIndices(stateTreeDepth)(stateIndexMux); // 3. Verify that the original state leaf exists in the given state root. @@ -394,7 +395,14 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { b <== currentVoteWeight; c <== cmdNewVoteWeight; - var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); + var voiceCreditAmountValid = SafeGreaterEqThan(252)([ + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + b, + c + ]); + + var computedIsMessageEqual = IsEqual()([2, computedIsValid + voiceCreditAmountValid]); + var voteOptionIndexValid = SafeLessThan(N_BITS)([cmdVoteOptionIndex, maxVoteOptions]); + var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], voteOptionIndexValid); var computedCurrentVoteWeightPathIndices[voteOptionTreeDepth] = QuinGeneratePathIndices(voteOptionTreeDepth)(cmdVoteOptionIndexMux); var computedCurrentVoteWeightQip = QuinTreeInclusionProof(voteOptionTreeDepth)( @@ -405,13 +413,20 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { computedCurrentVoteWeightQip === ballot[BALLOT_VO_ROOT_IDX]; - var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsValid); - var voiceCreditBalanceMux = Mux1()( + var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsMessageEqual); + var newSlVoiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + b - c ], - computedIsValid + voiceCreditAmountValid + ); + var voiceCreditBalanceMux = Mux1()( + [ + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], + newSlVoiceCreditBalanceMux + ], + computedIsMessageEqual ); // 5.1. Update the ballot's vote option root with the new vote weight. @@ -424,7 +439,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // The new vote option root in the ballot var newBallotVoRootMux = Mux1()( [ballot[BALLOT_VO_ROOT_IDX], computedNewVoteOptionTreeQip], - computedIsValid + computedIsMessageEqual ); newBallotVoRoot <== newBallotVoRootMux; @@ -447,7 +462,8 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { newStateRoot <== computedNewStateLeafQip; // 7. Generate a new ballot root. - var computedNewBallot = PoseidonHasher(2)([computedNewBallotNonce, newBallotVoRoot]); + var newBallotNonceMux = Mux1()([ballot[BALLOT_NONCE_IDX], computedNewBallotNonce], computedIsMessageEqual); + var computedNewBallot = PoseidonHasher(2)([newBallotNonceMux, newBallotVoRoot]); var computedNewBallotQip = MerkleTreeInclusionProof(stateTreeDepth)( computedNewBallot, computedStateLeafPathIndices, diff --git a/packages/circuits/circom/core/qv/pollJoining.circom b/packages/circuits/circom/core/qv/pollJoining.circom index 9de81f7951..1650685d63 100644 --- a/packages/circuits/circom/core/qv/pollJoining.circom +++ b/packages/circuits/circom/core/qv/pollJoining.circom @@ -43,12 +43,9 @@ template PollJoining(stateTreeDepth) { signal input stateRoot; // The actual tree depth (might be <= stateTreeDepth) Used in BinaryMerkleRoot signal input actualStateTreeDepth; - // Public input hash (nullifier, credits, stateRoot) - signal input inputHash; - // Check public input hash - var computedInputHash = Sha256Hasher(5)([nullifier, credits, stateRoot, pollPubKey[0], pollPubKey[1]]); - inputHash === computedInputHash; + var computedNullifier = PoseidonHasher(1)([privKey]); + nullifier === computedNullifier; // User private to public key var derivedPubKey[2] = PrivToPubKey()(privKey); diff --git a/packages/circuits/circom/core/qv/processMessages.circom b/packages/circuits/circom/core/qv/processMessages.circom index 15497bd2d6..d9a3dfbfde 100644 --- a/packages/circuits/circom/core/qv/processMessages.circom +++ b/packages/circuits/circom/core/qv/processMessages.circom @@ -47,9 +47,9 @@ template ProcessMessages( // 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; + signal input numSignUps; // Number of options for this poll. - signal maxVoteOptions; + signal input maxVoteOptions; // Value of chainHash at beginning of batch signal input inputBatchHash; // Value of chainHash at end of batch @@ -66,10 +66,6 @@ template ProcessMessages( // @note it is a public input to ensure fair processing from // the coordinator (no censoring) signal input actualStateTreeDepth; - // The last batch index - signal input batchEndIndex; - // The batch index of current message batch - signal input index; // The coordinator public key hash signal input coordinatorPublicKeyHash; @@ -104,12 +100,12 @@ template ProcessMessages( // 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; + signal input index; // 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; + signal input batchEndIndex; // The history of state and ballot roots and temporary intermediate // signals (for processing purposes). @@ -135,18 +131,19 @@ template ProcessMessages( 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]); + var batchStartIndexValid = SafeLessThan(32)([index + 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 batchEndIndex < batchStartIndex + i, the remaining + // If batchEndIndex < index + 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. @@ -181,6 +178,12 @@ template ProcessMessages( var computedCommandsPackedCommandOut[batchSize][PACKED_CMD_LENGTH]; for (var i = 0; i < batchSize; i++) { + var message[MSG_LENGTH]; + + for (var j = 0; j < MSG_LENGTH; j++) { + message[j] = msgs[i][j]; + } + ( computedCommandsStateIndex[i], computedCommandsNewPubKey[i], @@ -192,7 +195,7 @@ template ProcessMessages( computedCommandsSigR8[i], computedCommandsSigS[i], computedCommandsPackedCommandOut[i] - ) = MessageToCommand()(msgs[i], coordPrivKey, encPubKeys[i]); + ) = MessageToCommand()(message, coordPrivKey, encPubKeys[i]); } // Process messages in reverse order. @@ -276,9 +279,6 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // Ballot vote option (VO) root index. var BALLOT_VO_ROOT_IDX = 1; - // Number of options for this poll. - var maxVoteOptions = MESSAGE_TREE_ARITY ** voteOptionTreeDepth; - // Indices for elements within a state leaf. // Public key. var STATE_LEAF_PUB_X_IDX = 0; @@ -287,10 +287,10 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { var STATE_LEAF_VOICE_CREDIT_BALANCE_IDX = 2; // Timestamp. var STATE_LEAF_TIMESTAMP_IDX = 3; + var N_BITS = 252; - // Number of users that have completed the sign up. + // Inputs representing the message and the current state. signal input numSignUps; - signal input maxVoteOptions; // The current value of the state tree root. @@ -338,8 +338,8 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // 1. Transform a state leaf and a ballot with a command. // The result is a new state leaf, a new ballot, and an isValid signal (0 or 1). - var computedNewSlPubKey[2], computedNewBallotNonce, computedIsValid, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid; - (computedNewSlPubKey, computedNewBallotNonce, computedIsValid, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid) = StateLeafAndBallotTransformer()( + var computedNewSlPubKey[2], computedNewBallotNonce, computedIsValid; + (computedNewSlPubKey, computedNewBallotNonce, computedIsValid) = StateLeafAndBallotTransformer()( numSignUps, maxVoteOptions, [stateLeaf[STATE_LEAF_PUB_X_IDX], stateLeaf[STATE_LEAF_PUB_Y_IDX]], @@ -359,9 +359,10 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { packedCmd ); - // 2. If computedIsStateLeafIndexValid is equal to zero, generate indices for leaf zero. + // 2. If isValid is equal to zero, generate indices for leaf zero. // Otherwise, generate indices for command.stateIndex. - var stateIndexMux = Mux1()([0, cmdStateIndex], computedIsStateLeafIndexValid); + var stateLeafIndexValid = SafeLessThan(N_BITS)([cmdStateIndex, numSignUps]); + var stateIndexMux = Mux1()([0, cmdStateIndex], stateLeafIndexValid); var computedStateLeafPathIndices[stateTreeDepth] = MerkleGeneratePathIndices(stateTreeDepth)(stateIndexMux); // 3. Verify that the original state leaf exists in the given state root. @@ -394,7 +395,14 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { b <== currentVoteWeight * currentVoteWeight; c <== cmdNewVoteWeight * cmdNewVoteWeight; - var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); + var voiceCreditAmountValid = SafeGreaterEqThan(252)([ + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + b, + c + ]); + + var computedIsMessageEqual = IsEqual()([2, computedIsValid + voiceCreditAmountValid]); + var voteOptionIndexValid = SafeLessThan(N_BITS)([cmdVoteOptionIndex, maxVoteOptions]); + var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], voteOptionIndexValid); var computedCurrentVoteWeightPathIndices[voteOptionTreeDepth] = QuinGeneratePathIndices(voteOptionTreeDepth)(cmdVoteOptionIndexMux); var computedCurrentVoteWeightQip = QuinTreeInclusionProof(voteOptionTreeDepth)( @@ -405,13 +413,20 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { computedCurrentVoteWeightQip === ballot[BALLOT_VO_ROOT_IDX]; - var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsValid); - var voiceCreditBalanceMux = Mux1()( + var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsMessageEqual); + var newSlVoiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + b - c ], - computedIsValid + voiceCreditAmountValid + ); + var voiceCreditBalanceMux = Mux1()( + [ + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], + newSlVoiceCreditBalanceMux + ], + computedIsMessageEqual ); // 5.1. Update the ballot's vote option root with the new vote weight. @@ -424,7 +439,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // The new vote option root in the ballot var newBallotVoRootMux = Mux1()( [ballot[BALLOT_VO_ROOT_IDX], computedNewVoteOptionTreeQip], - computedIsValid + computedIsMessageEqual ); newBallotVoRoot <== newBallotVoRootMux; @@ -447,7 +462,8 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { newStateRoot <== computedNewStateLeafQip; // 7. Generate a new ballot root. - var computedNewBallot = PoseidonHasher(2)([computedNewBallotNonce, newBallotVoRoot]); + var newBallotNonceMux = Mux1()([ballot[BALLOT_NONCE_IDX], computedNewBallotNonce], computedIsMessageEqual); + var computedNewBallot = PoseidonHasher(2)([newBallotNonceMux, newBallotVoRoot]); var computedNewBallotQip = MerkleTreeInclusionProof(stateTreeDepth)( computedNewBallot, computedStateLeafPathIndices, diff --git a/packages/circuits/circom/utils/non-qv/messageValidator.circom b/packages/circuits/circom/utils/non-qv/messageValidator.circom index e5cad3aaec..ae235804f3 100644 --- a/packages/circuits/circom/utils/non-qv/messageValidator.circom +++ b/packages/circuits/circom/utils/non-qv/messageValidator.circom @@ -42,10 +42,6 @@ template MessageValidatorNonQv() { // True when the command is valid; otherwise false. signal output isValid; - // True if the state leaf index is valid - signal output isStateLeafIndexValid; - // True if the vote option index is valid - signal output isVoteOptionIndexValid; // Check (1) - The state leaf index must be valid. // The check ensure that the stateTreeIndex < numSignUps as first validation. @@ -84,6 +80,4 @@ template MessageValidatorNonQv() { ); isValid <== computedIsUpdateValid; - isStateLeafIndexValid <== computedIsStateLeafIndexValid; - isVoteOptionIndexValid <== computedIsVoteOptionIndexValid; } diff --git a/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom b/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom index 28426f5290..ee9150eee5 100644 --- a/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom +++ b/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom @@ -62,13 +62,9 @@ template StateLeafAndBallotTransformerNonQv() { // True when the command is valid; otherwise false. signal output isValid; - // True if the state leaf index is valid - signal output isStateLeafIndexValid; - // True if the vote option index is valid - signal output isVoteOptionIndexValid; // Check if the command / message is valid. - var (computedMessageValidator, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid) = MessageValidatorNonQv()( + var computedMessageValidator = MessageValidatorNonQv()( cmdStateIndex, numSignUps, cmdVoteOptionIndex, @@ -100,6 +96,4 @@ template StateLeafAndBallotTransformerNonQv() { newBallotNonce <== computedNewBallotNonceMux; isValid <== computedMessageValidator; - isStateLeafIndexValid <== computedIsStateLeafIndexValid; - isVoteOptionIndexValid <== computedIsVoteOptionIndexValid; -} \ No newline at end of file +} diff --git a/packages/circuits/circom/utils/qv/messageValidator.circom b/packages/circuits/circom/utils/qv/messageValidator.circom index bee3af3994..b782739959 100644 --- a/packages/circuits/circom/utils/qv/messageValidator.circom +++ b/packages/circuits/circom/utils/qv/messageValidator.circom @@ -42,10 +42,6 @@ template MessageValidator() { // True when the command is valid; otherwise false. signal output isValid; - // True if the state leaf index is valid - signal output isStateLeafIndexValid; - // True if the vote option index is valid - signal output isVoteOptionIndexValid; // Check (1) - The state leaf index must be valid. // The check ensure that the stateTreeIndex < numSignUps as first validation. @@ -90,7 +86,4 @@ template MessageValidator() { ); isValid <== computedIsUpdateValid; - isStateLeafIndexValid <== computedIsStateLeafIndexValid; - isVoteOptionIndexValid <== computedIsVoteOptionIndexValid; } - diff --git a/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom b/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom index 17886de928..1eeaa47d18 100644 --- a/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom +++ b/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom @@ -62,13 +62,9 @@ template StateLeafAndBallotTransformer() { // True when the command is valid; otherwise false. signal output isValid; - // True if the state leaf index is valid - signal output isStateLeafIndexValid; - // True if the vote option index is valid - signal output isVoteOptionIndexValid; // Check if the command / message is valid. - var (computedMessageValidator, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid) = MessageValidator()( + var computedMessageValidator = MessageValidator()( cmdStateIndex, numSignUps, cmdVoteOptionIndex, @@ -100,7 +96,4 @@ template StateLeafAndBallotTransformer() { newBallotNonce <== computedNewBallotNonceMux; isValid <== computedMessageValidator; - isStateLeafIndexValid <== computedIsStateLeafIndexValid; - isVoteOptionIndexValid <== computedIsVoteOptionIndexValid; } - diff --git a/packages/circuits/ts/__tests__/CeremonyParams.test.ts b/packages/circuits/ts/__tests__/CeremonyParams.test.ts index 7d215b6702..1bc64a7c85 100644 --- a/packages/circuits/ts/__tests__/CeremonyParams.test.ts +++ b/packages/circuits/ts/__tests__/CeremonyParams.test.ts @@ -24,8 +24,7 @@ describe("Ceremony param tests", () => { }; const treeDepths = { - // can be 1 - intStateTreeDepth: params.stateLeafBatchDepth, + intStateTreeDepth: 1, voteOptionTreeDepth: params.voteOptionTreeDepth, }; @@ -159,7 +158,7 @@ describe("Ceremony param tests", () => { ballotTree.insert(emptyBallotHash); }); - const currentStateRoot = poll.pollStateLeaves?.root; + const currentStateRoot = poll.pollStateTree?.root; const currentBallotRoot = ballotTree.root; const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; @@ -170,7 +169,7 @@ describe("Ceremony param tests", () => { // The new roots, which should differ, since at least one of the // messages modified a Ballot or State Leaf - const newStateRoot = poll.pollStateLeaves?.root; + const newStateRoot = poll.pollStateTree?.root; const newBallotRoot = poll.ballotTree?.root; expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); diff --git a/packages/circuits/ts/__tests__/MessageToCommand.test.ts b/packages/circuits/ts/__tests__/MessageToCommand.test.ts index 3e57cc61c1..a368150553 100644 --- a/packages/circuits/ts/__tests__/MessageToCommand.test.ts +++ b/packages/circuits/ts/__tests__/MessageToCommand.test.ts @@ -5,7 +5,9 @@ import { Keypair, PCommand, PrivKey } from "maci-domainobjs"; import { circomkitInstance, getSignal } from "./utils/utils"; -describe("MessageToCommand circuit", () => { +describe("MessageToCommand circuit", function test() { + this.timeout(900000); + let circuit: WitnessTester< ["message", "encPubKey", "encPubKey"], [ diff --git a/packages/circuits/ts/__tests__/MessageValidator.test.ts b/packages/circuits/ts/__tests__/MessageValidator.test.ts index 05e16392a8..1d5e0a327f 100644 --- a/packages/circuits/ts/__tests__/MessageValidator.test.ts +++ b/packages/circuits/ts/__tests__/MessageValidator.test.ts @@ -28,7 +28,7 @@ describe("MessageValidator circuit", function test() { "currentVotesForOption", "voteWeight", ], - ["isValid", "isStateLeafIndexValid", "isVoteOptionIndexValid"] + ["isValid"] >; before(async () => { @@ -76,10 +76,6 @@ describe("MessageValidator circuit", function test() { await circuit.expectConstraintPass(witness); const isValid = await getSignal(circuit, witness, "isValid"); expect(isValid.toString()).to.be.eq("1"); - const isStateLeafIndexValid = await getSignal(circuit, witness, "isStateLeafIndexValid"); - expect(isStateLeafIndexValid.toString()).to.be.eq("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the signature is invalid", async () => { @@ -89,10 +85,6 @@ describe("MessageValidator circuit", function test() { 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("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the pubkey is invalid", async () => { @@ -102,10 +94,6 @@ describe("MessageValidator circuit", function test() { 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("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if there are insufficient voice credits", async () => { @@ -115,10 +103,6 @@ describe("MessageValidator circuit", function test() { 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("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the nonce is invalid", async () => { @@ -128,10 +112,6 @@ describe("MessageValidator circuit", function test() { 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("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the state leaf index is invalid", async () => { @@ -141,10 +121,6 @@ describe("MessageValidator circuit", function test() { 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("1"); }); it("should be invalid if the vote option index is invalid", async () => { @@ -154,10 +130,6 @@ describe("MessageValidator circuit", function test() { 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"); }); it("should be invalid if the vote option index is invalid", async () => { @@ -167,10 +139,6 @@ describe("MessageValidator circuit", function test() { 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"); }); }); @@ -193,7 +161,7 @@ describe("MessageValidator circuit", function test() { "currentVotesForOption", "voteWeight", ], - ["isValid", "isStateLeafIndexValid", "isVoteOptionIndexValid"] + ["isValid"] >; before(async () => { @@ -241,10 +209,6 @@ describe("MessageValidator circuit", function test() { await circuit.expectConstraintPass(witness); const isValid = await getSignal(circuit, witness, "isValid"); expect(isValid.toString()).to.be.eq("1"); - const isStateLeafIndexValid = await getSignal(circuit, witness, "isStateLeafIndexValid"); - expect(isStateLeafIndexValid.toString()).to.be.eq("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the signature is invalid", async () => { @@ -254,10 +218,6 @@ describe("MessageValidator circuit", function test() { 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("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the pubkey is invalid", async () => { @@ -267,10 +227,6 @@ describe("MessageValidator circuit", function test() { 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("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if there are insufficient voice credits", async () => { @@ -280,10 +236,6 @@ describe("MessageValidator circuit", function test() { 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("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the nonce is invalid", async () => { @@ -293,10 +245,6 @@ describe("MessageValidator circuit", function test() { 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("1"); - const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); - expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the state leaf index is invalid", async () => { @@ -306,10 +254,6 @@ describe("MessageValidator circuit", function test() { 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("1"); }); it("should be invalid if the vote option index is invalid", async () => { @@ -319,10 +263,6 @@ describe("MessageValidator circuit", function test() { 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"); }); it("should be invalid if the vote option index is invalid", async () => { @@ -332,10 +272,6 @@ describe("MessageValidator circuit", function test() { 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__/PollJoining.test.ts b/packages/circuits/ts/__tests__/PollJoining.test.ts index 677f9c7932..120e882b51 100644 --- a/packages/circuits/ts/__tests__/PollJoining.test.ts +++ b/packages/circuits/ts/__tests__/PollJoining.test.ts @@ -16,7 +16,7 @@ import { } from "./utils/constants"; import { circomkitInstance } from "./utils/utils"; -describe("Poll Joining circuit", function test() { +describe.only("Poll Joining circuit", function test() { this.timeout(900000); const NUM_USERS = 50; @@ -33,7 +33,6 @@ describe("Poll Joining circuit", function test() { "credits", "stateRoot", "actualStateTreeDepth", - "inputHash", ]; let circuit: WitnessTester; diff --git a/packages/circuits/ts/types.ts b/packages/circuits/ts/types.ts index 87f2fb24f0..5e2e7ce1ed 100644 --- a/packages/circuits/ts/types.ts +++ b/packages/circuits/ts/types.ts @@ -54,7 +54,6 @@ export interface IPollJoiningInputs { credits: bigint; stateRoot: bigint; actualStateTreeDepth: bigint; - inputHash: bigint; } /** diff --git a/packages/cli/deploy-config.json b/packages/cli/deploy-config.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/cli/deploy-config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/cli/deployed-contracts.json b/packages/cli/deployed-contracts.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/cli/deployed-contracts.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/cli/tests/constants.ts b/packages/cli/tests/constants.ts index 14397f0067..147b0c86ee 100644 --- a/packages/cli/tests/constants.ts +++ b/packages/cli/tests/constants.ts @@ -144,6 +144,7 @@ export const verifyArgs = async (): Promise> => { pollId: 0n, tallyData, maciAddress: tallyData.maci, + tallyAddress: tallyData.tallyAddress, }; }; diff --git a/packages/cli/tests/e2e/e2e.nonQv.test.ts b/packages/cli/tests/e2e/e2e.nonQv.test.ts index 3335bd99b9..45dd3a0239 100644 --- a/packages/cli/tests/e2e/e2e.nonQv.test.ts +++ b/packages/cli/tests/e2e/e2e.nonQv.test.ts @@ -115,7 +115,7 @@ 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 mergeSignups({ ...mergeSignupsArgs, signer }); + await mergeSignups({ ...mergeSignupsArgs, maciAddress: maciAddresses.maciAddress, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer, useQuadraticVoting: false }); await proveOnChain({ ...proveOnChainArgs, signer }); await verify({ diff --git a/packages/cli/tests/e2e/e2e.test.ts b/packages/cli/tests/e2e/e2e.test.ts index b17ffa4374..4508fd8743 100644 --- a/packages/cli/tests/e2e/e2e.test.ts +++ b/packages/cli/tests/e2e/e2e.test.ts @@ -160,7 +160,7 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ seconds: pollDuration, signer }); - await mergeSignups({ ...mergeSignupsArgs, signer }); + await mergeSignups({ ...mergeSignupsArgs, maciAddress: maciAddresses.maciAddress, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); await verify({ @@ -225,7 +225,7 @@ describe("e2e tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeSignups({ ...mergeSignupsArgs, signer }); + await mergeSignups({ ...mergeSignupsArgs, maciAddress: maciAddresses.maciAddress, signer }); const tallyFileData = await genProofs({ ...genProofsArgs, signer }); await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }); await proveOnChain({ ...proveOnChainArgs, signer }); diff --git a/packages/cli/tests/unit/poll.test.ts b/packages/cli/tests/unit/poll.test.ts index be0bc2e980..c646c2389b 100644 --- a/packages/cli/tests/unit/poll.test.ts +++ b/packages/cli/tests/unit/poll.test.ts @@ -46,20 +46,37 @@ describe("poll", function test() { }); it("should get current poll properly", async () => { - const pollData = await getPoll({ maciAddress: maciAddresses.maciAddress, signer }); - const samePollData = await getPoll({ maciAddress: maciAddresses.maciAddress, pollId: pollData.id, signer }); + const pollData = await getPoll({ + maciAddress: maciAddresses.maciAddress, + tallyAddress: pollAddresses.tally, + signer, + }); + const samePollData = await getPoll({ + maciAddress: maciAddresses.maciAddress, + tallyAddress: pollAddresses.tally, + pollId: pollData.id, + signer, + }); expect(pollData.address).to.eq(pollAddresses.poll); expect(pollData).to.deep.eq(samePollData); }); it("should get finished poll properly", async () => { - const pollData = await getPoll({ maciAddress: maciAddresses.maciAddress, provider: signer.provider! }); + const pollData = await getPoll({ + maciAddress: maciAddresses.maciAddress, + tallyAddress: pollAddresses.tally, + provider: signer.provider!, + }); await timeTravel({ seconds: Number(pollData.duration), signer }); - await mergeSignups({ pollId: BigInt(pollData.id), signer }); + await mergeSignups({ pollId: BigInt(pollData.id), maciAddress: maciAddresses.maciAddress, signer }); - const finishedPollData = await getPoll({ maciAddress: maciAddresses.maciAddress, signer }); + const finishedPollData = await getPoll({ + maciAddress: maciAddresses.maciAddress, + tallyAddress: pollAddresses.tally, + signer, + }); expect(pollData.id).to.eq(finishedPollData.id); expect(pollData.address).to.eq(finishedPollData.address); @@ -67,21 +84,21 @@ describe("poll", function test() { }); it("should throw error if there are no signer and provider", async () => { - await expect(getPoll({ maciAddress: maciAddresses.maciAddress, pollId: -1n })).eventually.rejectedWith( - "No signer and provider are provided", - ); + await expect( + getPoll({ maciAddress: maciAddresses.maciAddress, tallyAddress: pollAddresses.tally, pollId: -1n }), + ).eventually.rejectedWith("No signer and provider are provided"); }); it("should throw error if current poll id is invalid", async () => { - await expect(getPoll({ maciAddress: maciAddresses.maciAddress, pollId: -1n, signer })).eventually.rejectedWith( - "Invalid poll id -1", - ); + await expect( + getPoll({ maciAddress: maciAddresses.maciAddress, tallyAddress: pollAddresses.tally, pollId: -1n, signer }), + ).eventually.rejectedWith("Invalid poll id -1"); }); it("should throw error if current poll is not deployed", async () => { - await expect(getPoll({ maciAddress: maciAddresses.maciAddress, pollId: 9000n, signer })).eventually.rejectedWith( - "MACI contract doesn't have any deployed poll 9000", - ); + await expect( + getPoll({ maciAddress: maciAddresses.maciAddress, tallyAddress: pollAddresses.tally, pollId: 9000n, signer }), + ).eventually.rejectedWith("MACI contract doesn't have any deployed poll 9000"); }); }); }); diff --git a/packages/cli/tests/unit/utils.test.ts b/packages/cli/tests/unit/utils.test.ts index 92421e938c..95abb2fda5 100644 --- a/packages/cli/tests/unit/utils.test.ts +++ b/packages/cli/tests/unit/utils.test.ts @@ -9,15 +9,18 @@ import { compareVks } from "../../ts/utils"; import { validateSalt } from "../../ts/utils/salt"; describe("utils", () => { - const vkPath = path.resolve(__dirname, "data", "testVk.json"); - const vk1 = VerifyingKey.fromJSON(fs.readFileSync(vkPath).toString()); - describe("vks", () => { - it("should return true for two equal VKs", () => { + it("should return true for two equal VKs", async () => { + const vkPath = path.resolve(__dirname, "data", "testVk.json"); + const vk1 = VerifyingKey.fromJSON(await fs.promises.readFile(vkPath).then((res) => res.toString())); + expect(compareVks(vk1, vk1.asContractParam())).to.eq(true); }); - it("should return false for two unequal VKs", () => { + it("should return false for two unequal VKs", async () => { + const vkPath = path.resolve(__dirname, "data", "testVk.json"); + const vk1 = VerifyingKey.fromJSON(await fs.promises.readFile(vkPath).then((res) => res.toString())); + const vk2 = vk1.asContractParam(); vk2.alpha1.x = 9999n; expect(compareVks(vk1, vk2)).to.eq(false); diff --git a/packages/cli/ts/commands/deployPoll.ts b/packages/cli/ts/commands/deployPoll.ts index bff6d257f2..ed6ab06c41 100644 --- a/packages/cli/ts/commands/deployPoll.ts +++ b/packages/cli/ts/commands/deployPoll.ts @@ -81,7 +81,7 @@ export const deployPoll = async ({ const unserializedKey = PubKey.deserialize(coordinatorPubkey); // get the verifier contract - const verifierContractAddress = await readContractAddress("Verifier", network?.name); + const verifierContractAddress = readContractAddress("Verifier", network?.name); const maciContract = MACIFactory.connect(maci, signer); @@ -119,6 +119,11 @@ export const deployPoll = async ({ const log = iface.parseLog(receiptLog as unknown as { topics: string[]; data: string }) as unknown as { args: { _pollId: bigint; + pollAddr: { + poll: string; + messageProcessor: string; + tally: string; + }; }; name: string; }; @@ -131,10 +136,9 @@ export const deployPoll = async ({ // eslint-disable-next-line no-underscore-dangle const pollId = log.args._pollId; - const pollContracts = await maciContract.getPoll(pollId); - pollAddr = pollContracts.poll; - messageProcessorContractAddress = pollContracts.messageProcessor; - tallyContractAddress = pollContracts.tally; + pollAddr = log.args.pollAddr.poll; + messageProcessorContractAddress = log.args.pollAddr.messageProcessor; + tallyContractAddress = log.args.pollAddr.tally; logGreen(quiet, info(`Poll ID: ${pollId.toString()}`)); logGreen(quiet, info(`Poll contract: ${pollAddr}`)); @@ -142,9 +146,9 @@ export const deployPoll = async ({ logGreen(quiet, info(`Tally contract: ${tallyContractAddress}`)); // store the address - await storeContractAddress(`MessageProcessor-${pollId.toString()}`, messageProcessorContractAddress, network?.name); - await storeContractAddress(`Tally-${pollId.toString()}`, tallyContractAddress, network?.name); - await storeContractAddress(`Poll-${pollId.toString()}`, pollAddr, network?.name); + storeContractAddress(`MessageProcessor-${pollId.toString()}`, messageProcessorContractAddress, network?.name); + storeContractAddress(`Tally-${pollId.toString()}`, tallyContractAddress, network?.name); + storeContractAddress(`Poll-${pollId.toString()}`, pollAddr, network?.name); } catch (error) { logError((error as Error).message); } diff --git a/packages/cli/ts/commands/genLocalState.ts b/packages/cli/ts/commands/genLocalState.ts index fda6905183..c350f86d76 100644 --- a/packages/cli/ts/commands/genLocalState.ts +++ b/packages/cli/ts/commands/genLocalState.ts @@ -60,12 +60,12 @@ export const genLocalState = async ({ const coordinatorKeypair = new Keypair(coordinatorMaciPrivKey); const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollContracts = await maciContract.polls(pollId); + const pollAddr = await maciContract.polls(pollId); - if (!(await contractExists(signer.provider!, pollContracts.poll))) { + if (!(await contractExists(signer.provider!, pollAddr))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContract = PollFactory.connect(pollAddr, signer); const [defaultStartBlockSignup, defaultStartBlockPoll, stateRoot, numSignups] = await Promise.all([ maciContract.queryFilter(maciContract.filters.SignUp(), startBlock).then((events) => events[0]?.blockNumber ?? 0), diff --git a/packages/cli/ts/commands/genProofs.ts b/packages/cli/ts/commands/genProofs.ts index e302a10c63..36a5cbce0a 100644 --- a/packages/cli/ts/commands/genProofs.ts +++ b/packages/cli/ts/commands/genProofs.ts @@ -53,6 +53,7 @@ export const genProofs = async ({ blocksPerBatch, endBlock, signer, + tallyAddress, useQuadraticVoting = true, quiet = true, }: GenProofsArgs): Promise => { @@ -140,12 +141,12 @@ export const genProofs = async ({ } const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollContracts = await maciContract.polls(pollId); + const pollAddr = await maciContract.polls(pollId); - if (!(await contractExists(signer.provider!, pollContracts.poll))) { + if (!(await contractExists(signer.provider!, pollAddr))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContract = PollFactory.connect(pollAddr, signer); // Check that the state and message trees have been merged if (!(await pollContract.stateMerged())) { @@ -340,6 +341,9 @@ export const genProofs = async ({ BigInt(asHex(tallyCircuitInputs!.newSpentVoiceCreditSubtotalSalt as BigNumberish)), ); + // get the tally contract address + const tallyContractAddress = tallyAddress || (await readContractAddress(`Tally-${pollId}`, network?.name)); + let newPerVOSpentVoiceCreditsCommitment: bigint | undefined; let newTallyCommitment: bigint; @@ -350,7 +354,7 @@ export const genProofs = async ({ network: network?.name, chainId: network?.chainId.toString(), isQuadratic: useQuadraticVoting, - tallyAddress: pollContracts.tally, + tallyAddress: tallyContractAddress, newTallyCommitment: asHex(tallyCircuitInputs!.newTallyCommitment as BigNumberish), results: { tally: poll.tallyResult.map((x) => x.toString()), diff --git a/packages/cli/ts/commands/joinPoll.ts b/packages/cli/ts/commands/joinPoll.ts index c359f37f6d..a3a3d7fd5b 100644 --- a/packages/cli/ts/commands/joinPoll.ts +++ b/packages/cli/ts/commands/joinPoll.ts @@ -3,7 +3,7 @@ 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 { poseidon, stringifyBigInts } from "maci-crypto"; import { IVkObjectParams, Keypair, PrivKey, PubKey, StateLeaf } from "maci-domainobjs"; import assert from "assert"; @@ -120,7 +120,6 @@ const joiningCircuitInputs = ( 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"); @@ -156,7 +155,6 @@ const joiningCircuitInputs = ( 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(), @@ -169,7 +167,6 @@ const joiningCircuitInputs = ( credits, stateRoot, actualStateTreeDepth, - inputHash, }; return stringifyBigInts(circuitInputs) as unknown as IPollJoiningCircuitInputs; diff --git a/packages/cli/ts/commands/mergeSignups.ts b/packages/cli/ts/commands/mergeSignups.ts index a73d6e5790..d92a081c8a 100644 --- a/packages/cli/ts/commands/mergeSignups.ts +++ b/packages/cli/ts/commands/mergeSignups.ts @@ -31,13 +31,13 @@ export const mergeSignups = async ({ pollId, maciAddress, signer, quiet = true } } const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollContracts = await maciContract.polls(pollId); + const pollAddress = await maciContract.polls(pollId); - if (!(await contractExists(signer.provider!, pollContracts.poll))) { + if (!(await contractExists(signer.provider!, pollAddress))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContract = PollFactory.connect(pollAddress, signer); // check if it's time to merge the message AQ const dd = await pollContract.getDeployTimeAndDuration(); diff --git a/packages/cli/ts/commands/poll.ts b/packages/cli/ts/commands/poll.ts index f6d2ede98c..a0cb3995d1 100644 --- a/packages/cli/ts/commands/poll.ts +++ b/packages/cli/ts/commands/poll.ts @@ -17,6 +17,7 @@ import { logError, logGreen, success } from "../utils/theme"; */ export const getPoll = async ({ maciAddress, + tallyAddress, signer, provider, pollId, @@ -36,9 +37,9 @@ export const getPoll = async ({ logError(`Invalid poll id ${id}`); } - const { poll: pollAddress, tally: tallyAddress } = await maciContract.polls(id); + const pollAddress = await maciContract.polls(id); - if (pollAddress === ZeroAddress || tallyAddress === ZeroAddress) { + if (pollAddress === ZeroAddress) { logError(`MACI contract doesn't have any deployed poll ${id}`); } diff --git a/packages/cli/ts/commands/proveOnChain.ts b/packages/cli/ts/commands/proveOnChain.ts index c3374c940e..79ebd2f1d7 100644 --- a/packages/cli/ts/commands/proveOnChain.ts +++ b/packages/cli/ts/commands/proveOnChain.ts @@ -11,7 +11,7 @@ import { type IVerifyingKeyStruct, } from "maci-contracts"; import { STATE_TREE_ARITY } from "maci-core"; -import { G1Point, G2Point, hashLeftRight } from "maci-crypto"; +import { G1Point, G2Point, genTreeProof } from "maci-crypto"; import { VerifyingKey } from "maci-domainobjs"; import fs from "fs"; @@ -29,6 +29,7 @@ import { logYellow, readContractAddress, success, + type TallyData, type Proof, type ProveOnChainArgs, } from "../utils"; @@ -41,36 +42,59 @@ export const proveOnChain = async ({ pollId, proofDir, maciAddress, - signer, + messageProcessorAddress, + tallyAddress, tallyFile, + signer, quiet = true, }: ProveOnChainArgs): Promise => { banner(quiet); const network = await signer.provider?.getNetwork(); - // check existence of contract addresses const maciContractAddress = maciAddress || (await readContractAddress("MACI", network?.name)); + // check existence of contract addresses if (!maciContractAddress) { logError("MACI contract address is empty"); } + const messageProcessorContractAddress = + messageProcessorAddress || (await readContractAddress(`MessageProcessor-${pollId}`, network?.name)); + + if (!messageProcessorContractAddress) { + logError("MessageProcessor contract address is empty"); + } + + const tallyContractAddress = tallyAddress || (await readContractAddress(`Tally-${pollId}`, network?.name)); + + if (!tallyContractAddress) { + logError("Tally contract address is empty"); + } + // check contracts are deployed on chain if (!(await contractExists(signer.provider!, maciContractAddress))) { logError("MACI contract does not exist"); } + if (!(await contractExists(signer.provider!, messageProcessorContractAddress))) { + logError("MessageProcessor contract does not exist"); + } + + if (!(await contractExists(signer.provider!, tallyContractAddress))) { + logError("Tally contract does not exist"); + } + const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollContracts = await maciContract.polls(pollId); + const pollAddr = await maciContract.polls(pollId); - if (!(await contractExists(signer.provider!, pollContracts.poll))) { + if (!(await contractExists(signer.provider!, pollAddr))) { logError("There is no Poll contract with this poll ID linked to the specified MACI contract."); } - const pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContract = PollFactory.connect(pollAddr, signer); - const mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); - const tallyContract = TallyFactory.connect(pollContracts.tally, signer); + const mpContract = MessageProcessorFactory.connect(messageProcessorContractAddress, signer); + const tallyContract = TallyFactory.connect(tallyContractAddress, signer); const vkRegistryContractAddress = await tallyContract.vkRegistry(); diff --git a/packages/cli/ts/commands/publish.ts b/packages/cli/ts/commands/publish.ts index 5c6d205c99..dee42b3a13 100644 --- a/packages/cli/ts/commands/publish.ts +++ b/packages/cli/ts/commands/publish.ts @@ -78,13 +78,13 @@ export const publish = async ({ } const maciContract = MACIFactory.connect(maciAddress, signer); - const pollContracts = await maciContract.getPoll(pollId); + const pollAddress = await maciContract.getPoll(pollId); - if (!(await contractExists(signer.provider!, pollContracts.poll))) { + if (!(await contractExists(signer.provider!, pollAddress))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContract = PollFactory.connect(pollAddress, signer); const maxVoteOptions = Number(await pollContract.maxVoteOptions()); const coordinatorPubKeyResult = await pollContract.coordinatorPubKey(); @@ -167,9 +167,9 @@ export const publishBatch = async ({ const userMaciPubKey = PubKey.deserialize(publicKey); const userMaciPrivKey = PrivKey.deserialize(privateKey); const maciContract = MACIFactory.connect(maciAddress, signer); - const pollContracts = await maciContract.getPoll(pollId); + const pollAddress = await maciContract.getPoll(pollId); - const pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContract = PollFactory.connect(pollAddress, signer); const [maxVoteOptions, coordinatorPubKeyResult] = await Promise.all([ pollContract.maxVoteOptions().then(Number), diff --git a/packages/cli/ts/commands/showContracts.ts b/packages/cli/ts/commands/showContracts.ts index 3b6d12a5f2..ac48532f01 100644 --- a/packages/cli/ts/commands/showContracts.ts +++ b/packages/cli/ts/commands/showContracts.ts @@ -6,14 +6,16 @@ import { banner, contractAddressesStore, logGreen, info, logError } from "../uti * Utility to print all contracts that have been deployed using maci-cli * @param quiet - whether to log the output */ -export const showContracts = (quiet = false): void => { +export const showContracts = async (quiet = false): Promise => { banner(quiet); if (!fs.existsSync(contractAddressesStore)) { logError("No contracts have been deployed yet"); } - const data = JSON.parse(fs.readFileSync(contractAddressesStore, "utf8").toString()) as Record; + const data = JSON.parse( + await fs.promises.readFile(contractAddressesStore, "utf8").then((result) => result.toString()), + ) as Record; Object.entries(data).forEach(([key, value]) => { logGreen(quiet, info(`${key}: ${value}`)); diff --git a/packages/cli/ts/commands/verify.ts b/packages/cli/ts/commands/verify.ts index 24072b427f..7b24948eac 100644 --- a/packages/cli/ts/commands/verify.ts +++ b/packages/cli/ts/commands/verify.ts @@ -16,12 +16,30 @@ import { verifyPerVOSpentVoiceCredits, verifyTallyResults } from "../utils/verif * Verify the results of a poll on-chain * @param VerifyArgs - The arguments for the verify command */ -export const verify = async ({ pollId, tallyData, maciAddress, signer, quiet = true }: VerifyArgs): Promise => { +export const verify = async ({ + pollId, + tallyData, + maciAddress, + tallyAddress, + signer, + quiet = true, +}: VerifyArgs): Promise => { banner(quiet); const tallyResults = tallyData; const useQv = tallyResults.isQuadratic; + // we prioritize the tally file data + const tallyContractAddress = tallyResults.tallyAddress || tallyAddress; + + if (!tallyContractAddress) { + logError("Tally contract address is empty"); + } + + if (!(await contractExists(signer.provider!, tallyContractAddress))) { + logError(`Error: there is no Tally contract deployed at ${tallyContractAddress}.`); + } + // prioritize the tally file data const maciContractAddress = tallyResults.maci || maciAddress; @@ -36,11 +54,11 @@ export const verify = async ({ pollId, tallyData, maciAddress, signer, quiet = t // get the contract objects const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollContracts = await maciContract.polls(pollId); + const pollAddr = await maciContract.polls(pollId); - const pollContract = PollFactory.connect(pollContracts.poll, signer); + const pollContract = PollFactory.connect(pollAddr, signer); - const tallyContract = TallyFactory.connect(pollContracts.tally, signer); + const tallyContract = TallyFactory.connect(tallyContractAddress, signer); // verification const onChainTallyCommitment = BigInt(await tallyContract.tallyCommitment()); diff --git a/packages/cli/ts/index.ts b/packages/cli/ts/index.ts index 056a0d2e80..117e70f7d4 100644 --- a/packages/cli/ts/index.ts +++ b/packages/cli/ts/index.ts @@ -166,9 +166,9 @@ program .description("show the deployed contract addresses") .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .option("-r, --rpc-provider ", "the rpc provider URL") - .action((cmdObj) => { + .action(async (cmdObj) => { try { - showContracts(cmdObj.quiet); + await showContracts(cmdObj.quiet); } catch (error) { program.error((error as Error).message, { exitCode: 1 }); } @@ -243,7 +243,7 @@ program const signer = await getSigner(); const network = await signer.provider?.getNetwork(); - const maciAddress = cmdObj.maciAddress || readContractAddress("MACI", network?.name); + const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); const privateKey = cmdObj.privKey || (await promptSensitiveValue("Insert your MACI private key")); await joinPoll({ @@ -514,7 +514,7 @@ program const signer = await getSigner(); const network = await signer.provider?.getNetwork(); - const maciAddress = cmdObj.maciAddress || readContractAddress("MACI", network?.name); + const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); await isJoinedUser({ pollPubKey: cmdObj.pubkey, @@ -534,6 +534,7 @@ program .description("Get deployed poll from MACI contract") .option("-p, --poll ", "the poll id") .option("-x, --maci-address ", "the MACI contract address") + .option("-ta, --tally-address ", "the tally contract address") .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .action(async (cmdObj) => { try { @@ -541,10 +542,12 @@ program const network = await signer.provider?.getNetwork(); const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); + const tallyAddress = cmdObj.tallyAddress || (await readContractAddress(`Tally-${cmdObj.poll}`, network?.name)); await getPoll({ pollId: cmdObj.poll, maciAddress, + tallyAddress, signer, quiet: cmdObj.quiet, }); @@ -577,6 +580,7 @@ program "the tally file with results, per vote option spent credits, spent voice credits total", ) .option("-x, --maci-address ", "the MACI contract address") + .option("-ta, --tally-address ", "the tally contract address") .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .option("-r, --rpc-provider ", "the rpc provider URL") .action(async (cmdObj) => { @@ -593,11 +597,16 @@ program const tallyData = JSON.parse(await fs.promises.readFile(cmdObj.tallyFile, { encoding: "utf8" })) as TallyData; const maciAddress = tallyData.maci || cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); + const tallyAddress = + tallyData.tallyAddress || + cmdObj.tallyAddress || + (await readContractAddress(`Tally-${cmdObj.pollId}`, network?.name)); await verify({ tallyData, pollId: cmdObj.pollId, maciAddress, + tallyAddress, quiet: cmdObj.quiet, signer, }); @@ -610,6 +619,7 @@ program .description("generate the proofs for a poll") .option("-sk, --privkey ", "your serialized MACI private key") .option("-x, --maci-address ", "the MACI contract address") + .option("-ta, --tally-address ", "the tally contract address") .requiredOption("-o, --poll-id ", "the poll id", BigInt) .requiredOption( "-t, --tally-file ", @@ -657,6 +667,7 @@ program tallyDatFile: cmdObj.tallyWitnessdat, coordinatorPrivKey: cmdObj.privkey, maciAddress: cmdObj.maciAddress, + tallyAddress: cmdObj.tallyAddress, transactionHash: cmdObj.transactionHash, processWasm: cmdObj.processWasm, tallyWasm: cmdObj.tallyWasm, @@ -720,6 +731,8 @@ program .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") + .option("-p, --message-processor-address ", "the message processor contract address") + .option("-ta, --tally-contract ", "the tally contract address") .requiredOption("-f, --proof-dir ", "the proof output directory from the genProofs subcommand") .action(async (cmdObj) => { try { @@ -730,6 +743,8 @@ program tallyFile: cmdObj.tallyFile, proofDir: cmdObj.proofDir, maciAddress: cmdObj.maciAddress, + messageProcessorAddress: cmdObj.messageProcessorAddress, + tallyAddress: cmdObj.tallyContract, quiet: cmdObj.quiet, signer, }); diff --git a/packages/cli/ts/utils/interfaces.ts b/packages/cli/ts/utils/interfaces.ts index f1054c0b83..305f76ab85 100644 --- a/packages/cli/ts/utils/interfaces.ts +++ b/packages/cli/ts/utils/interfaces.ts @@ -599,6 +599,11 @@ export interface GenProofsArgs { */ maciAddress?: string; + /** + * The address of the Tally contract + */ + tallyAddress?: string; + /** * The transaction hash of the first transaction */ @@ -709,6 +714,16 @@ export interface ProveOnChainArgs { */ maciAddress?: string; + /** + * The address of the MessageProcessor contract + */ + messageProcessorAddress?: string; + + /** + * The address of the Tally contract + */ + tallyAddress?: string; + /** * Whether to log the output */ @@ -1014,6 +1029,11 @@ export interface IGetPollArgs { */ maciAddress: string; + /** + * The address of the Tally contract + */ + tallyAddress: string; + /** * The poll id. If not specified, latest poll id will be used */ @@ -1089,6 +1109,11 @@ export interface VerifyArgs { */ maciAddress: string; + /** + * The address of the Tally contract + */ + tallyAddress: string; + /** * Whether to log the output */ diff --git a/packages/contracts/contracts/MACI.sol b/packages/contracts/contracts/MACI.sol index f05ecbd0f5..e32f8d1f4c 100644 --- a/packages/contracts/contracts/MACI.sol +++ b/packages/contracts/contracts/MACI.sol @@ -151,7 +151,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { bytes memory _initialVoiceCreditProxyData ) public virtual { // ensure we do not have more signups than what the circuits support - if (lazyIMTData.numberOfLeaves >= maxSignups) revert TooManySignups(); + if (leanIMTData.size >= maxSignups) revert TooManySignups(); // ensure that the public key is on the baby jubjub curve if (!CurveBabyJubJub.isOnCurve(_pubKey.x, _pubKey.y)) { @@ -218,12 +218,6 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { vkRegistry: IVkRegistry(_vkRegistry) }); - ExtContracts memory extContracts = ExtContracts({ - maci: IMACI(address(this)), - verifier: IVerifier(_verifier), - vkRegistry: IVkRegistry(_vkRegistry) - }); - address p = pollFactory.deploy( _duration, maxVoteOptions, diff --git a/packages/contracts/contracts/MessageProcessor.sol b/packages/contracts/contracts/MessageProcessor.sol index bc6e8d23dd..f557e35fc5 100644 --- a/packages/contracts/contracts/MessageProcessor.sol +++ b/packages/contracts/contracts/MessageProcessor.sol @@ -106,7 +106,6 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes !verifyProcessProof( currentBatchIndex, outputBatchHash, - sbCommitment, _newSbCommitment, messageBatchSize, voteOptionTreeDepth, @@ -153,7 +152,6 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes /// @dev used to update the sbCommitment /// @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 @@ -162,7 +160,6 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes function verifyProcessProof( uint256 _currentBatchIndex, uint256 _outputBatchHash, - uint256 _currentSbCommitment, uint256 _newSbCommitment, uint8 _messageBatchSize, uint8 _voteOptionTreeDepth, @@ -171,10 +168,13 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes // get the tree depths // 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); + uint256[] memory publicCircuitInputs = getPublicCircuitInputs( + _currentBatchIndex, + _newSbCommitment, + _outputBatchHash + ); // Get the verifying key from the VkRegistry VerifyingKey memory vk = vkRegistry.getProcessVk( diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index 6bf6d5377c..ab577a8ac8 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -1,15 +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 { LazyIMTData, InternalLazyIMT } from "./trees/LazyIMT.sol"; import { IMACI } from "./interfaces/IMACI.sol"; import { IPoll } from "./interfaces/IPoll.sol"; -import { IVerifier } from "./interfaces/IVerifier.sol"; -import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; import { Utilities } from "./utilities/Utilities.sol"; import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol"; @@ -276,10 +273,10 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { /// @param _proof The zk-SNARK proof function joinPoll( uint256 _nullifier, - PubKey memory _pubKey, + PubKey calldata _pubKey, uint256 _newVoiceCreditBalance, uint256 _stateRootIndex, - uint256[8] memory _proof + uint256[8] calldata _proof ) external { // Whether the user has already joined if (pollNullifier[_nullifier]) { @@ -314,7 +311,7 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { uint256 _nullifier, uint256 _voiceCreditBalance, uint256 _index, - PubKey memory _pubKey, + PubKey calldata _pubKey, uint256[8] memory _proof ) internal returns (bool isValid) { // Get the verifying key from the VkRegistry @@ -324,15 +321,30 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { ); // Generate the circuit public input - uint256[] memory input = new uint256[](5); - input[0] = _nullifier; - input[1] = _voiceCreditBalance; - input[2] = extContracts.maci.getStateRootOnIndexedSignUp(_index); - input[3] = _pubKey.x; - input[4] = _pubKey.y; - uint256 publicInputHash = sha256Hash(input); - - isValid = extContracts.verifier.verify(_proof, vk, publicInputHash); + uint256[] memory circuitPublicInputs = getPublicCircuitInputs(_nullifier, _voiceCreditBalance, _index, _pubKey); + + isValid = extContracts.verifier.verify(_proof, vk, circuitPublicInputs); + } + + /// @notice Get public circuit inputs for poll joining circuit + /// @param _nullifier Hashed user's private key to check whether user has already voted + /// @param _voiceCreditBalance User's credit balance for voting + /// @param _index Index of the MACI's stateRootOnSignUp when the user signed up + /// @param _pubKey Poll user's public key + /// @return publicInputs Public circuit inputs + function getPublicCircuitInputs( + uint256 _nullifier, + uint256 _voiceCreditBalance, + uint256 _index, + PubKey calldata _pubKey + ) public returns (uint256[] memory publicInputs) { + publicInputs = new uint256[](5); + + publicInputs[0] = _nullifier; + publicInputs[1] = _voiceCreditBalance; + publicInputs[2] = extContracts.maci.getStateRootOnIndexedSignUp(_index); + publicInputs[3] = _pubKey.x; + publicInputs[4] = _pubKey.y; } /// @inheritdoc IPoll @@ -348,7 +360,7 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { // Set currentSbCommitment uint256[3] memory sb; - sb[0] = _mergedStateRoot; + sb[0] = mergedStateRoot; sb[1] = emptyBallotRoots[treeDepths.voteOptionTreeDepth - 1]; sb[2] = uint256(0); diff --git a/packages/contracts/contracts/PollFactory.sol b/packages/contracts/contracts/PollFactory.sol index 1d0c46a27e..66de4b341b 100644 --- a/packages/contracts/contracts/PollFactory.sol +++ b/packages/contracts/contracts/PollFactory.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import { IMACI } from "./interfaces/IMACI.sol"; import { Params } from "./utilities/Params.sol"; import { DomainObjs } from "./utilities/DomainObjs.sol"; import { Poll } from "./Poll.sol"; diff --git a/packages/contracts/contracts/Tally.sol b/packages/contracts/contracts/Tally.sol index 06673822be..568d30c4a8 100644 --- a/packages/contracts/contracts/Tally.sol +++ b/packages/contracts/contracts/Tally.sol @@ -413,7 +413,7 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs, ITa * @param _tallyResultProof Proofs of correctness of the vote tally results. * @param _tallyResultSalt the respective salt in the results object in the tally.json * @param _spentVoiceCreditsHash hashLeftRight(number of spent voice credits, spent salt) - * @param _perVOSpentVoiceCreditsHash hashLeftRight(merkle root of the no spent voice credits per vote option, perVOSpentVoiceCredits salt) + * @param _perVOSpentVoiceCreditsHash hashLeftRight(root of noSpentVoiceCreditsPerVoteOption, perVOSpentVoiceCredits) * @param _voteOptionTreeDepth vote option tree depth */ function addTallyResult( diff --git a/packages/contracts/contracts/interfaces/IMACI.sol b/packages/contracts/contracts/interfaces/IMACI.sol index eb4fc09edb..b1be21d46b 100644 --- a/packages/contracts/contracts/interfaces/IMACI.sol +++ b/packages/contracts/contracts/interfaces/IMACI.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import { IVerifier } from "./IVerifier.sol"; -import { IVkRegistry } from "./IVkRegistry.sol"; -import { DomainObjs } from "../utilities/DomainObjs.sol"; - /// @title IMACI /// @notice MACI interface interface IMACI { diff --git a/packages/contracts/contracts/interfaces/IPoll.sol b/packages/contracts/contracts/interfaces/IPoll.sol index c918791ed0..50318db07f 100644 --- a/packages/contracts/contracts/interfaces/IPoll.sol +++ b/packages/contracts/contracts/interfaces/IPoll.sol @@ -10,10 +10,10 @@ interface IPoll { /// @notice Join the poll function joinPoll( uint256 _nullifier, - DomainObjs.PubKey memory _pubKey, + DomainObjs.PubKey calldata _pubKey, uint256 _newVoiceCreditBalance, uint256 _stateRootIndex, - uint256[8] memory _proof + uint256[8] calldata _proof ) external; /// @notice The number of messages which have been processed and the number of signups diff --git a/packages/contracts/contracts/interfaces/ITally.sol b/packages/contracts/contracts/interfaces/ITally.sol index 1ebcd0f881..d61e65fd88 100644 --- a/packages/contracts/contracts/interfaces/ITally.sol +++ b/packages/contracts/contracts/interfaces/ITally.sol @@ -18,11 +18,12 @@ interface ITally { uint256 totalSpentSalt; /// @param tallyResultSalt the respective salt in the results object in the tally.json uint256 tallyResultSalt; - /// @param newResultsCommitment The salted commitment of the vote tally for this batch of leaves plus the vote tally from currentResults + /// @param newResultsCommitment The salted commitment of the vote tally for this batch of + /// leaves plus the vote tally from currentResults uint256 newResultsCommitment; /// @param spentVoiceCreditsHash hashLeftRight(number of spent voice credits, spent salt) uint256 spentVoiceCreditsHash; - /// @param perVOSpentVoiceCreditsHash hashLeftRight(merkle root of the no spent voice credits per vote option, perVOSpentVoiceCredits salt) + /// @param perVOSpentVoiceCreditsHash hashLeftRight(root noSpentVoiceCreditsPerVoteOption, perVOSpentVoiceCredits) uint256 perVOSpentVoiceCreditsHash; } diff --git a/packages/contracts/contracts/trees/LazyIMT.sol b/packages/contracts/contracts/trees/LazyIMT.sol index 92af140fb9..2d1144fc83 100644 --- a/packages/contracts/contracts/trees/LazyIMT.sol +++ b/packages/contracts/contracts/trees/LazyIMT.sol @@ -21,7 +21,7 @@ error AmbiguousDepth(); /// @dev A LazyIMT with Zeroes value set to the hash of /// a MACI Blank State Leaf /// @notice This implementation is taken from zk-kit -/// https://github.com/privacy-scaling-explorations/zk-kit/blob/main/packages/imt.sol/contracts/internal/InternalLazyIMT.sol +/// https://github.com/privacy-scaling-explorations/zk-kit/blob/main/packages/imt.sol /// and modified to work with MACI. library InternalLazyIMT { uint256 internal constant MAX_DEPTH = 32; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 01aee349f4..e3cf5adca8 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -168,7 +168,7 @@ "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@openzeppelin/contracts": "^5.1.0", "@openzeppelin/merkle-tree": "^1.0.7", - "@pcd/util": "^0.8.0", + "@pcd/util": "^0.9.0", "@zk-kit/imt.sol": "2.0.0-beta.12", "@zk-kit/lean-imt": "^2.1.0", "circomlibjs": "^0.1.7", diff --git a/packages/contracts/tasks/helpers/ProofGenerator.ts b/packages/contracts/tasks/helpers/ProofGenerator.ts index 586c8d41c2..7583a0c550 100644 --- a/packages/contracts/tasks/helpers/ProofGenerator.ts +++ b/packages/contracts/tasks/helpers/ProofGenerator.ts @@ -120,7 +120,7 @@ export class ProofGenerator { const defaultEndBlock = await Promise.all([ pollContract - .queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock) + .queryFilter(pollContract.filters.MergeState(stateRoot, numSignups), fromBlock) .then((events) => events[events.length - 1]?.blockNumber), ]).then((blocks) => Math.max(...blocks)); diff --git a/packages/contracts/tests/MACI.test.ts b/packages/contracts/tests/MACI.test.ts index 3fa14ac8b0..c12197f54f 100644 --- a/packages/contracts/tests/MACI.test.ts +++ b/packages/contracts/tests/MACI.test.ts @@ -301,7 +301,7 @@ describe("MACI", function test() { it("should allow a Poll contract to merge the state tree (calculate the state root)", async () => { await timeTravel(signer.provider as unknown as EthereumProvider, Number(duration) + 1); - const tx = await pollContract.mergeMaciState(); + const tx = await pollContract.mergeState(); const receipt = await tx.wait(); expect(receipt?.status).to.eq(1); }); diff --git a/packages/contracts/tests/PollFactory.test.ts b/packages/contracts/tests/PollFactory.test.ts index af65c4b9e7..12c387b06f 100644 --- a/packages/contracts/tests/PollFactory.test.ts +++ b/packages/contracts/tests/PollFactory.test.ts @@ -49,19 +49,5 @@ describe("pollFactory", () => { 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(), - extContracts, - ), - ).to.be.revertedWithCustomError(pollFactory, "InvalidMaxVoteOptions"); - }); }); }); diff --git a/packages/contracts/tests/Tally.test.ts b/packages/contracts/tests/Tally.test.ts index 5083a6a3ab..9f9f4c2ff5 100644 --- a/packages/contracts/tests/Tally.test.ts +++ b/packages/contracts/tests/Tally.test.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { AbiCoder, BigNumberish, Signer } from "ethers"; import { EthereumProvider } from "hardhat/types"; import { MaciState, Poll, IProcessMessagesCircuitInputs, ITallyCircuitInputs } from "maci-core"; -import { genTreeCommitment, genTreeProof, hashLeftRight, NOTHING_UP_MY_SLEEVE } from "maci-crypto"; +import { genTreeCommitment, genTreeProof, hashLeftRight, NOTHING_UP_MY_SLEEVE, poseidon } from "maci-crypto"; import { Keypair, Message, PubKey } from "maci-domainobjs"; import { EMode } from "../ts/constants"; diff --git a/packages/core/ts/Poll.ts b/packages/core/ts/Poll.ts index c647274cf6..856fc34250 100644 --- a/packages/core/ts/Poll.ts +++ b/packages/core/ts/Poll.ts @@ -448,7 +448,6 @@ export class Poll implements IPoll { 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"); @@ -482,9 +481,6 @@ export class Poll implements IPoll { // 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(), @@ -496,7 +492,6 @@ export class Poll implements IPoll { credits, stateRoot, actualStateTreeDepth, - inputHash, }; return stringifyBigInts(circuitInputs) as unknown as IPollJoiningCircuitInputs; @@ -795,6 +790,8 @@ export class Poll implements IPoll { // this will be the hash of the roots with a salt circuitInputs.newSbCommitment = hash3([newStateRoot, newBallotRoot, newSbSalt]); + const coordinatorPublicKeyHash = this.coordinatorKeypair.pubKey.hash(); + // If this is the last batch, release the lock if (this.numBatchesProcessed * batchSize >= this.messages.length) { this.maciStateRef.pollBeingProcessed = false; @@ -802,9 +799,12 @@ export class Poll implements IPoll { // ensure we pass the dynamic tree depth circuitInputs.actualStateTreeDepth = this.actualStateTreeDepth.toString(); - circuitInputs.coordinatorPublicKeyHash = this.coordinatorKeypair.pubKey.hash(); - return stringifyBigInts(circuitInputs) as unknown as IProcessMessagesCircuitInputs; + return stringifyBigInts({ + ...circuitInputs, + coordinatorPublicKeyHash, + maxVoteOptions: BigInt(this.maxVoteOptions), + }) as unknown as IProcessMessagesCircuitInputs; }; /** @@ -881,10 +881,11 @@ export class Poll implements IPoll { return stringifyBigInts({ numSignUps: BigInt(this.numSignups), batchEndIndex: BigInt(batchEndIndex), - index: BigInt(index), + index: BigInt(0), inputBatchHash, outputBatchHash, msgs, + actualStateTreeDepth: BigInt(this.actualStateTreeDepth), coordPrivKey: this.coordinatorKeypair.privKey.asCircuitInputs(), encPubKeys: encPubKeys.map((x) => x.asCircuitInputs()), currentStateRoot, diff --git a/packages/core/ts/__tests__/utils.test.ts b/packages/core/ts/__tests__/utils.test.ts index 34aef4647b..79256fc8d0 100644 --- a/packages/core/ts/__tests__/utils.test.ts +++ b/packages/core/ts/__tests__/utils.test.ts @@ -4,8 +4,8 @@ import { genProcessVkSig, genTallyVkSig } from "../utils/utils"; describe("Utils", () => { it("genProcessVkSig should work", () => { - const result = genProcessVkSig(1, 2, 3, 4); - expect(result).to.equal(25108406941546723055683440059751604127909689873435325366275n); + const result = genProcessVkSig(1, 2, 20); + expect(result).to.equal(6805647338418769269285938892709073780738n); }); it("genTallyVkSig should work", () => { diff --git a/packages/core/ts/utils/types.ts b/packages/core/ts/utils/types.ts index 5496b3d51c..7ae79ec309 100644 --- a/packages/core/ts/utils/types.ts +++ b/packages/core/ts/utils/types.ts @@ -157,7 +157,6 @@ export interface IPollJoiningCircuitInputs { credits: string; stateRoot: string; actualStateTreeDepth: string; - inputHash: string; } /** * An interface describing the circuit inputs to the ProcessMessage circuit diff --git a/packages/integrationTests/ts/__tests__/integration.test.ts b/packages/integrationTests/ts/__tests__/integration.test.ts index 3f21f4e771..526d443d16 100644 --- a/packages/integrationTests/ts/__tests__/integration.test.ts +++ b/packages/integrationTests/ts/__tests__/integration.test.ts @@ -59,6 +59,7 @@ describe("Integration tests", function test() { // global variables we need shared between tests let maciState: MaciState; let contracts: DeployedContracts; + let pollContracts: PollContracts; let pollId: bigint; let signer: Signer; const coordinatorKeypair = new Keypair(); @@ -107,7 +108,7 @@ describe("Integration tests", function test() { const maxVoteOptions = 25; // 4. create a poll - await deployPoll({ + pollContracts = await deployPoll({ pollDuration: duration, intStateTreeDepth: INT_STATE_TREE_DEPTH, messageBatchSize: MESSAGE_BATCH_SIZE, @@ -325,6 +326,8 @@ describe("Integration tests", function test() { tallyFile: path.resolve(__dirname, "../../../cli/tally.json"), proofDir: path.resolve(__dirname, "../../../cli/proofs"), maciAddress: contracts.maciAddress, + messageProcessorAddress: pollContracts.messageProcessor, + tallyAddress: pollContracts.tally, signer, }), ).to.not.be.rejected; @@ -335,6 +338,7 @@ describe("Integration tests", function test() { pollId, tallyData, maciAddress: contracts.maciAddress, + tallyAddress: pollContracts.tally, signer, }), ).to.not.be.rejected; diff --git a/packages/integrationTests/ts/__tests__/maci-keys.test.ts b/packages/integrationTests/ts/__tests__/maci-keys.test.ts index 3b3ce7ebb5..fe647eb754 100644 --- a/packages/integrationTests/ts/__tests__/maci-keys.test.ts +++ b/packages/integrationTests/ts/__tests__/maci-keys.test.ts @@ -94,7 +94,7 @@ describe("integration tests private/public/keypair", () => { ); // we know it's the first poll so id is 0 - pollContract = PollFactory.connect((await maci.polls(0)).poll, signer); + pollContract = PollFactory.connect(await maci.polls(0), signer); }); it("should have the correct coordinator pub key set on chain", async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afced5f4d3..26edde1093 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -352,8 +352,8 @@ importers: specifier: ^1.0.7 version: 1.0.7 '@pcd/util': - specifier: ^0.8.0 - version: 0.8.0 + specifier: ^0.9.0 + version: 0.9.0 '@zk-kit/imt.sol': specifier: 2.0.0-beta.12 version: 2.0.0-beta.12 @@ -2438,8 +2438,8 @@ packages: '@openzeppelin/merkle-tree@1.0.7': resolution: {integrity: sha512-i93t0YYv6ZxTCYU3CdO5Q+DXK0JH10A4dCBOMlzYbX+ujTXm+k1lXiEyVqmf94t3sqmv8sm/XT5zTa0+efnPgQ==} - '@pcd/util@0.8.0': - resolution: {integrity: sha512-KG1V8O+o8Awr9iHH9I1eVeqHGlm3F6zqzvOJzXKe/bViE8A31DuiZoeHCVPFRA5+03d+qY4tWQJKOxYGQL6Kww==} + '@pcd/util@0.9.0': + resolution: {integrity: sha512-PI9XR9DY7Okn8AJyOfFPhSihewh5VVTyFf5Hm+qaH09t0MlV8ZbMAo9hbiyB5NtHJo7fEEM5BHTRCPpMeVEJcA==} '@peculiar/asn1-schema@2.3.8': resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==} @@ -13653,7 +13653,7 @@ snapshots: '@ethersproject/constants': 5.7.0 '@ethersproject/keccak256': 5.7.0 - '@pcd/util@0.8.0': + '@pcd/util@0.9.0': dependencies: buffer: 6.0.3 email-validator: 2.0.4 From bc6d4c28ce19d7d84d86fb6c96ca6a5f26780558 Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:13:05 -0600 Subject: [PATCH 04/14] fix: anon poll joining - [x] Fix hardhat tasks - [x] Fix tests for circuits --- .../circom/core/qv/processMessages.circom | 4 -- .../circuits/ts/__tests__/PollJoining.test.ts | 2 +- packages/circuits/ts/types.ts | 1 - packages/cli/tests/e2e/keyChange.test.ts | 2 +- .../contracts/contracts/MessageProcessor.sol | 28 ++++---- packages/contracts/contracts/Poll.sol | 10 +-- packages/contracts/deploy-config-example.json | 46 +++++++++++- .../tasks/deploy/maci/09-vkRegistry.ts | 13 +++- .../contracts/tasks/deploy/poll/01-poll.ts | 2 +- packages/contracts/tasks/helpers/Prover.ts | 70 ++++++++----------- packages/contracts/tasks/runner/prove.ts | 22 +----- packages/core/ts/Poll.ts | 23 +++--- packages/core/ts/utils/types.ts | 1 - 13 files changed, 118 insertions(+), 106 deletions(-) diff --git a/packages/circuits/circom/core/qv/processMessages.circom b/packages/circuits/circom/core/qv/processMessages.circom index d9a3dfbfde..b166dc5963 100644 --- a/packages/circuits/circom/core/qv/processMessages.circom +++ b/packages/circuits/circom/core/qv/processMessages.circom @@ -42,10 +42,6 @@ template ProcessMessages( var STATE_LEAF_TIMESTAMP_IDX = 3; var msgTreeZeroValue = 8370432830353022751713833565135785980866757267633941821328460903436894336785; - // 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 input numSignUps; // Number of options for this poll. diff --git a/packages/circuits/ts/__tests__/PollJoining.test.ts b/packages/circuits/ts/__tests__/PollJoining.test.ts index 120e882b51..c94bf0cad4 100644 --- a/packages/circuits/ts/__tests__/PollJoining.test.ts +++ b/packages/circuits/ts/__tests__/PollJoining.test.ts @@ -16,7 +16,7 @@ import { } from "./utils/constants"; import { circomkitInstance } from "./utils/utils"; -describe.only("Poll Joining circuit", function test() { +describe("Poll Joining circuit", function test() { this.timeout(900000); const NUM_USERS = 50; diff --git a/packages/circuits/ts/types.ts b/packages/circuits/ts/types.ts index 5e2e7ce1ed..d939303cb2 100644 --- a/packages/circuits/ts/types.ts +++ b/packages/circuits/ts/types.ts @@ -61,7 +61,6 @@ export interface IPollJoiningInputs { */ export interface IProcessMessagesInputs { actualStateTreeDepth: bigint; - pollEndTimestamp: bigint; numSignUps: bigint; batchEndIndex: bigint; index: bigint; diff --git a/packages/cli/tests/e2e/keyChange.test.ts b/packages/cli/tests/e2e/keyChange.test.ts index 212bcd5c6a..7fc5441f0e 100644 --- a/packages/cli/tests/e2e/keyChange.test.ts +++ b/packages/cli/tests/e2e/keyChange.test.ts @@ -158,7 +158,7 @@ describe("keyChange tests", function test() { it("should generate zk-SNARK proofs and verify them", async () => { await timeTravel({ ...timeTravelArgs, signer }); - await mergeSignups({ ...mergeSignupsArgs, signer }); + await mergeSignups({ ...mergeSignupsArgs, maciAddress: maciAddresses.maciAddress, signer }); await genProofs({ ...genProofsArgs, signer }); await proveOnChain({ ...proveOnChainArgs, signer }); await verify({ ...(await verifyArgs()), signer }); diff --git a/packages/contracts/contracts/MessageProcessor.sol b/packages/contracts/contracts/MessageProcessor.sol index f557e35fc5..f0a3aefe2d 100644 --- a/packages/contracts/contracts/MessageProcessor.sol +++ b/packages/contracts/contracts/MessageProcessor.sol @@ -91,11 +91,7 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes poll.padLastBatch(); batchHashes = poll.getBatchHashes(); - currentBatchIndex = batchHashes.length; - - if (currentBatchIndex > 0) { - currentBatchIndex -= 1; - } + currentBatchIndex = batchHashes.length - 1; } else { batchHashes = poll.getBatchHashes(); } @@ -129,23 +125,23 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes uint256 coordinatorPubKeyHash = poll.coordinatorPubKeyHash(); uint8 messageBatchSize = poll.messageBatchSize(); (uint256 numSignUps, uint256 numMessages) = poll.numSignUpsAndMessages(); - (uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration(); - uint256 batchEndIndex = _currentMessageBatchIndex + messageBatchSize; + uint256 batchEndIndex = _currentMessageBatchIndex * messageBatchSize; if (batchEndIndex > numMessages) { batchEndIndex = numMessages; } - publicInputs = new uint256[](9); + uint256 batchStartIndex = batchEndIndex > messageBatchSize ? batchEndIndex - messageBatchSize : 0; + + publicInputs = new uint256[](8); publicInputs[0] = numSignUps; - publicInputs[1] = deployTime + duration; - publicInputs[2] = _outputBatchHash; - publicInputs[3] = poll.actualStateTreeDepth(); - publicInputs[4] = batchEndIndex; - publicInputs[5] = _currentMessageBatchIndex; - publicInputs[6] = coordinatorPubKeyHash; - publicInputs[7] = (sbCommitment == 0 ? poll.currentSbCommitment() : sbCommitment); - publicInputs[8] = _newSbCommitment; + publicInputs[1] = _outputBatchHash; + publicInputs[2] = poll.actualStateTreeDepth(); + publicInputs[3] = coordinatorPubKeyHash; + publicInputs[4] = (sbCommitment == 0 ? poll.currentSbCommitment() : sbCommitment); + publicInputs[5] = _newSbCommitment; + publicInputs[6] = batchStartIndex; + publicInputs[7] = batchEndIndex; } /// @notice Verify the proof for processMessage diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index ab577a8ac8..f89cde0b90 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -340,11 +340,11 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { ) public returns (uint256[] memory publicInputs) { publicInputs = new uint256[](5); - publicInputs[0] = _nullifier; - publicInputs[1] = _voiceCreditBalance; - publicInputs[2] = extContracts.maci.getStateRootOnIndexedSignUp(_index); - publicInputs[3] = _pubKey.x; - publicInputs[4] = _pubKey.y; + publicInputs[0] = _pubKey.x; + publicInputs[1] = _pubKey.y; + publicInputs[2] = _nullifier; + publicInputs[3] = _voiceCreditBalance; + publicInputs[4] = extContracts.maci.getStateRootOnIndexedSignUp(_index); } /// @inheritdoc IPoll diff --git a/packages/contracts/deploy-config-example.json b/packages/contracts/deploy-config-example.json index f6d2bceef3..eb3daf87e4 100644 --- a/packages/contracts/deploy-config-example.json +++ b/packages/contracts/deploy-config-example.json @@ -55,6 +55,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -119,6 +123,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -171,7 +179,7 @@ "stateTreeDepth": 6, "intStateTreeDepth": 2, "voteOptionTreeDepth": 3, - "messageBatchDepth": 2, + "messageBatchSize": 20, "zkeys": { "qv": { "processMessagesZkey": "../cli/zkeys/ProcessMessages_6-9-2-3/processMessages_6-9-2-3.zkey", @@ -184,6 +192,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_6-2-3/tallyVotesNonQv_6-2-3.zkey", "processWasm": "../cli/zkeys/ProcessMessagesNonQv_6-9-2-3/ProcessMessagesNonQv_6-9-2-3_js/ProcessMessagesNonQv_6-9-2-3.wasm", "tallyWasm": "../cli/zkeys/TallyVotesNonQv_6-2-3/TallyVotesNonQv_6-2-3_js/TallyVotesNonQv_6-2-3.wasm" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -249,6 +261,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -314,6 +330,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -379,6 +399,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -449,6 +473,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -519,6 +547,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -589,6 +621,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -659,6 +695,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, @@ -729,6 +769,10 @@ "tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", "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" + }, + "pollZkey": { + "zkey": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey", + "wasm": "../cli/zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm" } } }, diff --git a/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts b/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts index 1a45c11a0c..4cb67f966c 100644 --- a/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts +++ b/packages/contracts/tasks/deploy/maci/09-vkRegistry.ts @@ -31,9 +31,12 @@ 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 messageBatchDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageBatchDepth"); + const messageBatchSize = deployment.getDeployConfigField(EContracts.VkRegistry, "messageBatchSize"); const voteOptionTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "voteOptionTreeDepth"); - const pollJoiningTestZkeyPath = deployment.getDeployConfigField(EContracts.VkRegistry, "zkeys.pollZkey"); + const pollJoiningTestZkeyPath = deployment.getDeployConfigField( + EContracts.VkRegistry, + "zkeys.pollZkey.zkey", + ); const processMessagesZkeyPathQv = deployment.getDeployConfigField( EContracts.VkRegistry, "zkeys.qv.processMessagesZkey", @@ -61,6 +64,10 @@ deployment.deployTask(EDeploySteps.VkRegistry, "Deploy Vk Registry and set keys" throw new Error("Non-QV zkeys are not set"); } + if (!pollJoiningTestZkeyPath) { + throw new Error("Poll zkeys are not set"); + } + const [qvProcessVk, qvTallyVk, nonQvProcessVk, nonQvTallyQv, pollVk] = await Promise.all([ processMessagesZkeyPathQv && extractVk(processMessagesZkeyPathQv), tallyVotesZkeyPathQv && extractVk(tallyVotesZkeyPathQv), @@ -97,7 +104,7 @@ deployment.deployTask(EDeploySteps.VkRegistry, "Deploy Vk Registry and set keys" stateTreeDepth, intStateTreeDepth, voteOptionTreeDepth, - 5 ** messageBatchDepth, + messageBatchSize, modes, pollZkeys, processZkeys, diff --git a/packages/contracts/tasks/deploy/poll/01-poll.ts b/packages/contracts/tasks/deploy/poll/01-poll.ts index 2e8c749e0a..4c881df688 100644 --- a/packages/contracts/tasks/deploy/poll/01-poll.ts +++ b/packages/contracts/tasks/deploy/poll/01-poll.ts @@ -105,7 +105,7 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => contract: pollContract, args: [ pollDuration, - maxVoteOptions, + Number(maxVoteOptions), { intStateTreeDepth, voteOptionTreeDepth, diff --git a/packages/contracts/tasks/helpers/Prover.ts b/packages/contracts/tasks/helpers/Prover.ts index 3f04a9a233..1af43a7182 100644 --- a/packages/contracts/tasks/helpers/Prover.ts +++ b/packages/contracts/tasks/helpers/Prover.ts @@ -79,28 +79,26 @@ export class Prover { numSignUpsAndMessages, numBatchesProcessed, stateTreeDepth, - dd, coordinatorPubKeyHash, mode, + batchHashes, + lastChainHash, ] = 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(), + this.pollContract.getBatchHashes().then((res) => [...res]), + this.pollContract.chainHash(), ]); const numMessages = Number(numSignUpsAndMessages[1]); - let totalMessageBatches = numMessages <= messageBatchSize ? 1 : Math.floor(numMessages / messageBatchSize); + const totalMessageBatches = batchHashes.length; let numberBatchesProcessed = numBatchesProcessed; - if (numMessages > messageBatchSize && numMessages % messageBatchSize > 0) { - totalMessageBatches += 1; - } - const onChainProcessVk = await this.vkRegistryContract.getProcessVk( stateTreeDepth, treeDepths.voteOptionTreeDepth, @@ -108,8 +106,6 @@ export class Prover { mode, ); - const pollEndTimestampOnChain = BigInt(dd[0]) + BigInt(dd[1]); - if (numberBatchesProcessed < totalMessageBatches) { console.log("Submitting proofs of message processing..."); } @@ -117,17 +113,18 @@ export class Prover { // process all batches left for (let i = numberBatchesProcessed; i < totalMessageBatches; i += 1) { let currentMessageBatchIndex: number; + if (numberBatchesProcessed === 0) { - const r = numMessages % messageBatchSize; + const chainHash = lastChainHash; + + if (numMessages % messageBatchSize !== 0) { + batchHashes.push(chainHash); + } - currentMessageBatchIndex = numMessages; + currentMessageBatchIndex = batchHashes.length; if (currentMessageBatchIndex > 0) { - if (r === 0) { - currentMessageBatchIndex -= messageBatchSize; - } else { - currentMessageBatchIndex -= r; - } + currentMessageBatchIndex -= 1; } } else { currentMessageBatchIndex = (totalMessageBatches - numberBatchesProcessed) * messageBatchSize; @@ -139,8 +136,17 @@ export class Prover { const { proof, circuitInputs, publicInputs } = proofs[i]; - // validation - this.validatePollDuration(circuitInputs.pollEndTimestamp as BigNumberish, pollEndTimestampOnChain); + const inputBatchHash = batchHashes[currentMessageBatchIndex - 1]; + + if (BigInt(circuitInputs.inputBatchHash as BigNumberish).toString() !== inputBatchHash.toString()) { + throw new Error("input batch hash mismatch."); + } + + const outputBatchHash = batchHashes[currentMessageBatchIndex]; + + if (BigInt(circuitInputs.outputBatchHash as BigNumberish).toString() !== outputBatchHash.toString()) { + throw new Error("output batch hash mismatch."); + } let currentSbCommitmentOnChain: bigint; @@ -158,13 +164,13 @@ 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(), - ); + const publicInputsOnChain = await this.mpContract + .getPublicCircuitInputs( + currentMessageBatchIndex, + asHex(circuitInputs.newSbCommitment as BigNumberish), + outputBatchHash.toString(), + ) + .then((value) => [...value]); this.validatePublicInput(publicInputs, publicInputsOnChain); const vk = new VerifyingKey( @@ -261,7 +267,6 @@ export class Prover { this.validateCommitment(circuitInputs.currentTallyCommitment as BigNumberish, currentTallyCommitmentOnChain); const currentSbCommitmentOnChain = await this.mpContract.sbCommitment(); - console.log(currentSbCommitmentOnChain, circuitInputs); this.validateCommitment(circuitInputs.sbCommitment as BigNumberish, currentSbCommitmentOnChain); const publicInputsOnChain = await this.tallyContract.getPublicCircuitInputs( @@ -349,19 +354,6 @@ export class Prover { console.log("Results have been submitted."); } - /** - * Validate poll end timestamp - * - * @param pollEndTimestamp - off-chain poll end timestamp - * @param pollEndTimestampOnChain - on-chain poll end timestamp - * @throws error if timestamps don't match - */ - private validatePollDuration(pollEndTimestamp: BigNumberish, pollEndTimestampOnChain: BigNumberish) { - if (pollEndTimestamp.toString() !== pollEndTimestampOnChain.toString()) { - throw new Error("poll end timestamp mismatch"); - } - } - /** * Validate commitment * diff --git a/packages/contracts/tasks/runner/prove.ts b/packages/contracts/tasks/runner/prove.ts index 724b013340..e85e944596 100644 --- a/packages/contracts/tasks/runner/prove.ts +++ b/packages/contracts/tasks/runner/prove.ts @@ -6,12 +6,11 @@ import { Keypair, PrivKey } from "maci-domainobjs"; import fs from "fs"; import type { Proof } from "../../ts/types"; -import type { MACI, MessageProcessor, Poll, Tally, Verifier, VkRegistry } from "../../typechain-types"; +import type { MACI, Poll, Tally } 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"; /** @@ -71,8 +70,6 @@ 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 pollAddress = await maciContract.polls(poll); const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollAddress }); @@ -112,11 +109,6 @@ task("prove", "Command to generate proofs") }); 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"; @@ -161,20 +153,8 @@ task("prove", "Command to generate proofs") 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/core/ts/Poll.ts b/packages/core/ts/Poll.ts index 856fc34250..c1eb33a4d0 100644 --- a/packages/core/ts/Poll.ts +++ b/packages/core/ts/Poll.ts @@ -550,12 +550,7 @@ export class Poll implements IPoll { this.padLastBatch(); - this.currentMessageBatchIndex = this.batchHashes.length; - - // if there are messages - if (this.currentMessageBatchIndex > 0) { - this.currentMessageBatchIndex -= 1; - } + this.currentMessageBatchIndex = this.batchHashes.length - 1; this.sbSalts[this.currentMessageBatchIndex] = 0n; } @@ -848,11 +843,19 @@ export class Poll implements IPoll { // 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; + if (batchEndIndex > this.messages.length) { batchEndIndex = this.messages.length - (index - 1) * messageBatchSize; } + let batchStartIndex = batchEndIndex - messageBatchSize; + + if (batchStartIndex < 0) { + batchStartIndex = 0; + } + // copy the public keys, pad the array with the last keys if needed let encPubKeys = this.encPubKeys.map((x) => x.copy()); while (encPubKeys.length % messageBatchSize > 0) { @@ -869,11 +872,7 @@ export class Poll implements IPoll { // calculate the current state and ballot root // commitment which is the hash of the state tree // root, the ballot tree root and a salt - const currentSbCommitment = hash3([ - currentStateRoot, - currentBallotRoot, - this.sbSalts[this.currentMessageBatchIndex], - ]); + const currentSbCommitment = hash3([currentStateRoot, currentBallotRoot, this.sbSalts[index]]); const inputBatchHash = this.batchHashes[index - 1]; const outputBatchHash = this.batchHashes[index]; @@ -881,7 +880,7 @@ export class Poll implements IPoll { return stringifyBigInts({ numSignUps: BigInt(this.numSignups), batchEndIndex: BigInt(batchEndIndex), - index: BigInt(0), + index: BigInt(batchStartIndex), inputBatchHash, outputBatchHash, msgs, diff --git a/packages/core/ts/utils/types.ts b/packages/core/ts/utils/types.ts index 7ae79ec309..c6494fd3fe 100644 --- a/packages/core/ts/utils/types.ts +++ b/packages/core/ts/utils/types.ts @@ -163,7 +163,6 @@ export interface IPollJoiningCircuitInputs { */ export interface IProcessMessagesCircuitInputs { actualStateTreeDepth: string; - pollEndTimestamp: string; numSignUps: string; batchEndIndex: string; index: string; From 1370f8226074d129f91409a3898a5867af43814c Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Sun, 24 Nov 2024 10:13:37 -0600 Subject: [PATCH 05/14] fix: storage write for e2e tests --- packages/cli/tests/e2e/e2e.test.ts | 1 + packages/cli/ts/commands/deployPoll.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/tests/e2e/e2e.test.ts b/packages/cli/tests/e2e/e2e.test.ts index 4508fd8743..4674b1169b 100644 --- a/packages/cli/tests/e2e/e2e.test.ts +++ b/packages/cli/tests/e2e/e2e.test.ts @@ -233,6 +233,7 @@ describe("e2e tests", function test() { ...(await verifyArgs()), tallyData: tallyFileData, maciAddress: tallyFileData.maci, + tallyAddress: tallyFileData.tallyAddress, signer, }); }); diff --git a/packages/cli/ts/commands/deployPoll.ts b/packages/cli/ts/commands/deployPoll.ts index ed6ab06c41..77300529af 100644 --- a/packages/cli/ts/commands/deployPoll.ts +++ b/packages/cli/ts/commands/deployPoll.ts @@ -146,9 +146,9 @@ export const deployPoll = async ({ logGreen(quiet, info(`Tally contract: ${tallyContractAddress}`)); // store the address - storeContractAddress(`MessageProcessor-${pollId.toString()}`, messageProcessorContractAddress, network?.name); - storeContractAddress(`Tally-${pollId.toString()}`, tallyContractAddress, network?.name); - storeContractAddress(`Poll-${pollId.toString()}`, pollAddr, network?.name); + await storeContractAddress(`MessageProcessor-${pollId.toString()}`, messageProcessorContractAddress, network?.name); + await storeContractAddress(`Tally-${pollId.toString()}`, tallyContractAddress, network?.name); + await storeContractAddress(`Poll-${pollId.toString()}`, pollAddr, network?.name); } catch (error) { logError((error as Error).message); } From 63498069fce87dd615d5be1db84a69332fcefe6e Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:18:16 -0600 Subject: [PATCH 06/14] fix: add missing parts after anon poll joining rebase - [x] Add poll contracts for poll deployment - [x] Prevent double calculation of state leaf index - [x] Prevent double calculation of vote option index - [x] Move poll joining circuit to anon folder --- apps/subgraph/src/maci.ts | 20 +++-- .../subgraph/templates/subgraph.template.yaml | 2 +- apps/subgraph/tests/common.ts | 10 ++- apps/subgraph/tests/maci/maci.test.ts | 31 +------- apps/subgraph/tests/maci/utils.ts | 17 +---- apps/subgraph/tests/poll/poll.test.ts | 18 +---- .../{core/qv => anon}/pollJoining.circom | 8 +- packages/circuits/circom/circuits.json | 2 +- .../circom/core/non-qv/processMessages.circom | 23 +++--- .../circom/core/qv/processMessages.circom | 23 +++--- .../utils/non-qv/messageValidator.circom | 6 ++ .../stateLeafAndBallotTransformer.circom | 8 +- .../circom/utils/qv/messageValidator.circom | 6 ++ .../qv/stateLeafAndBallotTransformer.circom | 8 +- .../circuits/ts/__tests__/PollJoining.test.ts | 2 +- packages/cli/ts/commands/deployPoll.ts | 14 ++-- packages/cli/ts/commands/genLocalState.ts | 6 +- packages/cli/ts/commands/genProofs.ts | 6 +- packages/cli/ts/commands/joinPoll.ts | 10 +-- packages/cli/ts/commands/mergeSignups.ts | 6 +- packages/cli/ts/commands/poll.ts | 8 +- packages/cli/ts/commands/proveOnChain.ts | 6 +- packages/cli/ts/commands/publish.ts | 10 +-- packages/cli/ts/commands/verify.ts | 4 +- packages/contracts/contracts/MACI.sol | 19 +++-- .../contracts/tasks/deploy/poll/01-poll.ts | 19 ++--- packages/contracts/tasks/runner/merge.ts | 9 ++- packages/contracts/tasks/runner/prove.ts | 7 +- .../contracts/tasks/runner/submitOnChain.ts | 4 +- packages/contracts/tests/MACI.test.ts | 10 ++- .../contracts/tests/MessageProcessor.test.ts | 20 +---- packages/contracts/tests/Poll.test.ts | 4 +- packages/contracts/tests/Tally.test.ts | 75 ++++--------------- packages/contracts/tests/TallyNonQv.test.ts | 27 ++----- packages/contracts/ts/genMaciState.ts | 6 +- .../ts/__tests__/maci-keys.test.ts | 3 +- 36 files changed, 176 insertions(+), 281 deletions(-) rename packages/circuits/circom/{core/qv => anon}/pollJoining.circom (94%) diff --git a/apps/subgraph/src/maci.ts b/apps/subgraph/src/maci.ts index 4400899927..ca2473c783 100644 --- a/apps/subgraph/src/maci.ts +++ b/apps/subgraph/src/maci.ts @@ -1,7 +1,7 @@ /* eslint-disable no-underscore-dangle */ import { Address, BigInt as GraphBN } from "@graphprotocol/graph-ts"; -import { DeployPoll as DeployPollEvent, SignUp as SignUpEvent } from "../generated/MACI/MACI"; +import { DeployPoll as DeployPollEvent, SignUp as SignUpEvent, MACI as MaciContract } 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"; @@ -12,15 +12,19 @@ import { createOrLoadMACI, createOrLoadUser, createOrLoadAccount } from "./utils export function handleDeployPoll(event: DeployPollEvent): void { const maci = createOrLoadMACI(event); - 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(); + 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 pollContract = PollContract.bind(contracts.poll); + const maxVoteOptions = pollContract.maxVoteOptions(); + const treeDepths = pollContract.treeDepths(); + const durations = pollContract.getDeployTimeAndDuration(); poll.pollId = event.params._pollId; - poll.messageProcessor = event.params.pollAddr.messageProcessor; - poll.tally = event.params.pollAddr.tally; + poll.messageProcessor = contracts.messageProcessor; + poll.tally = contracts.tally; poll.maxVoteOption = maxVoteOptions; poll.treeDepth = GraphBN.fromI32(treeDepths.value0); poll.duration = durations.value1; diff --git a/apps/subgraph/templates/subgraph.template.yaml b/apps/subgraph/templates/subgraph.template.yaml index 9999d45fed..76b6ac291c 100644 --- a/apps/subgraph/templates/subgraph.template.yaml +++ b/apps/subgraph/templates/subgraph.template.yaml @@ -29,7 +29,7 @@ dataSources: - name: Poll file: ./node_modules/maci-contracts/build/artifacts/contracts/Poll.sol/Poll.json eventHandlers: - - event: DeployPoll(uint256,indexed uint256,indexed uint256,(address,address,address),uint8) + - event: DeployPoll(uint256,indexed uint256,indexed uint256,uint8) handler: handleDeployPoll - event: SignUp(uint256,indexed uint256,indexed uint256,uint256,uint256,uint256) handler: handleSignUp diff --git a/apps/subgraph/tests/common.ts b/apps/subgraph/tests/common.ts index 859a2de171..7b7398694c 100644 --- a/apps/subgraph/tests/common.ts +++ b/apps/subgraph/tests/common.ts @@ -29,10 +29,16 @@ export function mockMaciContract(): void { createMockedFunction( Address.fromString("0xA16081F360e3847006dB660bae1c6d1b2e17eC2A"), "getPoll", - "getPoll(uint256):((address))", + "getPoll(uint256):((address,address,address))", ) .withArgs([ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1))]) .returns([ - ethereum.Value.fromTuple(changetype([ethereum.Value.fromAddress(DEFAULT_POLL_ADDRESS)])), + ethereum.Value.fromTuple( + changetype([ + ethereum.Value.fromAddress(DEFAULT_POLL_ADDRESS), + ethereum.Value.fromAddress(DEFAULT_MESSAGE_PROCESSOR_ADDRESS), + ethereum.Value.fromAddress(DEFAULT_TALLY_ADDRESS), + ]), + ), ]); } diff --git a/apps/subgraph/tests/maci/maci.test.ts b/apps/subgraph/tests/maci/maci.test.ts index d41e7c4dff..537b740f67 100644 --- a/apps/subgraph/tests/maci/maci.test.ts +++ b/apps/subgraph/tests/maci/maci.test.ts @@ -4,12 +4,7 @@ import { test, describe, afterEach, clearStore, assert, beforeAll } from "matchs import { Account, MACI, Poll, User } from "../../generated/schema"; import { handleSignUp, handleDeployPoll } from "../../src/maci"; -import { - DEFAULT_MESSAGE_PROCESSOR_ADDRESS, - DEFAULT_POLL_ADDRESS, - DEFAULT_TALLY_ADDRESS, - mockPollContract, -} from "../common"; +import { DEFAULT_POLL_ADDRESS, mockMaciContract, mockPollContract } from "../common"; import { createSignUpEvent, createDeployPollEvent } from "./utils"; @@ -17,6 +12,7 @@ export { handleSignUp, handleDeployPoll }; describe("MACI", () => { beforeAll(() => { + mockMaciContract(); mockPollContract(); }); @@ -52,15 +48,7 @@ describe("MACI", () => { }); test("should handle deploy poll properly (qv)", () => { - const event = createDeployPollEvent( - BigInt.fromI32(1), - BigInt.fromI32(1), - BigInt.fromI32(1), - DEFAULT_POLL_ADDRESS, - DEFAULT_MESSAGE_PROCESSOR_ADDRESS, - DEFAULT_TALLY_ADDRESS, - BigInt.fromI32(0), - ); + const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(0)); handleDeployPoll(event); @@ -77,15 +65,7 @@ describe("MACI", () => { }); test("should handle deploy poll properly (non-qv)", () => { - const event = createDeployPollEvent( - BigInt.fromI32(1), - BigInt.fromI32(1), - BigInt.fromI32(1), - DEFAULT_POLL_ADDRESS, - DEFAULT_MESSAGE_PROCESSOR_ADDRESS, - DEFAULT_TALLY_ADDRESS, - BigInt.fromI32(1), - ); + const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1)); handleDeployPoll(event); @@ -106,9 +86,6 @@ describe("MACI", () => { BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), - DEFAULT_POLL_ADDRESS, - DEFAULT_MESSAGE_PROCESSOR_ADDRESS, - DEFAULT_TALLY_ADDRESS, BigInt.fromI32(0), ); diff --git a/apps/subgraph/tests/maci/utils.ts b/apps/subgraph/tests/maci/utils.ts index 0f2c2f38e3..76e4a79a9d 100644 --- a/apps/subgraph/tests/maci/utils.ts +++ b/apps/subgraph/tests/maci/utils.ts @@ -1,4 +1,4 @@ -import { Address, BigInt as GraphBN, ethereum } from "@graphprotocol/graph-ts"; +import { BigInt as GraphBN, ethereum } from "@graphprotocol/graph-ts"; // eslint-disable-next-line import/no-extraneous-dependencies import { newMockEvent } from "matchstick-as"; @@ -28,9 +28,6 @@ export function createDeployPollEvent( pollId: GraphBN, coordinatorPubKeyX: GraphBN, coordinatorPubKeyY: GraphBN, - poll: Address, - messageProcessor: Address, - tally: Address, mode: GraphBN, ): DeployPoll { const event = changetype(newMockEvent()); @@ -42,18 +39,6 @@ export function createDeployPollEvent( event.parameters.push( new ethereum.EventParam("_coordinatorPubKeyY", ethereum.Value.fromUnsignedBigInt(coordinatorPubKeyY)), ); - event.parameters.push( - new ethereum.EventParam( - "pollAddr", - ethereum.Value.fromTuple( - changetype([ - ethereum.Value.fromAddress(poll), - ethereum.Value.fromAddress(messageProcessor), - ethereum.Value.fromAddress(tally), - ]), - ), - ), - ); event.parameters.push(new ethereum.EventParam("mode", ethereum.Value.fromUnsignedBigInt(mode))); return event; diff --git a/apps/subgraph/tests/poll/poll.test.ts b/apps/subgraph/tests/poll/poll.test.ts index a7468ccb19..bc4e224d80 100644 --- a/apps/subgraph/tests/poll/poll.test.ts +++ b/apps/subgraph/tests/poll/poll.test.ts @@ -5,12 +5,7 @@ import { test, describe, afterEach, clearStore, assert, beforeEach } from "match import { MACI, Poll } from "../../generated/schema"; import { handleDeployPoll } from "../../src/maci"; import { handleMergeState, handlePublishMessage } from "../../src/poll"; -import { - DEFAULT_MESSAGE_PROCESSOR_ADDRESS, - DEFAULT_POLL_ADDRESS, - DEFAULT_TALLY_ADDRESS, - mockPollContract, -} from "../common"; +import { DEFAULT_POLL_ADDRESS, mockMaciContract, mockPollContract } from "../common"; import { createDeployPollEvent } from "../maci/utils"; import { createMergeStateEvent, createPublishMessageEvent } from "./utils"; @@ -19,18 +14,11 @@ export { handleMergeState, handlePublishMessage }; describe("Poll", () => { beforeEach(() => { + mockMaciContract(); mockPollContract(); // mock the deploy poll event with non qv mode set - const event = createDeployPollEvent( - BigInt.fromI32(1), - BigInt.fromI32(1), - BigInt.fromI32(1), - DEFAULT_POLL_ADDRESS, - DEFAULT_MESSAGE_PROCESSOR_ADDRESS, - DEFAULT_TALLY_ADDRESS, - BigInt.fromI32(1), - ); + const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1)); handleDeployPoll(event); }); diff --git a/packages/circuits/circom/core/qv/pollJoining.circom b/packages/circuits/circom/anon/pollJoining.circom similarity index 94% rename from packages/circuits/circom/core/qv/pollJoining.circom rename to packages/circuits/circom/anon/pollJoining.circom index 1650685d63..b66513bac0 100644 --- a/packages/circuits/circom/core/qv/pollJoining.circom +++ b/packages/circuits/circom/anon/pollJoining.circom @@ -1,17 +1,15 @@ pragma circom 2.0.0; - // circomlib import include "./mux1.circom"; // zk-kit imports include "./safe-comparators.circom"; // local imports -include "../../utils/hashers.circom"; -include "../../utils/privToPubKey.circom"; -include "../../trees/incrementalMerkleTree.circom"; +include "../utils/hashers.circom"; +include "../utils/privToPubKey.circom"; +include "../trees/incrementalMerkleTree.circom"; template PollJoining(stateTreeDepth) { - // Constants defining the structure and size of state. var STATE_LEAF_LENGTH = 4; var STATE_TREE_ARITY = 2; diff --git a/packages/circuits/circom/circuits.json b/packages/circuits/circom/circuits.json index ba9522f44d..8486bd8574 100644 --- a/packages/circuits/circom/circuits.json +++ b/packages/circuits/circom/circuits.json @@ -1,6 +1,6 @@ { "PollJoining_10_test": { - "file": "./core/qv/pollJoining", + "file": "./core/anon/pollJoining", "template": "PollJoining", "params": [10], "pubs": ["nullifier", "credits", "stateRoot", "pollPubKey"] diff --git a/packages/circuits/circom/core/non-qv/processMessages.circom b/packages/circuits/circom/core/non-qv/processMessages.circom index 7efba4f633..9e6bd0dc3a 100644 --- a/packages/circuits/circom/core/non-qv/processMessages.circom +++ b/packages/circuits/circom/core/non-qv/processMessages.circom @@ -195,7 +195,7 @@ include "../../trees/incrementalQuinaryTree.circom"; computedCommandsSigR8[i], computedCommandsSigS[i], computedCommandsPackedCommandOut[i] - ) = MessageToCommand()(message, coordPrivKey, encPubKeys[i]); + ) = MessageToCommand()(msgs[i], coordPrivKey, encPubKeys[i]); } // Process messages in reverse order. @@ -338,13 +338,12 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // 1. Transform a state leaf and a ballot with a command. // The result is a new state leaf, a new ballot, and an isValid signal (0 or 1). - var computedNewSlPubKey[2], computedNewBallotNonce, computedIsValid; - (computedNewSlPubKey, computedNewBallotNonce, computedIsValid) = StateLeafAndBallotTransformerNonQv()( + var computedNewSlPubKey[2], computedNewBallotNonce, computedIsValid, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid; + (computedNewSlPubKey, computedNewBallotNonce, computedIsValid, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid) = StateLeafAndBallotTransformerNonQv()( numSignUps, 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], ballot[BALLOT_NONCE_IDX], currentVoteWeight, cmdStateIndex, @@ -361,8 +360,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // 2. If isValid is equal to zero, generate indices for leaf zero. // Otherwise, generate indices for command.stateIndex. - var stateLeafIndexValid = SafeLessThan(N_BITS)([cmdStateIndex, numSignUps]); - var stateIndexMux = Mux1()([0, cmdStateIndex], stateLeafIndexValid); + var stateIndexMux = Mux1()([0, cmdStateIndex], computedIsStateLeafIndexValid); var computedStateLeafPathIndices[stateTreeDepth] = MerkleGeneratePathIndices(stateTreeDepth)(stateIndexMux); // 3. Verify that the original state leaf exists in the given state root. @@ -400,9 +398,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { c ]); - var computedIsMessageEqual = IsEqual()([2, computedIsValid + voiceCreditAmountValid]); - var voteOptionIndexValid = SafeLessThan(N_BITS)([cmdVoteOptionIndex, maxVoteOptions]); - var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], voteOptionIndexValid); + var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); var computedCurrentVoteWeightPathIndices[voteOptionTreeDepth] = QuinGeneratePathIndices(voteOptionTreeDepth)(cmdVoteOptionIndexMux); var computedCurrentVoteWeightQip = QuinTreeInclusionProof(voteOptionTreeDepth)( @@ -413,7 +409,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { computedCurrentVoteWeightQip === ballot[BALLOT_VO_ROOT_IDX]; - var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsMessageEqual); + var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsValid); var newSlVoiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], @@ -426,7 +422,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], newSlVoiceCreditBalanceMux ], - computedIsMessageEqual + computedIsValid ); // 5.1. Update the ballot's vote option root with the new vote weight. @@ -439,7 +435,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // The new vote option root in the ballot var newBallotVoRootMux = Mux1()( [ballot[BALLOT_VO_ROOT_IDX], computedNewVoteOptionTreeQip], - computedIsMessageEqual + computedIsValid ); newBallotVoRoot <== newBallotVoRootMux; @@ -462,8 +458,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { newStateRoot <== computedNewStateLeafQip; // 7. Generate a new ballot root. - var newBallotNonceMux = Mux1()([ballot[BALLOT_NONCE_IDX], computedNewBallotNonce], computedIsMessageEqual); - var computedNewBallot = PoseidonHasher(2)([newBallotNonceMux, newBallotVoRoot]); + var computedNewBallot = PoseidonHasher(2)([computedNewBallotNonce, newBallotVoRoot]); var computedNewBallotQip = MerkleTreeInclusionProof(stateTreeDepth)( computedNewBallot, computedStateLeafPathIndices, diff --git a/packages/circuits/circom/core/qv/processMessages.circom b/packages/circuits/circom/core/qv/processMessages.circom index b166dc5963..85cf443045 100644 --- a/packages/circuits/circom/core/qv/processMessages.circom +++ b/packages/circuits/circom/core/qv/processMessages.circom @@ -191,7 +191,7 @@ template ProcessMessages( computedCommandsSigR8[i], computedCommandsSigS[i], computedCommandsPackedCommandOut[i] - ) = MessageToCommand()(message, coordPrivKey, encPubKeys[i]); + ) = MessageToCommand()(msgs[i], coordPrivKey, encPubKeys[i]); } // Process messages in reverse order. @@ -334,13 +334,12 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // 1. Transform a state leaf and a ballot with a command. // The result is a new state leaf, a new ballot, and an isValid signal (0 or 1). - var computedNewSlPubKey[2], computedNewBallotNonce, computedIsValid; - (computedNewSlPubKey, computedNewBallotNonce, computedIsValid) = StateLeafAndBallotTransformer()( + var computedNewSlPubKey[2], computedNewBallotNonce, computedIsValid, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid; + (computedNewSlPubKey, computedNewBallotNonce, computedIsValid, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid) = StateLeafAndBallotTransformer()( numSignUps, 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], ballot[BALLOT_NONCE_IDX], currentVoteWeight, cmdStateIndex, @@ -357,8 +356,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // 2. If isValid is equal to zero, generate indices for leaf zero. // Otherwise, generate indices for command.stateIndex. - var stateLeafIndexValid = SafeLessThan(N_BITS)([cmdStateIndex, numSignUps]); - var stateIndexMux = Mux1()([0, cmdStateIndex], stateLeafIndexValid); + var stateIndexMux = Mux1()([0, cmdStateIndex], computedIsStateLeafIndexValid); var computedStateLeafPathIndices[stateTreeDepth] = MerkleGeneratePathIndices(stateTreeDepth)(stateIndexMux); // 3. Verify that the original state leaf exists in the given state root. @@ -396,9 +394,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { c ]); - var computedIsMessageEqual = IsEqual()([2, computedIsValid + voiceCreditAmountValid]); - var voteOptionIndexValid = SafeLessThan(N_BITS)([cmdVoteOptionIndex, maxVoteOptions]); - var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], voteOptionIndexValid); + var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); var computedCurrentVoteWeightPathIndices[voteOptionTreeDepth] = QuinGeneratePathIndices(voteOptionTreeDepth)(cmdVoteOptionIndexMux); var computedCurrentVoteWeightQip = QuinTreeInclusionProof(voteOptionTreeDepth)( @@ -409,7 +405,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { computedCurrentVoteWeightQip === ballot[BALLOT_VO_ROOT_IDX]; - var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsMessageEqual); + var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsValid); var newSlVoiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], @@ -422,7 +418,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], newSlVoiceCreditBalanceMux ], - computedIsMessageEqual + computedIsValid ); // 5.1. Update the ballot's vote option root with the new vote weight. @@ -435,7 +431,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // The new vote option root in the ballot var newBallotVoRootMux = Mux1()( [ballot[BALLOT_VO_ROOT_IDX], computedNewVoteOptionTreeQip], - computedIsMessageEqual + computedIsValid ); newBallotVoRoot <== newBallotVoRootMux; @@ -458,8 +454,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { newStateRoot <== computedNewStateLeafQip; // 7. Generate a new ballot root. - var newBallotNonceMux = Mux1()([ballot[BALLOT_NONCE_IDX], computedNewBallotNonce], computedIsMessageEqual); - var computedNewBallot = PoseidonHasher(2)([newBallotNonceMux, newBallotVoRoot]); + var computedNewBallot = PoseidonHasher(2)([computedNewBallotNonce, newBallotVoRoot]); var computedNewBallotQip = MerkleTreeInclusionProof(stateTreeDepth)( computedNewBallot, computedStateLeafPathIndices, diff --git a/packages/circuits/circom/utils/non-qv/messageValidator.circom b/packages/circuits/circom/utils/non-qv/messageValidator.circom index ae235804f3..e5cad3aaec 100644 --- a/packages/circuits/circom/utils/non-qv/messageValidator.circom +++ b/packages/circuits/circom/utils/non-qv/messageValidator.circom @@ -42,6 +42,10 @@ template MessageValidatorNonQv() { // True when the command is valid; otherwise false. signal output isValid; + // True if the state leaf index is valid + signal output isStateLeafIndexValid; + // True if the vote option index is valid + signal output isVoteOptionIndexValid; // Check (1) - The state leaf index must be valid. // The check ensure that the stateTreeIndex < numSignUps as first validation. @@ -80,4 +84,6 @@ template MessageValidatorNonQv() { ); isValid <== computedIsUpdateValid; + isStateLeafIndexValid <== computedIsStateLeafIndexValid; + isVoteOptionIndexValid <== computedIsVoteOptionIndexValid; } diff --git a/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom b/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom index ee9150eee5..ad9d2190ef 100644 --- a/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom +++ b/packages/circuits/circom/utils/non-qv/stateLeafAndBallotTransformer.circom @@ -62,9 +62,13 @@ template StateLeafAndBallotTransformerNonQv() { // True when the command is valid; otherwise false. signal output isValid; + // True if the state leaf index is valid + signal output isStateLeafIndexValid; + // True if the vote option index is valid + signal output isVoteOptionIndexValid; // Check if the command / message is valid. - var computedMessageValidator = MessageValidatorNonQv()( + var (computedMessageValidator, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid) = MessageValidatorNonQv()( cmdStateIndex, numSignUps, cmdVoteOptionIndex, @@ -96,4 +100,6 @@ template StateLeafAndBallotTransformerNonQv() { newBallotNonce <== computedNewBallotNonceMux; isValid <== computedMessageValidator; + isStateLeafIndexValid <== computedIsStateLeafIndexValid; + isVoteOptionIndexValid <== computedIsVoteOptionIndexValid; } diff --git a/packages/circuits/circom/utils/qv/messageValidator.circom b/packages/circuits/circom/utils/qv/messageValidator.circom index b782739959..13ac75fd0a 100644 --- a/packages/circuits/circom/utils/qv/messageValidator.circom +++ b/packages/circuits/circom/utils/qv/messageValidator.circom @@ -42,6 +42,10 @@ template MessageValidator() { // True when the command is valid; otherwise false. signal output isValid; + // True if the state leaf index is valid + signal output isStateLeafIndexValid; + // True if the vote option index is valid + signal output isVoteOptionIndexValid; // Check (1) - The state leaf index must be valid. // The check ensure that the stateTreeIndex < numSignUps as first validation. @@ -86,4 +90,6 @@ template MessageValidator() { ); isValid <== computedIsUpdateValid; + isStateLeafIndexValid <== computedIsStateLeafIndexValid; + isVoteOptionIndexValid <== computedIsVoteOptionIndexValid; } diff --git a/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom b/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom index 1eeaa47d18..1213288b17 100644 --- a/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom +++ b/packages/circuits/circom/utils/qv/stateLeafAndBallotTransformer.circom @@ -62,9 +62,13 @@ template StateLeafAndBallotTransformer() { // True when the command is valid; otherwise false. signal output isValid; + // True if the state leaf index is valid + signal output isStateLeafIndexValid; + // True if the vote option index is valid + signal output isVoteOptionIndexValid; // Check if the command / message is valid. - var computedMessageValidator = MessageValidator()( + var (computedMessageValidator, computedIsStateLeafIndexValid, computedIsVoteOptionIndexValid) = MessageValidator()( cmdStateIndex, numSignUps, cmdVoteOptionIndex, @@ -96,4 +100,6 @@ template StateLeafAndBallotTransformer() { newBallotNonce <== computedNewBallotNonceMux; isValid <== computedMessageValidator; + isStateLeafIndexValid <== computedIsStateLeafIndexValid; + isVoteOptionIndexValid <== computedIsVoteOptionIndexValid; } diff --git a/packages/circuits/ts/__tests__/PollJoining.test.ts b/packages/circuits/ts/__tests__/PollJoining.test.ts index c94bf0cad4..d8af537895 100644 --- a/packages/circuits/ts/__tests__/PollJoining.test.ts +++ b/packages/circuits/ts/__tests__/PollJoining.test.ts @@ -39,7 +39,7 @@ describe("Poll Joining circuit", function test() { before(async () => { circuit = await circomkitInstance.WitnessTester("pollJoining", { - file: "./core/qv/pollJoining", + file: "./core/anon/pollJoining", template: "PollJoining", params: [STATE_TREE_DEPTH], }); diff --git a/packages/cli/ts/commands/deployPoll.ts b/packages/cli/ts/commands/deployPoll.ts index 77300529af..bff6d257f2 100644 --- a/packages/cli/ts/commands/deployPoll.ts +++ b/packages/cli/ts/commands/deployPoll.ts @@ -81,7 +81,7 @@ export const deployPoll = async ({ const unserializedKey = PubKey.deserialize(coordinatorPubkey); // get the verifier contract - const verifierContractAddress = readContractAddress("Verifier", network?.name); + const verifierContractAddress = await readContractAddress("Verifier", network?.name); const maciContract = MACIFactory.connect(maci, signer); @@ -119,11 +119,6 @@ export const deployPoll = async ({ const log = iface.parseLog(receiptLog as unknown as { topics: string[]; data: string }) as unknown as { args: { _pollId: bigint; - pollAddr: { - poll: string; - messageProcessor: string; - tally: string; - }; }; name: string; }; @@ -136,9 +131,10 @@ export const deployPoll = async ({ // eslint-disable-next-line no-underscore-dangle const pollId = log.args._pollId; - pollAddr = log.args.pollAddr.poll; - messageProcessorContractAddress = log.args.pollAddr.messageProcessor; - tallyContractAddress = log.args.pollAddr.tally; + const pollContracts = await maciContract.getPoll(pollId); + pollAddr = pollContracts.poll; + messageProcessorContractAddress = pollContracts.messageProcessor; + tallyContractAddress = pollContracts.tally; logGreen(quiet, info(`Poll ID: ${pollId.toString()}`)); logGreen(quiet, info(`Poll contract: ${pollAddr}`)); diff --git a/packages/cli/ts/commands/genLocalState.ts b/packages/cli/ts/commands/genLocalState.ts index c350f86d76..fda6905183 100644 --- a/packages/cli/ts/commands/genLocalState.ts +++ b/packages/cli/ts/commands/genLocalState.ts @@ -60,12 +60,12 @@ export const genLocalState = async ({ const coordinatorKeypair = new Keypair(coordinatorMaciPrivKey); const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollAddr = await maciContract.polls(pollId); + const pollContracts = await maciContract.polls(pollId); - if (!(await contractExists(signer.provider!, pollAddr))) { + if (!(await contractExists(signer.provider!, pollContracts.poll))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollAddr, signer); + const pollContract = PollFactory.connect(pollContracts.poll, signer); const [defaultStartBlockSignup, defaultStartBlockPoll, stateRoot, numSignups] = await Promise.all([ maciContract.queryFilter(maciContract.filters.SignUp(), startBlock).then((events) => events[0]?.blockNumber ?? 0), diff --git a/packages/cli/ts/commands/genProofs.ts b/packages/cli/ts/commands/genProofs.ts index 36a5cbce0a..80bd102e20 100644 --- a/packages/cli/ts/commands/genProofs.ts +++ b/packages/cli/ts/commands/genProofs.ts @@ -141,12 +141,12 @@ export const genProofs = async ({ } const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollAddr = await maciContract.polls(pollId); + const pollContracts = await maciContract.polls(pollId); - if (!(await contractExists(signer.provider!, pollAddr))) { + if (!(await contractExists(signer.provider!, pollContracts.poll))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollAddr, signer); + const pollContract = PollFactory.connect(pollContracts.poll, signer); // Check that the state and message trees have been merged if (!(await pollContract.stateMerged())) { diff --git a/packages/cli/ts/commands/joinPoll.ts b/packages/cli/ts/commands/joinPoll.ts index a3a3d7fd5b..b86242b8ee 100644 --- a/packages/cli/ts/commands/joinPoll.ts +++ b/packages/cli/ts/commands/joinPoll.ts @@ -221,13 +221,13 @@ export const joinPoll = async ({ } const maciContract = MACIFactory.connect(maciAddress, signer); - const pollAddress = await maciContract.getPoll(pollId); + const pollContracts = await maciContract.getPoll(pollId); - if (!(await contractExists(signer.provider!, pollAddress))) { + if (!(await contractExists(signer.provider!, pollContracts.poll))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollAddress, signer); + const pollContract = PollFactory.connect(pollContracts.poll, signer); let loadedStateIndex: bigint | null; let loadedCreditBalance: bigint | null; @@ -431,8 +431,8 @@ export const isJoinedUser = async ({ banner(quiet); const maciContract = MACIFactory.connect(maciAddress, signer); - const pollAddress = await maciContract.getPoll(pollId); - const pollContract = PollFactory.connect(pollAddress, signer); + const pollContracts = await maciContract.getPoll(pollId); + const pollContract = PollFactory.connect(pollContracts.poll, signer); const pollPublicKey = PubKey.deserialize(pollPubKey); const startBlockNumber = startBlock || 0; diff --git a/packages/cli/ts/commands/mergeSignups.ts b/packages/cli/ts/commands/mergeSignups.ts index d92a081c8a..a73d6e5790 100644 --- a/packages/cli/ts/commands/mergeSignups.ts +++ b/packages/cli/ts/commands/mergeSignups.ts @@ -31,13 +31,13 @@ export const mergeSignups = async ({ pollId, maciAddress, signer, quiet = true } } const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollAddress = await maciContract.polls(pollId); + const pollContracts = await maciContract.polls(pollId); - if (!(await contractExists(signer.provider!, pollAddress))) { + if (!(await contractExists(signer.provider!, pollContracts.poll))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollAddress, signer); + const pollContract = PollFactory.connect(pollContracts.poll, signer); // check if it's time to merge the message AQ const dd = await pollContract.getDeployTimeAndDuration(); diff --git a/packages/cli/ts/commands/poll.ts b/packages/cli/ts/commands/poll.ts index a0cb3995d1..0f1c4a2ab7 100644 --- a/packages/cli/ts/commands/poll.ts +++ b/packages/cli/ts/commands/poll.ts @@ -37,13 +37,13 @@ export const getPoll = async ({ logError(`Invalid poll id ${id}`); } - const pollAddress = await maciContract.polls(id); + const pollContracts = await maciContract.polls(id); - if (pollAddress === ZeroAddress) { + if (pollContracts.poll === ZeroAddress) { logError(`MACI contract doesn't have any deployed poll ${id}`); } - const pollContract = PollFactory.connect(pollAddress, signer ?? provider); + const pollContract = PollFactory.connect(pollContracts.poll, signer ?? provider); const [[deployTime, duration], mergedStateRoot] = await Promise.all([ pollContract.getDeployTimeAndDuration(), @@ -72,7 +72,7 @@ export const getPoll = async ({ return { id, - address: pollAddress, + address: pollContracts.poll, deployTime, duration, numSignups, diff --git a/packages/cli/ts/commands/proveOnChain.ts b/packages/cli/ts/commands/proveOnChain.ts index 79ebd2f1d7..dcaef8c32b 100644 --- a/packages/cli/ts/commands/proveOnChain.ts +++ b/packages/cli/ts/commands/proveOnChain.ts @@ -85,13 +85,13 @@ export const proveOnChain = async ({ } const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollAddr = await maciContract.polls(pollId); + const pollContracts = await maciContract.polls(pollId); - if (!(await contractExists(signer.provider!, pollAddr))) { + if (!(await contractExists(signer.provider!, pollContracts.poll))) { logError("There is no Poll contract with this poll ID linked to the specified MACI contract."); } - const pollContract = PollFactory.connect(pollAddr, signer); + const pollContract = PollFactory.connect(pollContracts.poll, signer); const mpContract = MessageProcessorFactory.connect(messageProcessorContractAddress, signer); const tallyContract = TallyFactory.connect(tallyContractAddress, signer); diff --git a/packages/cli/ts/commands/publish.ts b/packages/cli/ts/commands/publish.ts index dee42b3a13..5c6d205c99 100644 --- a/packages/cli/ts/commands/publish.ts +++ b/packages/cli/ts/commands/publish.ts @@ -78,13 +78,13 @@ export const publish = async ({ } const maciContract = MACIFactory.connect(maciAddress, signer); - const pollAddress = await maciContract.getPoll(pollId); + const pollContracts = await maciContract.getPoll(pollId); - if (!(await contractExists(signer.provider!, pollAddress))) { + if (!(await contractExists(signer.provider!, pollContracts.poll))) { logError("Poll contract does not exist"); } - const pollContract = PollFactory.connect(pollAddress, signer); + const pollContract = PollFactory.connect(pollContracts.poll, signer); const maxVoteOptions = Number(await pollContract.maxVoteOptions()); const coordinatorPubKeyResult = await pollContract.coordinatorPubKey(); @@ -167,9 +167,9 @@ export const publishBatch = async ({ const userMaciPubKey = PubKey.deserialize(publicKey); const userMaciPrivKey = PrivKey.deserialize(privateKey); const maciContract = MACIFactory.connect(maciAddress, signer); - const pollAddress = await maciContract.getPoll(pollId); + const pollContracts = await maciContract.getPoll(pollId); - const pollContract = PollFactory.connect(pollAddress, signer); + const pollContract = PollFactory.connect(pollContracts.poll, signer); const [maxVoteOptions, coordinatorPubKeyResult] = await Promise.all([ pollContract.maxVoteOptions().then(Number), diff --git a/packages/cli/ts/commands/verify.ts b/packages/cli/ts/commands/verify.ts index 7b24948eac..2e59667ac2 100644 --- a/packages/cli/ts/commands/verify.ts +++ b/packages/cli/ts/commands/verify.ts @@ -54,9 +54,9 @@ export const verify = async ({ // get the contract objects const maciContract = MACIFactory.connect(maciContractAddress, signer); - const pollAddr = await maciContract.polls(pollId); + const pollContracts = await maciContract.polls(pollId); - const pollContract = PollFactory.connect(pollAddr, signer); + const pollContract = PollFactory.connect(pollContracts.poll, signer); const tallyContract = TallyFactory.connect(tallyContractAddress, signer); diff --git a/packages/contracts/contracts/MACI.sol b/packages/contracts/contracts/MACI.sol index e32f8d1f4c..ae4e77c39d 100644 --- a/packages/contracts/contracts/MACI.sol +++ b/packages/contracts/contracts/MACI.sol @@ -43,7 +43,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { uint256 public nextPollId; /// @notice A mapping of poll IDs to Poll contracts. - mapping(uint256 => address) public polls; + mapping(uint256 => PollContracts) public polls; /// @notice Factory contract that deploy a Poll contract IPollFactory public immutable pollFactory; @@ -90,7 +90,6 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { uint256 _pollId, uint256 indexed _coordinatorPubKeyX, uint256 indexed _coordinatorPubKeyY, - PollContracts pollAddr, Mode _mode ); @@ -194,7 +193,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { address _verifier, address _vkRegistry, Mode _mode - ) public virtual returns (PollContracts memory pollAddr) { + ) public virtual { // cache the poll to a local variable so we can increment it uint256 pollId = nextPollId; @@ -230,12 +229,12 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, msg.sender, _mode); address tally = tallyFactory.deploy(_verifier, _vkRegistry, p, mp, msg.sender, _mode); - polls[pollId] = p; - // store the addresses in a struct so they can be returned - pollAddr = PollContracts({ poll: p, messageProcessor: mp, tally: tally }); + PollContracts memory pollAddr = PollContracts({ poll: p, messageProcessor: mp, tally: tally }); + + polls[pollId] = pollAddr; - emit DeployPoll(pollId, _coordinatorPubKey.x, _coordinatorPubKey.y, pollAddr, _mode); + emit DeployPoll(pollId, _coordinatorPubKey.x, _coordinatorPubKey.y, _mode); } /// @inheritdoc IMACI @@ -245,10 +244,10 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @notice Get the Poll details /// @param _pollId The identifier of the Poll to retrieve - /// @return poll The Poll contract object - function getPoll(uint256 _pollId) public view returns (address poll) { + /// @return pollContracts The Poll contract object + function getPoll(uint256 _pollId) public view returns (PollContracts memory pollContracts) { if (_pollId >= nextPollId) revert PollDoesNotExist(_pollId); - poll = polls[_pollId]; + pollContracts = polls[_pollId]; } /// @inheritdoc IMACI diff --git a/packages/contracts/tasks/deploy/poll/01-poll.ts b/packages/contracts/tasks/deploy/poll/01-poll.ts index 4c881df688..ca560a1ce6 100644 --- a/packages/contracts/tasks/deploy/poll/01-poll.ts +++ b/packages/contracts/tasks/deploy/poll/01-poll.ts @@ -49,20 +49,6 @@ 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, { @@ -82,6 +68,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 [maxVoteOptions, extContracts] = await Promise.all([ pollContract.maxVoteOptions(), diff --git a/packages/contracts/tasks/runner/merge.ts b/packages/contracts/tasks/runner/merge.ts index e008cbdc6c..ebbbcedc80 100644 --- a/packages/contracts/tasks/runner/merge.ts +++ b/packages/contracts/tasks/runner/merge.ts @@ -23,13 +23,16 @@ task("merge", "Merge signups") const maciContract = await deployment.getContract({ name: EContracts.MACI }); - const pollContractAddress = await maciContract.polls(poll); + const pollContracts = await maciContract.polls(poll); - if (pollContractAddress === ZeroAddress) { + if (pollContracts.poll === ZeroAddress) { throw new Error(`No poll ${poll} found`); } - const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollContractAddress }); + const pollContract = await deployment.getContract({ + name: EContracts.Poll, + address: pollContracts.poll, + }); const treeMerger = new TreeMerger({ deployer, diff --git a/packages/contracts/tasks/runner/prove.ts b/packages/contracts/tasks/runner/prove.ts index e85e944596..cad853a5bd 100644 --- a/packages/contracts/tasks/runner/prove.ts +++ b/packages/contracts/tasks/runner/prove.ts @@ -71,8 +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 pollAddress = await maciContract.polls(poll); - const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollAddress }); + const pollContracts = await maciContract.polls(poll); + const pollContract = await deployment.getContract({ + name: EContracts.Poll, + address: pollContracts.poll, + }); const isStateAqMerged = await pollContract.stateMerged(); // Check that the state and message trees have been merged for at least the first poll diff --git a/packages/contracts/tasks/runner/submitOnChain.ts b/packages/contracts/tasks/runner/submitOnChain.ts index ed9932cf99..7f88e1cf96 100644 --- a/packages/contracts/tasks/runner/submitOnChain.ts +++ b/packages/contracts/tasks/runner/submitOnChain.ts @@ -78,10 +78,10 @@ task("submitOnChain", "Command to prove the result of a poll on-chain") deployment.getContract({ name: EContracts.Verifier }), ]); - const pollContractAddress = await maciContract.polls(poll); + const pollContracts = await maciContract.polls(poll); const pollContract = await deployment.getContract({ name: EContracts.Poll, - address: pollContractAddress, + address: pollContracts.poll, }); const [isStateAqMerged, mpContract, tallyContract] = await Promise.all([ diff --git a/packages/contracts/tests/MACI.test.ts b/packages/contracts/tests/MACI.test.ts index c12197f54f..f4f35fb3a5 100644 --- a/packages/contracts/tests/MACI.test.ts +++ b/packages/contracts/tests/MACI.test.ts @@ -294,8 +294,8 @@ describe("MACI", function test() { let pollContract: PollContract; before(async () => { - const pollContractAddress = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContractAddress, signer); + const pollContracts = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContracts.poll, signer); }); it("should allow a Poll contract to merge the state tree (calculate the state root)", async () => { @@ -337,8 +337,10 @@ describe("MACI", function test() { describe("getPoll", () => { it("should return an object for a valid id", async () => { - const pollContractAddress = await maciContract.getPoll(pollId); - expect(pollContractAddress).to.not.eq(ZeroAddress); + 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); }); 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 3c1d499dc4..b6db5b7c3e 100644 --- a/packages/contracts/tests/MessageProcessor.test.ts +++ b/packages/contracts/tests/MessageProcessor.test.ts @@ -74,22 +74,10 @@ describe("MessageProcessor", () => { // extract poll id expect(receipt?.status).to.eq(1); - 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; - }; - }; - }; - - pollId = event.args._pollId; - - mpContract = MessageProcessorFactory.connect(event.args.pollAddr.messageProcessor, signer); + pollId = (await maciContract.nextPollId()) - 1n; + + const pollContracts = await maciContract.getPoll(pollId); + mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); const block = await signer.provider!.getBlock(receipt!.blockHash); const deployTime = block!.timestamp; diff --git a/packages/contracts/tests/Poll.test.ts b/packages/contracts/tests/Poll.test.ts index c9678c2a10..46692ce770 100644 --- a/packages/contracts/tests/Poll.test.ts +++ b/packages/contracts/tests/Poll.test.ts @@ -85,8 +85,8 @@ describe("Poll", () => { pollId = (await maciContract.nextPollId()) - 1n; - const pollContractAddress = await maciContract.getPoll(pollId); - pollContract = PollFactory.connect(pollContractAddress, signer); + const pollContracts = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContracts.poll, signer); // deploy local poll const p = maciState.deployPoll( diff --git a/packages/contracts/tests/Tally.test.ts b/packages/contracts/tests/Tally.test.ts index 9f9f4c2ff5..a01d5e4e6e 100644 --- a/packages/contracts/tests/Tally.test.ts +++ b/packages/contracts/tests/Tally.test.ts @@ -83,27 +83,12 @@ describe("TallyVotes", () => { expect(receipt?.status).to.eq(1); - 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; + pollId = (await maciContract.nextPollId()) - 1n; - 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); + const pollContracts = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContracts.poll, signer); + mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); + tallyContract = TallyFactory.connect(pollContracts.tally, signer); // deploy local poll const p = maciState.deployPoll( @@ -261,27 +246,12 @@ describe("TallyVotes", () => { expect(receipt?.status).to.eq(1); - 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; + pollId = (await maciContract.nextPollId()) - 1n; - 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); + const pollContracts = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContracts.poll, signer); + mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); + tallyContract = TallyFactory.connect(pollContracts.tally, signer); // deploy local poll const p = maciState.deployPoll( @@ -584,27 +554,12 @@ describe("TallyVotes", () => { expect(receipt?.status).to.eq(1); - 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; + pollId = (await maciContract.nextPollId()) - 1n; - 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); + const pollContracts = await maciContract.getPoll(pollId); + pollContract = PollFactory.connect(pollContracts.poll, signer); + mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); + tallyContract = TallyFactory.connect(pollContracts.tally, signer); // deploy local poll const p = maciState.deployPoll( diff --git a/packages/contracts/tests/TallyNonQv.test.ts b/packages/contracts/tests/TallyNonQv.test.ts index bcb464b85c..1e587efeb9 100644 --- a/packages/contracts/tests/TallyNonQv.test.ts +++ b/packages/contracts/tests/TallyNonQv.test.ts @@ -77,27 +77,12 @@ describe("TallyVotesNonQv", () => { expect(receipt?.status).to.eq(1); - 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); + 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); // deploy local poll const p = maciState.deployPoll( diff --git a/packages/contracts/ts/genMaciState.ts b/packages/contracts/ts/genMaciState.ts index a23b80f882..b6caa3ca83 100644 --- a/packages/contracts/ts/genMaciState.ts +++ b/packages/contracts/ts/genMaciState.ts @@ -92,17 +92,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 pollAddr = await maciContract.getPoll(id); + const pollContracts = await maciContract.getPoll(id); actions.push({ type: "DeployPoll", blockNumber: event.blockNumber, transactionIndex: event.transactionIndex, - data: { pollId: id, pollAddr, pubKey }, + data: { pollId: id, pollAddr: pollContracts.poll, pubKey }, }); foundPollIds.add(Number(id)); - pollContractAddresses.set(BigInt(id), pollAddr); + pollContractAddresses.set(BigInt(id), pollContracts.poll); } if (sleepAmount) { diff --git a/packages/integrationTests/ts/__tests__/maci-keys.test.ts b/packages/integrationTests/ts/__tests__/maci-keys.test.ts index fe647eb754..52a86fcfb4 100644 --- a/packages/integrationTests/ts/__tests__/maci-keys.test.ts +++ b/packages/integrationTests/ts/__tests__/maci-keys.test.ts @@ -94,7 +94,8 @@ describe("integration tests private/public/keypair", () => { ); // we know it's the first poll so id is 0 - pollContract = PollFactory.connect(await maci.polls(0), signer); + const { poll } = await maci.polls(0); + pollContract = PollFactory.connect(poll, signer); }); it("should have the correct coordinator pub key set on chain", async () => { From 765ca42d391afc8026895d0e4d269b71435c3f73 Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:18:47 -0600 Subject: [PATCH 07/14] fix: remove max vote options param --- packages/circuits/circom/circuits.json | 2 +- .../circom/core/non-qv/processMessages.circom | 23 +++++++++--------- .../circom/core/qv/processMessages.circom | 23 +++++++++--------- .../ts/__tests__/CeremonyParams.test.ts | 12 ++-------- .../circuits/ts/__tests__/PollJoining.test.ts | 12 ++-------- .../ts/__tests__/ProcessMessages.test.ts | 18 +------------- .../circuits/ts/__tests__/TallyVotes.test.ts | 5 +--- .../circuits/ts/__tests__/utils/constants.ts | 1 - packages/circuits/ts/types.ts | 1 - packages/contracts/contracts/MACI.sol | 12 +--------- packages/contracts/contracts/Poll.sol | 6 ++--- packages/contracts/contracts/PollFactory.sol | 3 +-- .../contracts/interfaces/IPollFactory.sol | 2 -- .../contracts/tasks/deploy/poll/01-poll.ts | 6 +---- packages/contracts/tests/MACI.test.ts | 17 ++----------- .../contracts/tests/MessageProcessor.test.ts | 9 +------ packages/contracts/tests/Poll.test.ts | 13 +++------- packages/contracts/tests/PollFactory.test.ts | 2 -- packages/contracts/tests/Tally.test.ts | 11 +-------- packages/contracts/tests/TallyNonQv.test.ts | 18 ++------------ packages/contracts/ts/genMaciState.ts | 24 ++++++------------- packages/core/ts/MaciState.ts | 3 --- packages/core/ts/Poll.ts | 15 ++---------- packages/core/ts/__benchmarks__/index.ts | 2 -- .../core/ts/__benchmarks__/utils/constants.ts | 1 - packages/core/ts/__tests__/MaciState.test.ts | 10 +------- packages/core/ts/__tests__/Poll.test.ts | 22 ++++------------- packages/core/ts/__tests__/e2e.test.ts | 15 +----------- packages/core/ts/__tests__/utils/constants.ts | 1 - packages/core/ts/__tests__/utils/utils.ts | 3 +-- packages/core/ts/utils/types.ts | 2 -- .../ts/__tests__/integration.test.ts | 3 --- 32 files changed, 60 insertions(+), 237 deletions(-) diff --git a/packages/circuits/circom/circuits.json b/packages/circuits/circom/circuits.json index 8486bd8574..bcf56fbade 100644 --- a/packages/circuits/circom/circuits.json +++ b/packages/circuits/circom/circuits.json @@ -1,6 +1,6 @@ { "PollJoining_10_test": { - "file": "./core/anon/pollJoining", + "file": "./anon/pollJoining", "template": "PollJoining", "params": [10], "pubs": ["nullifier", "credits", "stateRoot", "pollPubKey"] diff --git a/packages/circuits/circom/core/non-qv/processMessages.circom b/packages/circuits/circom/core/non-qv/processMessages.circom index 9e6bd0dc3a..7f3a54da92 100644 --- a/packages/circuits/circom/core/non-qv/processMessages.circom +++ b/packages/circuits/circom/core/non-qv/processMessages.circom @@ -41,11 +41,11 @@ include "../../trees/incrementalQuinaryTree.circom"; var STATE_LEAF_VOICE_CREDIT_BALANCE_IDX = 2; var STATE_LEAF_TIMESTAMP_IDX = 3; var msgTreeZeroValue = 8370432830353022751713833565135785980866757267633941821328460903436894336785; + // Number of options for this poll. + var maxVoteOptions = VOTE_OPTION_TREE_ARITY ** voteOptionTreeDepth; // Number of users that have completed the sign up. signal input numSignUps; - // Number of options for this poll. - signal input maxVoteOptions; // Value of chainHash at beginning of batch signal input inputBatchHash; // Value of chainHash at end of batch @@ -229,7 +229,6 @@ include "../../trees/incrementalQuinaryTree.circom"; (computedNewVoteStateRoot[i], computedNewVoteBallotRoot[i]) = ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth)( numSignUps, - maxVoteOptions, stateRoots[i + 1], ballotRoots[i + 1], actualStateTreeDepth, @@ -278,6 +277,8 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { var BALLOT_NONCE_IDX = 0; // Ballot vote option (VO) root index. var BALLOT_VO_ROOT_IDX = 1; + // Number of options for this poll. + var maxVoteOptions = VOTE_OPTION_TREE_ARITY ** voteOptionTreeDepth; // Indices for elements within a state leaf. // Public key. @@ -291,8 +292,6 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // Inputs representing the message and the current state. signal input numSignUps; - signal input maxVoteOptions; - // The current value of the state tree root. signal input currentStateRoot; // The current value of the ballot tree root. @@ -330,9 +329,9 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // Intermediate signals. // currentVoteWeight * currentVoteWeight. - signal b; + signal currentVoteWeightSquare; // cmdNewVoteWeight * cmdNewVoteWeight. - signal c; + signal cmdNewVoteWeightSquare; // equal to newBallotVoRootMux (Mux1). signal newBallotVoRoot; @@ -390,12 +389,12 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // 5. Verify that currentVoteWeight exists in the ballot's vote option root // at cmdVoteOptionIndex. - b <== currentVoteWeight; - c <== cmdNewVoteWeight; + currentVoteWeightSquare <== currentVoteWeight; + cmdNewVoteWeightSquare <== cmdNewVoteWeight; var voiceCreditAmountValid = SafeGreaterEqThan(252)([ - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + b, - c + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare, + cmdNewVoteWeightSquare ]); var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); @@ -413,7 +412,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { var newSlVoiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + b - c + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare - cmdNewVoteWeightSquare ], voiceCreditAmountValid ); diff --git a/packages/circuits/circom/core/qv/processMessages.circom b/packages/circuits/circom/core/qv/processMessages.circom index 85cf443045..17d8b71d10 100644 --- a/packages/circuits/circom/core/qv/processMessages.circom +++ b/packages/circuits/circom/core/qv/processMessages.circom @@ -41,11 +41,11 @@ template ProcessMessages( var STATE_LEAF_VOICE_CREDIT_BALANCE_IDX = 2; var STATE_LEAF_TIMESTAMP_IDX = 3; var msgTreeZeroValue = 8370432830353022751713833565135785980866757267633941821328460903436894336785; + // Number of options for this poll. + var maxVoteOptions = VOTE_OPTION_TREE_ARITY ** voteOptionTreeDepth; // Number of users that have completed the sign up. signal input numSignUps; - // Number of options for this poll. - signal input maxVoteOptions; // Value of chainHash at beginning of batch signal input inputBatchHash; // Value of chainHash at end of batch @@ -225,7 +225,6 @@ template ProcessMessages( (computedNewVoteStateRoot[i], computedNewVoteBallotRoot[i]) = ProcessOne(stateTreeDepth, voteOptionTreeDepth)( numSignUps, - maxVoteOptions, stateRoots[i + 1], ballotRoots[i + 1], actualStateTreeDepth, @@ -274,6 +273,8 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { var BALLOT_NONCE_IDX = 0; // Ballot vote option (VO) root index. var BALLOT_VO_ROOT_IDX = 1; + // Number of options for this poll. + var maxVoteOptions = VOTE_OPTION_TREE_ARITY ** voteOptionTreeDepth; // Indices for elements within a state leaf. // Public key. @@ -287,8 +288,6 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // Inputs representing the message and the current state. signal input numSignUps; - signal input maxVoteOptions; - // The current value of the state tree root. signal input currentStateRoot; // The current value of the ballot tree root. @@ -326,9 +325,9 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // Intermediate signals. // currentVoteWeight * currentVoteWeight. - signal b; + signal currentVoteWeightSquare; // cmdNewVoteWeight * cmdNewVoteWeight. - signal c; + signal cmdNewVoteWeightSquare; // equal to newBallotVoRootMux (Mux1). signal newBallotVoRoot; @@ -386,12 +385,12 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { // 5. Verify that currentVoteWeight exists in the ballot's vote option root // at cmdVoteOptionIndex. - b <== currentVoteWeight * currentVoteWeight; - c <== cmdNewVoteWeight * cmdNewVoteWeight; + currentVoteWeightSquare <== currentVoteWeight * currentVoteWeight; + cmdNewVoteWeightSquare <== cmdNewVoteWeight * cmdNewVoteWeight; var voiceCreditAmountValid = SafeGreaterEqThan(252)([ - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + b, - c + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare, + cmdNewVoteWeightSquare ]); var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); @@ -409,7 +408,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { var newSlVoiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + b - c + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare - cmdNewVoteWeightSquare ], voiceCreditAmountValid ); diff --git a/packages/circuits/ts/__tests__/CeremonyParams.test.ts b/packages/circuits/ts/__tests__/CeremonyParams.test.ts index 1bc64a7c85..d8a705056b 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, VOTE_OPTION_TREE_ARITY, MESSAGE_BATCH_SIZE } from "maci-core"; +import { MaciState, Poll, STATE_TREE_ARITY, MESSAGE_BATCH_SIZE } from "maci-core"; import { hash5, IncrementalQuinTree, poseidon } from "maci-crypto"; import { PrivKey, Keypair, PCommand, Message, Ballot } from "maci-domainobjs"; @@ -18,11 +18,6 @@ describe("Ceremony param tests", () => { stateLeafBatchDepth: 2, }; - const maxValues = { - maxUsers: STATE_TREE_ARITY ** params.stateTreeDepth, - maxVoteOptions: VOTE_OPTION_TREE_ARITY ** params.voteOptionTreeDepth, - }; - const treeDepths = { intStateTreeDepth: 1, voteOptionTreeDepth: params.voteOptionTreeDepth, @@ -41,7 +36,6 @@ describe("Ceremony param tests", () => { "numSignUps", "batchEndIndex", "index", - "maxVoteOptions", "inputBatchHash", "outputBatchHash", "msgs", @@ -67,7 +61,7 @@ describe("Ceremony param tests", () => { circuit = await circomkitInstance.WitnessTester("processMessages", { file: "./core/qv/processMessages", template: "ProcessMessages", - params: [14, MESSAGE_BATCH_SIZE, 3], + params: [params.stateTreeDepth, MESSAGE_BATCH_SIZE, params.voteOptionTreeDepth], }); }); @@ -88,7 +82,6 @@ describe("Ceremony param tests", () => { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, MESSAGE_BATCH_SIZE, coordinatorKeypair, @@ -232,7 +225,6 @@ describe("Ceremony param tests", () => { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, MESSAGE_BATCH_SIZE, coordinatorKeypair, diff --git a/packages/circuits/ts/__tests__/PollJoining.test.ts b/packages/circuits/ts/__tests__/PollJoining.test.ts index d8af537895..5bfedadd8e 100644 --- a/packages/circuits/ts/__tests__/PollJoining.test.ts +++ b/packages/circuits/ts/__tests__/PollJoining.test.ts @@ -6,14 +6,7 @@ import { Keypair, Message, PCommand } from "maci-domainobjs"; import { IPollJoiningInputs } from "../types"; -import { - STATE_TREE_DEPTH, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; +import { STATE_TREE_DEPTH, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; import { circomkitInstance } from "./utils/utils"; describe("Poll Joining circuit", function test() { @@ -39,7 +32,7 @@ describe("Poll Joining circuit", function test() { before(async () => { circuit = await circomkitInstance.WitnessTester("pollJoining", { - file: "./core/anon/pollJoining", + file: "./anon/pollJoining", template: "PollJoining", params: [STATE_TREE_DEPTH], }); @@ -64,7 +57,6 @@ describe("Poll Joining circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, diff --git a/packages/circuits/ts/__tests__/ProcessMessages.test.ts b/packages/circuits/ts/__tests__/ProcessMessages.test.ts index 89b7ef316c..21e810435f 100644 --- a/packages/circuits/ts/__tests__/ProcessMessages.test.ts +++ b/packages/circuits/ts/__tests__/ProcessMessages.test.ts @@ -6,14 +6,7 @@ import { PrivKey, Keypair, PCommand, Message, Ballot, PubKey } from "maci-domain import { IProcessMessagesInputs } from "../types"; -import { - STATE_TREE_DEPTH, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; +import { STATE_TREE_DEPTH, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; import { circomkitInstance } from "./utils/utils"; describe("ProcessMessage circuit", function test() { @@ -25,7 +18,6 @@ describe("ProcessMessage circuit", function test() { "numSignUps", "batchEndIndex", "index", - "maxVoteOptions", "inputBatchHash", "outputBatchHash", "msgs", @@ -85,7 +77,6 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -172,7 +163,6 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -285,7 +275,6 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(2 + duration), // BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -374,7 +363,6 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(2 + duration), // BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -497,7 +485,6 @@ describe("ProcessMessage circuit", function test() { // Sign up and publish pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -563,7 +550,6 @@ describe("ProcessMessage circuit", function test() { maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -687,7 +673,6 @@ describe("ProcessMessage circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -818,7 +803,6 @@ describe("ProcessMessage circuit", function test() { maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, diff --git a/packages/circuits/ts/__tests__/TallyVotes.test.ts b/packages/circuits/ts/__tests__/TallyVotes.test.ts index 7003dc56de..24fc8eaa05 100644 --- a/packages/circuits/ts/__tests__/TallyVotes.test.ts +++ b/packages/circuits/ts/__tests__/TallyVotes.test.ts @@ -5,7 +5,7 @@ import { Keypair, PCommand, Message } from "maci-domainobjs"; import { ITallyVotesInputs } from "../types"; -import { STATE_TREE_DEPTH, duration, maxValues, messageBatchSize, voiceCreditBalance } from "./utils/constants"; +import { STATE_TREE_DEPTH, duration, messageBatchSize, voiceCreditBalance } from "./utils/constants"; import { generateRandomIndex, circomkitInstance } from "./utils/utils"; describe("TallyVotes circuit", function test() { @@ -77,7 +77,6 @@ describe("TallyVotes circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -158,7 +157,6 @@ describe("TallyVotes circuit", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -242,7 +240,6 @@ describe("TallyVotes circuit", function test() { // Deploy poll 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 e6a4574763..be532cf843 100644 --- a/packages/circuits/ts/__tests__/utils/constants.ts +++ b/packages/circuits/ts/__tests__/utils/constants.ts @@ -4,7 +4,6 @@ export const duration = 30; export const maxValues = { maxUsers: 25, - maxVoteOptions: 25, }; export const treeDepths = { diff --git a/packages/circuits/ts/types.ts b/packages/circuits/ts/types.ts index d939303cb2..1ac9ba3038 100644 --- a/packages/circuits/ts/types.ts +++ b/packages/circuits/ts/types.ts @@ -64,7 +64,6 @@ export interface IProcessMessagesInputs { numSignUps: bigint; batchEndIndex: bigint; index: bigint; - maxVoteOptions: bigint; inputBatchHash: bigint; outputBatchHash: bigint; msgs: bigint[]; diff --git a/packages/contracts/contracts/MACI.sol b/packages/contracts/contracts/MACI.sol index ae4e77c39d..ccf256c911 100644 --- a/packages/contracts/contracts/MACI.sol +++ b/packages/contracts/contracts/MACI.sol @@ -30,8 +30,6 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { 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 = uint256(6769006970205099520508948723718471724660867171122235270773600567925038008762); @@ -209,7 +207,6 @@ 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)), @@ -217,14 +214,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { vkRegistry: IVkRegistry(_vkRegistry) }); - address p = pollFactory.deploy( - _duration, - maxVoteOptions, - _treeDepths, - _messageBatchSize, - _coordinatorPubKey, - extContracts - ); + address p = pollFactory.deploy(_duration, _treeDepths, _messageBatchSize, _coordinatorPubKey, extContracts); address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, msg.sender, _mode); address tally = tallyFactory.deploy(_verifier, _vkRegistry, p, mp, msg.sender, _mode); diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index f89cde0b90..23d1c358f9 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -85,6 +85,8 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { uint256 internal constant BLANK_STATE_LEAF_HASH = uint256(6769006970205099520508948723718471724660867171122235270773600567925038008762); + uint8 internal constant VOTE_TREE_ARITY = 5; + /// @notice Poll voting nullifier mapping(uint256 => bool) private pollNullifier; @@ -113,7 +115,6 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { /// @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 @@ -121,7 +122,6 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { constructor( uint256 _duration, - uint256 _maxVoteOptions, TreeDepths memory _treeDepths, uint8 _messageBatchSize, PubKey memory _coordinatorPubKey, @@ -141,7 +141,7 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { // store duration of the poll duration = _duration; // store max vote options - maxVoteOptions = _maxVoteOptions; + maxVoteOptions = uint256(VOTE_TREE_ARITY) ** _treeDepths.voteOptionTreeDepth; // store message batch size messageBatchSize = _messageBatchSize; // store tree depth diff --git a/packages/contracts/contracts/PollFactory.sol b/packages/contracts/contracts/PollFactory.sol index 66de4b341b..e8fd93dc6d 100644 --- a/packages/contracts/contracts/PollFactory.sol +++ b/packages/contracts/contracts/PollFactory.sol @@ -17,14 +17,13 @@ contract PollFactory is Params, DomainObjs, IPollFactory { /// @inheritdoc IPollFactory function deploy( uint256 _duration, - uint256 _maxVoteOptions, Params.TreeDepths calldata _treeDepths, uint8 _messageBatchSize, DomainObjs.PubKey calldata _coordinatorPubKey, Params.ExtContracts calldata _extContracts ) public virtual returns (address pollAddr) { // deploy the poll - Poll poll = new Poll(_duration, _maxVoteOptions, _treeDepths, _messageBatchSize, _coordinatorPubKey, _extContracts); + Poll poll = new Poll(_duration, _treeDepths, _messageBatchSize, _coordinatorPubKey, _extContracts); // init Poll poll.init(); diff --git a/packages/contracts/contracts/interfaces/IPollFactory.sol b/packages/contracts/contracts/interfaces/IPollFactory.sol index a1e318ba3b..0051dfd7ec 100644 --- a/packages/contracts/contracts/interfaces/IPollFactory.sol +++ b/packages/contracts/contracts/interfaces/IPollFactory.sol @@ -9,7 +9,6 @@ import { DomainObjs } from "../utilities/DomainObjs.sol"; interface IPollFactory { /// @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 @@ -17,7 +16,6 @@ interface IPollFactory { /// @return The deployed Poll contract function deploy( uint256 _duration, - uint256 _maxVoteOptions, Params.TreeDepths calldata _treeDepths, uint8 _messageBatchSize, DomainObjs.PubKey calldata _coordinatorPubKey, diff --git a/packages/contracts/tasks/deploy/poll/01-poll.ts b/packages/contracts/tasks/deploy/poll/01-poll.ts index ca560a1ce6..f356d301a6 100644 --- a/packages/contracts/tasks/deploy/poll/01-poll.ts +++ b/packages/contracts/tasks/deploy/poll/01-poll.ts @@ -74,10 +74,7 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => const tallyContractAddress = pollContracts.tally; const pollContract = await deployment.getContract({ name: EContracts.Poll, address: pollContractAddress }); - const [maxVoteOptions, extContracts] = await Promise.all([ - pollContract.maxVoteOptions(), - pollContract.extContracts(), - ]); + const extContracts = await pollContract.extContracts(); const messageProcessorContract = await deployment.getContract({ name: EContracts.MessageProcessor, @@ -96,7 +93,6 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => contract: pollContract, args: [ pollDuration, - Number(maxVoteOptions), { intStateTreeDepth, voteOptionTreeDepth, diff --git a/packages/contracts/tests/MACI.test.ts b/packages/contracts/tests/MACI.test.ts index f4f35fb3a5..19bd9d7b14 100644 --- a/packages/contracts/tests/MACI.test.ts +++ b/packages/contracts/tests/MACI.test.ts @@ -10,14 +10,7 @@ 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, - maxVoteOptions, - messageBatchSize, - treeDepths, -} from "./constants"; +import { STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, messageBatchSize, treeDepths } from "./constants"; import { timeTravel, deployTestContracts } from "./utils"; describe("MACI", function test() { @@ -234,13 +227,7 @@ describe("MACI", function test() { expect(receipt?.status).to.eq(1); pollId = (await maciContract.nextPollId()) - 1n; - const p = maciState.deployPoll( - BigInt(deployTime + duration), - maxVoteOptions, - treeDepths, - messageBatchSize, - coordinator, - ); + const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message diff --git a/packages/contracts/tests/MessageProcessor.test.ts b/packages/contracts/tests/MessageProcessor.test.ts index b6db5b7c3e..598558d1af 100644 --- a/packages/contracts/tests/MessageProcessor.test.ts +++ b/packages/contracts/tests/MessageProcessor.test.ts @@ -21,7 +21,6 @@ import { STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, - maxVoteOptions, messageBatchSize, testProcessVk, testTallyVk, @@ -83,13 +82,7 @@ describe("MessageProcessor", () => { const deployTime = block!.timestamp; // deploy local poll - const p = maciState.deployPoll( - BigInt(deployTime + duration), - maxVoteOptions, - treeDepths, - messageBatchSize, - coordinator, - ); + const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); expect(p.toString()).to.eq(pollId.toString()); const messages = []; diff --git a/packages/contracts/tests/Poll.test.ts b/packages/contracts/tests/Poll.test.ts index 46692ce770..2859ca54cd 100644 --- a/packages/contracts/tests/Poll.test.ts +++ b/packages/contracts/tests/Poll.test.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { AbiCoder, Signer } from "ethers"; import { EthereumProvider } from "hardhat/types"; -import { MaciState } from "maci-core"; +import { MaciState, VOTE_OPTION_TREE_ARITY } from "maci-core"; import { NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { Keypair, Message, PCommand, PubKey } from "maci-domainobjs"; @@ -16,7 +16,6 @@ import { STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, - maxVoteOptions, messageBatchSize, testPollVk, testProcessVk, @@ -89,13 +88,7 @@ describe("Poll", () => { pollContract = PollFactory.connect(pollContracts.poll, signer); // deploy local poll - const p = maciState.deployPoll( - BigInt(deployTime + duration), - maxVoteOptions, - treeDepths, - messageBatchSize, - coordinator, - ); + const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message const messageData = [NOTHING_UP_MY_SLEEVE]; @@ -149,7 +142,7 @@ describe("Poll", () => { it("should have the correct max values set", async () => { const mvo = await pollContract.maxVoteOptions(); - expect(mvo.toString()).to.eq(maxVoteOptions.toString()); + expect(mvo.toString()).to.eq(BigInt(VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth).toString()); }); it("should have the correct tree depths set", async () => { diff --git a/packages/contracts/tests/PollFactory.test.ts b/packages/contracts/tests/PollFactory.test.ts index 12c387b06f..a388556cea 100644 --- a/packages/contracts/tests/PollFactory.test.ts +++ b/packages/contracts/tests/PollFactory.test.ts @@ -8,7 +8,6 @@ import { MACI, PollFactory, Verifier, VkRegistry } from "../typechain-types"; import { messageBatchSize, initialVoiceCreditBalance, - maxVoteOptions, STATE_TREE_DEPTH, treeDepths, ExtContractsStruct, @@ -40,7 +39,6 @@ describe("pollFactory", () => { it("should allow anyone to deploy a new poll", async () => { const tx = await pollFactory.deploy( "100", - maxVoteOptions, treeDepths, messageBatchSize, coordinatorPubKey.asContractParam(), diff --git a/packages/contracts/tests/Tally.test.ts b/packages/contracts/tests/Tally.test.ts index a01d5e4e6e..ed76475005 100644 --- a/packages/contracts/tests/Tally.test.ts +++ b/packages/contracts/tests/Tally.test.ts @@ -25,7 +25,6 @@ import { STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, - maxVoteOptions, messageBatchSize, testPollVk, testProcessVk, @@ -91,13 +90,7 @@ describe("TallyVotes", () => { tallyContract = TallyFactory.connect(pollContracts.tally, signer); // deploy local poll - const p = maciState.deployPoll( - BigInt(deployTime + duration), - maxVoteOptions, - treeDepths, - messageBatchSize, - coordinator, - ); + const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message const messageData = [NOTHING_UP_MY_SLEEVE]; @@ -256,7 +249,6 @@ describe("TallyVotes", () => { // deploy local poll const p = maciState.deployPoll( BigInt(deployTime + updatedDuration), - maxVoteOptions, { ...treeDepths, intStateTreeDepth, @@ -564,7 +556,6 @@ describe("TallyVotes", () => { // deploy local poll const p = maciState.deployPoll( BigInt(deployTime + updatedDuration), - maxVoteOptions, { ...treeDepths, intStateTreeDepth, diff --git a/packages/contracts/tests/TallyNonQv.test.ts b/packages/contracts/tests/TallyNonQv.test.ts index 1e587efeb9..7d4e218e40 100644 --- a/packages/contracts/tests/TallyNonQv.test.ts +++ b/packages/contracts/tests/TallyNonQv.test.ts @@ -21,15 +21,7 @@ import { Tally__factory as TallyFactory, } from "../typechain-types"; -import { - STATE_TREE_DEPTH, - duration, - maxVoteOptions, - messageBatchSize, - testProcessVk, - testTallyVk, - treeDepths, -} from "./constants"; +import { STATE_TREE_DEPTH, duration, messageBatchSize, testProcessVk, testTallyVk, treeDepths } from "./constants"; import { timeTravel, deployTestContracts } from "./utils"; describe("TallyVotesNonQv", () => { @@ -85,13 +77,7 @@ describe("TallyVotesNonQv", () => { tallyContract = TallyFactory.connect(pollContracts.tally, signer); // deploy local poll - const p = maciState.deployPoll( - BigInt(deployTime + duration), - maxVoteOptions, - treeDepths, - messageBatchSize, - coordinator, - ); + const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message const messageData = [NOTHING_UP_MY_SLEEVE]; diff --git a/packages/contracts/ts/genMaciState.ts b/packages/contracts/ts/genMaciState.ts index b6caa3ca83..4a9e800105 100644 --- a/packages/contracts/ts/genMaciState.ts +++ b/packages/contracts/ts/genMaciState.ts @@ -117,20 +117,16 @@ export const genMaciStateFromContract = async ( const pollContractAddress = pollContractAddresses.get(pollId)!; const pollContract = PollFactory.connect(pollContractAddress, provider); - 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(), - ]); + const [coordinatorPubKeyOnChain, [deployTime, duration], onChainTreeDepths, msgBatchSize] = await Promise.all([ + pollContract.coordinatorPubKey(), + pollContract.getDeployTimeAndDuration().then((values) => values.map(Number)), + pollContract.treeDepths(), + pollContract.messageBatchSize(), + ]); assert(coordinatorPubKeyOnChain[0].toString() === coordinatorKeypair.pubKey.rawPubKey[0].toString()); assert(coordinatorPubKeyOnChain[1].toString() === coordinatorKeypair.pubKey.rawPubKey[1].toString()); - const maxVoteOptions = Number(onChainMaxVoteOptions); - const treeDepths = { intStateTreeDepth: Number(onChainTreeDepths.intStateTreeDepth), voteOptionTreeDepth: Number(onChainTreeDepths.voteOptionTreeDepth), @@ -207,13 +203,7 @@ export const genMaciStateFromContract = async ( } case action.type === "DeployPoll" && action.data.pollId?.toString() === pollId.toString(): { - maciState.deployPoll( - BigInt(deployTime + duration), - maxVoteOptions, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); + maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinatorKeypair); break; } diff --git a/packages/core/ts/MaciState.ts b/packages/core/ts/MaciState.ts index 82df27cda7..506c51fbb1 100644 --- a/packages/core/ts/MaciState.ts +++ b/packages/core/ts/MaciState.ts @@ -65,7 +65,6 @@ 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. @@ -73,7 +72,6 @@ export class MaciState implements IMaciState { */ deployPoll( pollEndTimestamp: bigint, - maxVoteOptions: number, treeDepths: TreeDepths, messageBatchSize: number, coordinatorKeypair: Keypair, @@ -86,7 +84,6 @@ 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 c1eb33a4d0..1511410425 100644 --- a/packages/core/ts/Poll.ts +++ b/packages/core/ts/Poll.ts @@ -141,7 +141,6 @@ 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( @@ -149,14 +148,13 @@ 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.maxVoteOptions = VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth; this.maciStateRef = maciStateRef; this.pollId = BigInt(maciStateRef.polls.size); this.stateTreeDepth = maciStateRef.stateTreeDepth; @@ -798,7 +796,6 @@ export class Poll implements IPoll { return stringifyBigInts({ ...circuitInputs, coordinatorPublicKeyHash, - maxVoteOptions: BigInt(this.maxVoteOptions), }) as unknown as IProcessMessagesCircuitInputs; }; @@ -1303,7 +1300,6 @@ export class Poll implements IPoll { tallyBatchSize: Number(this.batchSizes.tallyBatchSize.toString()), messageBatchSize: Number(this.batchSizes.messageBatchSize.toString()), }, - this.maxVoteOptions, this.maciStateRef, ); @@ -1421,14 +1417,7 @@ 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, - json.maxVoteOptions, - maciState, - ); + const poll = new Poll(BigInt(json.pollEndTimestamp), new Keypair(), json.treeDepths, json.batchSizes, maciState); // set all properties poll.pollStateLeaves = json.pollStateLeaves.map((leaf) => StateLeaf.fromJSON(leaf)); diff --git a/packages/core/ts/__benchmarks__/index.ts b/packages/core/ts/__benchmarks__/index.ts index 72c13f1e20..ef9bb44ccf 100644 --- a/packages/core/ts/__benchmarks__/index.ts +++ b/packages/core/ts/__benchmarks__/index.ts @@ -6,7 +6,6 @@ import { MaciState } from ".."; import { COORDINATOR_KEYPAIR, DURATION, - MAX_VALUES, MESSAGE_BATCH_SIZE, STATE_TREE_DEPTH, TREE_DEPTHS, @@ -35,7 +34,6 @@ 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 5c41dcb71d..aa67d86349 100644 --- a/packages/core/ts/__benchmarks__/utils/constants.ts +++ b/packages/core/ts/__benchmarks__/utils/constants.ts @@ -8,7 +8,6 @@ export const STATE_TREE_DEPTH = 10; export const MAX_VALUES = { maxUsers: 25, - maxVoteOptions: 25, }; export const TREE_DEPTHS = { diff --git a/packages/core/ts/__tests__/MaciState.test.ts b/packages/core/ts/__tests__/MaciState.test.ts index c14f6ed70c..1c6ce09c10 100644 --- a/packages/core/ts/__tests__/MaciState.test.ts +++ b/packages/core/ts/__tests__/MaciState.test.ts @@ -7,14 +7,7 @@ import { MaciState } from "../MaciState"; import { STATE_TREE_DEPTH } from "../utils/constants"; import { IJsonMaciState } from "../utils/types"; -import { - coordinatorKeypair, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; +import { coordinatorKeypair, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; describe("MaciState", function test() { this.timeout(100000); @@ -36,7 +29,6 @@ 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, diff --git a/packages/core/ts/__tests__/Poll.test.ts b/packages/core/ts/__tests__/Poll.test.ts index a8378c453d..b56400abe5 100644 --- a/packages/core/ts/__tests__/Poll.test.ts +++ b/packages/core/ts/__tests__/Poll.test.ts @@ -6,14 +6,7 @@ import { MaciState } from "../MaciState"; import { Poll } from "../Poll"; import { STATE_TREE_DEPTH } from "../utils/constants"; -import { - coordinatorKeypair, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; +import { coordinatorKeypair, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; describe("Poll", function test() { this.timeout(90000); @@ -22,7 +15,6 @@ 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, @@ -124,10 +116,12 @@ describe("Poll", function test() { }); it("should throw if a message has an invalid vote option index (>= max vote options)", () => { + const maxVoteOptions = 25n; + const command = new PCommand( BigInt(stateIndex), pollPubKey, - BigInt(maxValues.maxVoteOptions), + maxVoteOptions, // voice credits spent would be this value ** this value 1n, 1n, @@ -246,7 +240,6 @@ 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, @@ -270,7 +263,6 @@ describe("Poll", function test() { 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, @@ -324,7 +316,6 @@ 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, @@ -418,7 +409,6 @@ 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, @@ -487,7 +477,6 @@ describe("Poll", function test() { // deploy a second poll const secondPollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -538,7 +527,6 @@ 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, @@ -557,7 +545,6 @@ 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, @@ -583,7 +570,6 @@ 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 84fd0e3e6e..3827ed5feb 100644 --- a/packages/core/ts/__tests__/e2e.test.ts +++ b/packages/core/ts/__tests__/e2e.test.ts @@ -6,14 +6,7 @@ import { MaciState } from "../MaciState"; import { Poll } from "../Poll"; import { STATE_TREE_DEPTH, STATE_TREE_ARITY } from "../utils/constants"; -import { - coordinatorKeypair, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; +import { coordinatorKeypair, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; import { TestHarness, calculateTotal } from "./utils/utils"; describe("MaciState/Poll e2e", function test() { @@ -59,7 +52,6 @@ describe("MaciState/Poll e2e", function test() { // deploy a poll pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -155,7 +147,6 @@ describe("MaciState/Poll e2e", function test() { // deploy a poll pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -266,7 +257,6 @@ describe("MaciState/Poll e2e", function test() { // deploy a poll pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -356,7 +346,6 @@ describe("MaciState/Poll e2e", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -450,7 +439,6 @@ describe("MaciState/Poll e2e", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, @@ -597,7 +585,6 @@ describe("MaciState/Poll e2e", function test() { pollId = maciState.deployPoll( BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues.maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, diff --git a/packages/core/ts/__tests__/utils/constants.ts b/packages/core/ts/__tests__/utils/constants.ts index a163a7ac06..04dd352a23 100644 --- a/packages/core/ts/__tests__/utils/constants.ts +++ b/packages/core/ts/__tests__/utils/constants.ts @@ -7,7 +7,6 @@ export const coordinatorKeypair = new Keypair(); export const maxValues = { maxUsers: 25, - maxVoteOptions: 25, }; export const treeDepths = { diff --git a/packages/core/ts/__tests__/utils/utils.ts b/packages/core/ts/__tests__/utils/utils.ts index 16d2d5f493..85f86ffb15 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, maxValues, messageBatchSize, treeDepths, voiceCreditBalance } from "./constants"; +import { duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./constants"; /** * Calculates the total of a tally result @@ -36,7 +36,6 @@ 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/utils/types.ts b/packages/core/ts/utils/types.ts index c6494fd3fe..d1b9a79708 100644 --- a/packages/core/ts/utils/types.ts +++ b/packages/core/ts/utils/types.ts @@ -48,7 +48,6 @@ export interface IMaciState { // This method is used for deploying poll. deployPoll( pollEndTimestamp: bigint, - maxVoteOptions: number, treeDepths: TreeDepths, messageBatchSize: number, coordinatorKeypair: Keypair, @@ -166,7 +165,6 @@ export interface IProcessMessagesCircuitInputs { numSignUps: string; batchEndIndex: string; index: string; - maxVoteOptions: string; msgRoot: string; coordinatorPublicKeyHash: string; inputBatchHash: string; diff --git a/packages/integrationTests/ts/__tests__/integration.test.ts b/packages/integrationTests/ts/__tests__/integration.test.ts index 526d443d16..362b141e61 100644 --- a/packages/integrationTests/ts/__tests__/integration.test.ts +++ b/packages/integrationTests/ts/__tests__/integration.test.ts @@ -105,8 +105,6 @@ describe("Integration tests", function test() { // 3. deploy maci contracts = await deploy({ stateTreeDepth: STATE_TREE_DEPTH, initialVoiceCredits, signer }); - const maxVoteOptions = 25; - // 4. create a poll pollContracts = await deployPoll({ pollDuration: duration, @@ -128,7 +126,6 @@ describe("Integration tests", function test() { pollId = maciState.deployPoll( BigInt(Date.now() + duration * 60000), - maxVoteOptions, treeDepths, messageBatchSize, coordinatorKeypair, From 1c8fa1b922929a3753f7e6ff92aa6806a7998048 Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:45:06 -0600 Subject: [PATCH 08/14] chore: update zkeys link --- .github/scripts/downloadZkeys.ts | 2 +- packages/core/ts/__tests__/Poll.test.ts | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/scripts/downloadZkeys.ts b/.github/scripts/downloadZkeys.ts index fe19ff2bd8..8a1b6bbc07 100644 --- a/.github/scripts/downloadZkeys.ts +++ b/.github/scripts/downloadZkeys.ts @@ -6,7 +6,7 @@ import path from "path"; const ZKEY_PATH = path.resolve(process.argv.slice(3)[0]); const ZKEYS_URLS = { - test: "https://maci-develop-fra.s3.eu-central-1.amazonaws.com/v2.0.0/maci_artifacts_10-2-1-2_test.tar.gz", + test: "https://maci-develop-fra.s3.eu-central-1.amazonaws.com/v3.0.0/maci_artifacts_v3.0.0_test.tar.gz", prod: "https://maci-develop-fra.s3.eu-central-1.amazonaws.com/v2.0.0/maci_artifacts_14-9-2-3_prod.tar.gz", }; const ARCHIVE_NAME = path.resolve(ZKEY_PATH, "maci_keys.tar.gz"); diff --git a/packages/core/ts/__tests__/Poll.test.ts b/packages/core/ts/__tests__/Poll.test.ts index b56400abe5..2be83d427f 100644 --- a/packages/core/ts/__tests__/Poll.test.ts +++ b/packages/core/ts/__tests__/Poll.test.ts @@ -4,7 +4,7 @@ import { PCommand, Keypair, StateLeaf, PrivKey, Ballot } from "maci-domainobjs"; import { MaciState } from "../MaciState"; import { Poll } from "../Poll"; -import { STATE_TREE_DEPTH } from "../utils/constants"; +import { STATE_TREE_DEPTH, VOTE_OPTION_TREE_ARITY } from "../utils/constants"; import { coordinatorKeypair, duration, messageBatchSize, treeDepths, voiceCreditBalance } from "./utils/constants"; @@ -116,12 +116,10 @@ describe("Poll", function test() { }); it("should throw if a message has an invalid vote option index (>= max vote options)", () => { - const maxVoteOptions = 25n; - const command = new PCommand( BigInt(stateIndex), pollPubKey, - maxVoteOptions, + BigInt(VOTE_OPTION_TREE_ARITY ** treeDepths.voteOptionTreeDepth), // voice credits spent would be this value ** this value 1n, 1n, From a68701b409284a1d6c787aa275a2eaf26b55ee5d Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:17:32 -0600 Subject: [PATCH 09/14] chore: optimize lean imt and minor improvements --- .../ts/__tests__/MessageValidator.test.ts | 68 ++++++++++++++++++- packages/cli/deploy-config.json | 1 - packages/cli/deployed-contracts.json | 1 - packages/cli/ts/commands/joinPoll.ts | 7 +- .../contracts/contracts/trees/LeanIMT.sol | 8 +-- 5 files changed, 73 insertions(+), 12 deletions(-) delete mode 100644 packages/cli/deploy-config.json delete mode 100644 packages/cli/deployed-contracts.json diff --git a/packages/circuits/ts/__tests__/MessageValidator.test.ts b/packages/circuits/ts/__tests__/MessageValidator.test.ts index 1d5e0a327f..05e16392a8 100644 --- a/packages/circuits/ts/__tests__/MessageValidator.test.ts +++ b/packages/circuits/ts/__tests__/MessageValidator.test.ts @@ -28,7 +28,7 @@ describe("MessageValidator circuit", function test() { "currentVotesForOption", "voteWeight", ], - ["isValid"] + ["isValid", "isStateLeafIndexValid", "isVoteOptionIndexValid"] >; before(async () => { @@ -76,6 +76,10 @@ describe("MessageValidator circuit", function test() { await circuit.expectConstraintPass(witness); const isValid = await getSignal(circuit, witness, "isValid"); expect(isValid.toString()).to.be.eq("1"); + const isStateLeafIndexValid = await getSignal(circuit, witness, "isStateLeafIndexValid"); + expect(isStateLeafIndexValid.toString()).to.be.eq("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the signature is invalid", async () => { @@ -85,6 +89,10 @@ describe("MessageValidator circuit", function test() { 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("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the pubkey is invalid", async () => { @@ -94,6 +102,10 @@ describe("MessageValidator circuit", function test() { 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("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if there are insufficient voice credits", async () => { @@ -103,6 +115,10 @@ describe("MessageValidator circuit", function test() { 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("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the nonce is invalid", async () => { @@ -112,6 +128,10 @@ describe("MessageValidator circuit", function test() { 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("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the state leaf index is invalid", async () => { @@ -121,6 +141,10 @@ describe("MessageValidator circuit", function test() { 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("1"); }); it("should be invalid if the vote option index is invalid", async () => { @@ -130,6 +154,10 @@ describe("MessageValidator circuit", function test() { 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"); }); it("should be invalid if the vote option index is invalid", async () => { @@ -139,6 +167,10 @@ describe("MessageValidator circuit", function test() { 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"); }); }); @@ -161,7 +193,7 @@ describe("MessageValidator circuit", function test() { "currentVotesForOption", "voteWeight", ], - ["isValid"] + ["isValid", "isStateLeafIndexValid", "isVoteOptionIndexValid"] >; before(async () => { @@ -209,6 +241,10 @@ describe("MessageValidator circuit", function test() { await circuit.expectConstraintPass(witness); const isValid = await getSignal(circuit, witness, "isValid"); expect(isValid.toString()).to.be.eq("1"); + const isStateLeafIndexValid = await getSignal(circuit, witness, "isStateLeafIndexValid"); + expect(isStateLeafIndexValid.toString()).to.be.eq("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the signature is invalid", async () => { @@ -218,6 +254,10 @@ describe("MessageValidator circuit", function test() { 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("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the pubkey is invalid", async () => { @@ -227,6 +267,10 @@ describe("MessageValidator circuit", function test() { 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("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if there are insufficient voice credits", async () => { @@ -236,6 +280,10 @@ describe("MessageValidator circuit", function test() { 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("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the nonce is invalid", async () => { @@ -245,6 +293,10 @@ describe("MessageValidator circuit", function test() { 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("1"); + const isVoteOptionIndexValid = await getSignal(circuit, witness, "isVoteOptionIndexValid"); + expect(isVoteOptionIndexValid.toString()).to.be.eq("1"); }); it("should be invalid if the state leaf index is invalid", async () => { @@ -254,6 +306,10 @@ describe("MessageValidator circuit", function test() { 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("1"); }); it("should be invalid if the vote option index is invalid", async () => { @@ -263,6 +319,10 @@ describe("MessageValidator circuit", function test() { 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"); }); it("should be invalid if the vote option index is invalid", async () => { @@ -272,6 +332,10 @@ describe("MessageValidator circuit", function test() { 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/cli/deploy-config.json b/packages/cli/deploy-config.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/packages/cli/deploy-config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/packages/cli/deployed-contracts.json b/packages/cli/deployed-contracts.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/packages/cli/deployed-contracts.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/packages/cli/ts/commands/joinPoll.ts b/packages/cli/ts/commands/joinPoll.ts index b86242b8ee..a706e185ae 100644 --- a/packages/cli/ts/commands/joinPoll.ts +++ b/packages/cli/ts/commands/joinPoll.ts @@ -39,7 +39,6 @@ const getStateIndexAndCreditBalance = ( loadedStateIndex = BigInt(index); } else { logError("State leaf not found"); - process.exit(); } } if (!newVoiceCreditBalance) { @@ -48,7 +47,6 @@ const getStateIndexAndCreditBalance = ( loadedCreditBalance = balance; } else { logError("Voice credit balance not found"); - process.exit(); } } @@ -86,6 +84,7 @@ const generateAndVerifyProof = async ( // verify it const isValid = await verifyProof(r.publicSignals, r.proof, pollVk); + if (!isValid) { throw new Error("Generated an invalid proof"); } @@ -117,8 +116,7 @@ const joiningCircuitInputs = ( 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 [pubKeyX, pubKeyY] = pubKey.asArray(); const stateLeafArray = [pubKeyX, pubKeyY, voiceCreditBalance, timestamp]; assert(credits <= voiceCreditBalance, "Credits must be lower than signed up credits"); @@ -235,6 +233,7 @@ export const joinPoll = async ({ let signUpData: IGenSignUpTree | undefined; let currentStateRootIndex: number; let circuitInputs: CircuitInputs; + if (stateFile) { try { const file = await fs.promises.readFile(stateFile); diff --git a/packages/contracts/contracts/trees/LeanIMT.sol b/packages/contracts/contracts/trees/LeanIMT.sol index c82f8d7a0b..cc3fe22448 100644 --- a/packages/contracts/contracts/trees/LeanIMT.sol +++ b/packages/contracts/contracts/trees/LeanIMT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import { PoseidonT3 } from "../crypto/PoseidonT3.sol"; @@ -57,7 +57,7 @@ library InternalLeanIMT { // A new insertion can increase a tree's depth by at most 1, // and only if the number of leaves supported by the current // depth is less than the number of leaves to be supported after insertion. - if (2 ** treeDepth < index + 1) { + if (1 << treeDepth < index + 1) { ++treeDepth; } @@ -123,7 +123,7 @@ library InternalLeanIMT { // Calculate the depth of the tree after adding the new values. // Unlike the 'insert' function, we need a while here as // N insertions can increase the tree's depth more than once. - while (2 ** treeDepth < treeSize + leaves.length) { + while (1 << treeDepth < treeSize + leaves.length) { ++treeDepth; } @@ -156,7 +156,7 @@ library InternalLeanIMT { leftNode = currentLevelNewNodes[(i + nextLevelStartIndex) * 2 - currentLevelStartIndex]; } - uint256 rightNode; + uint256 rightNode = 0; // Assign the right node if the value exists. if ((i + nextLevelStartIndex) * 2 + 1 < currentLevelSize) { From c94bd8e52713a976d639aaeac3226cc4c2ea9fb5 Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:19:15 -0600 Subject: [PATCH 10/14] refactor: cleanup anon poll joining - [x] Get rid of tally and message processor addresses params for cli - [x] Return empty ballot roots as param --- .../circom/core/non-qv/processMessages.circom | 19 ++++--------- .../circom/core/qv/processMessages.circom | 6 ++--- packages/cli/tests/constants.ts | 1 - packages/cli/tests/e2e/e2e.test.ts | 1 - packages/cli/tests/unit/poll.test.ts | 22 +++++++-------- packages/cli/ts/commands/genProofs.ts | 6 +---- packages/cli/ts/commands/joinPoll.ts | 15 +++++++---- packages/cli/ts/commands/poll.ts | 3 +-- packages/cli/ts/commands/proveOnChain.ts | 27 ++----------------- packages/cli/ts/commands/verify.ts | 22 ++------------- packages/cli/ts/index.ts | 15 ----------- packages/cli/ts/utils/interfaces.ts | 25 ----------------- packages/contracts/contracts/MACI.sol | 13 ++++++--- .../contracts/contracts/MessageProcessor.sol | 3 --- packages/contracts/contracts/Poll.sol | 17 ++++++++---- packages/contracts/contracts/PollFactory.sol | 12 +++++++-- .../contracts/interfaces/IPollFactory.sol | 4 ++- .../contracts/tasks/deploy/poll/01-poll.ts | 4 +++ packages/contracts/tests/PollFactory.test.ts | 6 ++++- .../ts/__tests__/integration.test.ts | 7 +---- 20 files changed, 77 insertions(+), 151 deletions(-) diff --git a/packages/circuits/circom/core/non-qv/processMessages.circom b/packages/circuits/circom/core/non-qv/processMessages.circom index 7f3a54da92..24e754d90e 100644 --- a/packages/circuits/circom/core/non-qv/processMessages.circom +++ b/packages/circuits/circom/core/non-qv/processMessages.circom @@ -179,7 +179,6 @@ include "../../trees/incrementalQuinaryTree.circom"; var computedCommandsPackedCommandOut[batchSize][PACKED_CMD_LENGTH]; for (var i = 0; i < batchSize; i++) { - var message[MSG_LENGTH]; for (var j = 0; j < MSG_LENGTH; j++) { message[j] = msgs[i][j]; } @@ -290,7 +289,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { var STATE_LEAF_TIMESTAMP_IDX = 3; var N_BITS = 252; - // Inputs representing the message and the current state. + // Number of users that have completed the sign up. signal input numSignUps; // The current value of the state tree root. signal input currentStateRoot; @@ -327,11 +326,6 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { signal output newStateRoot; signal output newBallotRoot; - // Intermediate signals. - // currentVoteWeight * currentVoteWeight. - signal currentVoteWeightSquare; - // cmdNewVoteWeight * cmdNewVoteWeight. - signal cmdNewVoteWeightSquare; // equal to newBallotVoRootMux (Mux1). signal newBallotVoRoot; @@ -357,7 +351,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { packedCmd ); - // 2. If isValid is equal to zero, generate indices for leaf zero. + // 2. If computedIsStateLeafIndexValid is equal to zero, generate indices for leaf zero. // Otherwise, generate indices for command.stateIndex. var stateIndexMux = Mux1()([0, cmdStateIndex], computedIsStateLeafIndexValid); var computedStateLeafPathIndices[stateTreeDepth] = MerkleGeneratePathIndices(stateTreeDepth)(stateIndexMux); @@ -389,12 +383,9 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // 5. Verify that currentVoteWeight exists in the ballot's vote option root // at cmdVoteOptionIndex. - currentVoteWeightSquare <== currentVoteWeight; - cmdNewVoteWeightSquare <== cmdNewVoteWeight; - var voiceCreditAmountValid = SafeGreaterEqThan(252)([ - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare, - cmdNewVoteWeightSquare + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeight, + cmdNewVoteWeight ]); var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); @@ -412,7 +403,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { var newSlVoiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare - cmdNewVoteWeightSquare + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeight - cmdNewVoteWeight ], voiceCreditAmountValid ); diff --git a/packages/circuits/circom/core/qv/processMessages.circom b/packages/circuits/circom/core/qv/processMessages.circom index 17d8b71d10..ba1579b8f5 100644 --- a/packages/circuits/circom/core/qv/processMessages.circom +++ b/packages/circuits/circom/core/qv/processMessages.circom @@ -174,8 +174,6 @@ template ProcessMessages( var computedCommandsPackedCommandOut[batchSize][PACKED_CMD_LENGTH]; for (var i = 0; i < batchSize; i++) { - var message[MSG_LENGTH]; - for (var j = 0; j < MSG_LENGTH; j++) { message[j] = msgs[i][j]; } @@ -286,7 +284,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { var STATE_LEAF_TIMESTAMP_IDX = 3; var N_BITS = 252; - // Inputs representing the message and the current state. + // Number of users that have completed the sign up. signal input numSignUps; // The current value of the state tree root. signal input currentStateRoot; @@ -353,7 +351,7 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { packedCmd ); - // 2. If isValid is equal to zero, generate indices for leaf zero. + // 2. If computedIsStateLeafIndexValid is equal to zero, generate indices for leaf zero. // Otherwise, generate indices for command.stateIndex. var stateIndexMux = Mux1()([0, cmdStateIndex], computedIsStateLeafIndexValid); var computedStateLeafPathIndices[stateTreeDepth] = MerkleGeneratePathIndices(stateTreeDepth)(stateIndexMux); diff --git a/packages/cli/tests/constants.ts b/packages/cli/tests/constants.ts index 147b0c86ee..14397f0067 100644 --- a/packages/cli/tests/constants.ts +++ b/packages/cli/tests/constants.ts @@ -144,7 +144,6 @@ export const verifyArgs = async (): Promise> => { pollId: 0n, tallyData, maciAddress: tallyData.maci, - tallyAddress: tallyData.tallyAddress, }; }; diff --git a/packages/cli/tests/e2e/e2e.test.ts b/packages/cli/tests/e2e/e2e.test.ts index 4674b1169b..4508fd8743 100644 --- a/packages/cli/tests/e2e/e2e.test.ts +++ b/packages/cli/tests/e2e/e2e.test.ts @@ -233,7 +233,6 @@ describe("e2e tests", function test() { ...(await verifyArgs()), tallyData: tallyFileData, maciAddress: tallyFileData.maci, - tallyAddress: tallyFileData.tallyAddress, signer, }); }); diff --git a/packages/cli/tests/unit/poll.test.ts b/packages/cli/tests/unit/poll.test.ts index c646c2389b..4247bd3a0e 100644 --- a/packages/cli/tests/unit/poll.test.ts +++ b/packages/cli/tests/unit/poll.test.ts @@ -48,12 +48,10 @@ describe("poll", function test() { it("should get current poll properly", async () => { const pollData = await getPoll({ maciAddress: maciAddresses.maciAddress, - tallyAddress: pollAddresses.tally, signer, }); const samePollData = await getPoll({ maciAddress: maciAddresses.maciAddress, - tallyAddress: pollAddresses.tally, pollId: pollData.id, signer, }); @@ -65,7 +63,6 @@ describe("poll", function test() { it("should get finished poll properly", async () => { const pollData = await getPoll({ maciAddress: maciAddresses.maciAddress, - tallyAddress: pollAddresses.tally, provider: signer.provider!, }); @@ -74,7 +71,6 @@ describe("poll", function test() { const finishedPollData = await getPoll({ maciAddress: maciAddresses.maciAddress, - tallyAddress: pollAddresses.tally, signer, }); @@ -84,21 +80,21 @@ describe("poll", function test() { }); it("should throw error if there are no signer and provider", async () => { - await expect( - getPoll({ maciAddress: maciAddresses.maciAddress, tallyAddress: pollAddresses.tally, pollId: -1n }), - ).eventually.rejectedWith("No signer and provider are provided"); + await expect(getPoll({ maciAddress: maciAddresses.maciAddress, pollId: -1n })).eventually.rejectedWith( + "No signer and provider are provided", + ); }); it("should throw error if current poll id is invalid", async () => { - await expect( - getPoll({ maciAddress: maciAddresses.maciAddress, tallyAddress: pollAddresses.tally, pollId: -1n, signer }), - ).eventually.rejectedWith("Invalid poll id -1"); + await expect(getPoll({ maciAddress: maciAddresses.maciAddress, pollId: -1n, signer })).eventually.rejectedWith( + "Invalid poll id -1", + ); }); it("should throw error if current poll is not deployed", async () => { - await expect( - getPoll({ maciAddress: maciAddresses.maciAddress, tallyAddress: pollAddresses.tally, pollId: 9000n, signer }), - ).eventually.rejectedWith("MACI contract doesn't have any deployed poll 9000"); + await expect(getPoll({ maciAddress: maciAddresses.maciAddress, pollId: 9000n, signer })).eventually.rejectedWith( + "MACI contract doesn't have any deployed poll 9000", + ); }); }); }); diff --git a/packages/cli/ts/commands/genProofs.ts b/packages/cli/ts/commands/genProofs.ts index 80bd102e20..e302a10c63 100644 --- a/packages/cli/ts/commands/genProofs.ts +++ b/packages/cli/ts/commands/genProofs.ts @@ -53,7 +53,6 @@ export const genProofs = async ({ blocksPerBatch, endBlock, signer, - tallyAddress, useQuadraticVoting = true, quiet = true, }: GenProofsArgs): Promise => { @@ -341,9 +340,6 @@ export const genProofs = async ({ BigInt(asHex(tallyCircuitInputs!.newSpentVoiceCreditSubtotalSalt as BigNumberish)), ); - // get the tally contract address - const tallyContractAddress = tallyAddress || (await readContractAddress(`Tally-${pollId}`, network?.name)); - let newPerVOSpentVoiceCreditsCommitment: bigint | undefined; let newTallyCommitment: bigint; @@ -354,7 +350,7 @@ export const genProofs = async ({ network: network?.name, chainId: network?.chainId.toString(), isQuadratic: useQuadraticVoting, - tallyAddress: tallyContractAddress, + tallyAddress: pollContracts.tally, newTallyCommitment: asHex(tallyCircuitInputs!.newTallyCommitment as BigNumberish), results: { tally: poll.tallyResult.map((x) => x.toString()), diff --git a/packages/cli/ts/commands/joinPoll.ts b/packages/cli/ts/commands/joinPoll.ts index a706e185ae..7bb4347326 100644 --- a/packages/cli/ts/commands/joinPoll.ts +++ b/packages/cli/ts/commands/joinPoll.ts @@ -6,7 +6,6 @@ import { CircuitInputs, IJsonMaciState, MaciState, IPollJoiningCircuitInputs } f import { poseidon, 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"; @@ -29,7 +28,7 @@ const getStateIndexAndCreditBalance = ( newVoiceCreditBalance: bigint | null, stateLeaves: StateLeaf[], userMaciPubKey: PubKey, -) => { +): [bigint | null, bigint | null] => { let loadedStateIndex = stateIndex; let loadedCreditBalance = newVoiceCreditBalance; @@ -41,6 +40,7 @@ const getStateIndexAndCreditBalance = ( logError("State leaf not found"); } } + if (!newVoiceCreditBalance) { const balance = stateLeaves[Number(loadedStateIndex!)].voiceCreditBalance; if (balance) { @@ -72,7 +72,7 @@ const generateAndVerifyProof = async ( witnessExePath: string | undefined, wasmPath: string | undefined, pollVk: IVkObjectParams, -) => { +): Promise => { const r = await genProof({ inputs, zkeyPath, @@ -119,7 +119,9 @@ const joiningCircuitInputs = ( const [pubKeyX, pubKeyY] = pubKey.asArray(); const stateLeafArray = [pubKeyX, pubKeyY, voiceCreditBalance, timestamp]; - assert(credits <= voiceCreditBalance, "Credits must be lower than signed up credits"); + if (credits <= voiceCreditBalance) { + logError("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)); @@ -386,7 +388,10 @@ const parsePollJoinEvents = async ({ startBlock, currentBlock, pollPublicKey, -}: IParsePollJoinEventsArgs) => { +}: IParsePollJoinEventsArgs): Promise<{ + pollStateIndex?: string; + voiceCredits?: string; +}> => { // 1000 blocks at a time for (let block = startBlock; block <= currentBlock; block += BLOCKS_STEP) { const toBlock = Math.min(block + BLOCKS_STEP - 1, currentBlock); diff --git a/packages/cli/ts/commands/poll.ts b/packages/cli/ts/commands/poll.ts index 0f1c4a2ab7..5809b2a645 100644 --- a/packages/cli/ts/commands/poll.ts +++ b/packages/cli/ts/commands/poll.ts @@ -17,7 +17,6 @@ import { logError, logGreen, success } from "../utils/theme"; */ export const getPoll = async ({ maciAddress, - tallyAddress, signer, provider, pollId, @@ -53,7 +52,7 @@ export const getPoll = async ({ const numSignups = await (isMerged ? pollContract.numSignups() : maciContract.numSignUps()); // get the poll mode - const tallyContract = TallyFactory.connect(tallyAddress, signer ?? provider); + const tallyContract = TallyFactory.connect(pollContracts.tally, signer ?? provider); const mode = await tallyContract.mode(); logGreen( diff --git a/packages/cli/ts/commands/proveOnChain.ts b/packages/cli/ts/commands/proveOnChain.ts index dcaef8c32b..6b25cdaf5f 100644 --- a/packages/cli/ts/commands/proveOnChain.ts +++ b/packages/cli/ts/commands/proveOnChain.ts @@ -42,8 +42,6 @@ export const proveOnChain = async ({ pollId, proofDir, maciAddress, - messageProcessorAddress, - tallyAddress, tallyFile, signer, quiet = true, @@ -58,32 +56,11 @@ export const proveOnChain = async ({ logError("MACI contract address is empty"); } - const messageProcessorContractAddress = - messageProcessorAddress || (await readContractAddress(`MessageProcessor-${pollId}`, network?.name)); - - if (!messageProcessorContractAddress) { - logError("MessageProcessor contract address is empty"); - } - - const tallyContractAddress = tallyAddress || (await readContractAddress(`Tally-${pollId}`, network?.name)); - - if (!tallyContractAddress) { - logError("Tally contract address is empty"); - } - // check contracts are deployed on chain if (!(await contractExists(signer.provider!, maciContractAddress))) { logError("MACI contract does not exist"); } - if (!(await contractExists(signer.provider!, messageProcessorContractAddress))) { - logError("MessageProcessor contract does not exist"); - } - - if (!(await contractExists(signer.provider!, tallyContractAddress))) { - logError("Tally contract does not exist"); - } - const maciContract = MACIFactory.connect(maciContractAddress, signer); const pollContracts = await maciContract.polls(pollId); @@ -93,8 +70,8 @@ export const proveOnChain = async ({ const pollContract = PollFactory.connect(pollContracts.poll, signer); - const mpContract = MessageProcessorFactory.connect(messageProcessorContractAddress, signer); - const tallyContract = TallyFactory.connect(tallyContractAddress, signer); + const mpContract = MessageProcessorFactory.connect(pollContracts.messageProcessor, signer); + const tallyContract = TallyFactory.connect(pollContracts.tally, signer); const vkRegistryContractAddress = await tallyContract.vkRegistry(); diff --git a/packages/cli/ts/commands/verify.ts b/packages/cli/ts/commands/verify.ts index 2e59667ac2..24072b427f 100644 --- a/packages/cli/ts/commands/verify.ts +++ b/packages/cli/ts/commands/verify.ts @@ -16,30 +16,12 @@ import { verifyPerVOSpentVoiceCredits, verifyTallyResults } from "../utils/verif * Verify the results of a poll on-chain * @param VerifyArgs - The arguments for the verify command */ -export const verify = async ({ - pollId, - tallyData, - maciAddress, - tallyAddress, - signer, - quiet = true, -}: VerifyArgs): Promise => { +export const verify = async ({ pollId, tallyData, maciAddress, signer, quiet = true }: VerifyArgs): Promise => { banner(quiet); const tallyResults = tallyData; const useQv = tallyResults.isQuadratic; - // we prioritize the tally file data - const tallyContractAddress = tallyResults.tallyAddress || tallyAddress; - - if (!tallyContractAddress) { - logError("Tally contract address is empty"); - } - - if (!(await contractExists(signer.provider!, tallyContractAddress))) { - logError(`Error: there is no Tally contract deployed at ${tallyContractAddress}.`); - } - // prioritize the tally file data const maciContractAddress = tallyResults.maci || maciAddress; @@ -58,7 +40,7 @@ export const verify = async ({ const pollContract = PollFactory.connect(pollContracts.poll, signer); - const tallyContract = TallyFactory.connect(tallyContractAddress, signer); + const tallyContract = TallyFactory.connect(pollContracts.tally, signer); // verification const onChainTallyCommitment = BigInt(await tallyContract.tallyCommitment()); diff --git a/packages/cli/ts/index.ts b/packages/cli/ts/index.ts index 117e70f7d4..6051a4f856 100644 --- a/packages/cli/ts/index.ts +++ b/packages/cli/ts/index.ts @@ -534,7 +534,6 @@ program .description("Get deployed poll from MACI contract") .option("-p, --poll ", "the poll id") .option("-x, --maci-address ", "the MACI contract address") - .option("-ta, --tally-address ", "the tally contract address") .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .action(async (cmdObj) => { try { @@ -542,12 +541,10 @@ program const network = await signer.provider?.getNetwork(); const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); - const tallyAddress = cmdObj.tallyAddress || (await readContractAddress(`Tally-${cmdObj.poll}`, network?.name)); await getPoll({ pollId: cmdObj.poll, maciAddress, - tallyAddress, signer, quiet: cmdObj.quiet, }); @@ -580,7 +577,6 @@ program "the tally file with results, per vote option spent credits, spent voice credits total", ) .option("-x, --maci-address ", "the MACI contract address") - .option("-ta, --tally-address ", "the tally contract address") .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .option("-r, --rpc-provider ", "the rpc provider URL") .action(async (cmdObj) => { @@ -597,16 +593,11 @@ program const tallyData = JSON.parse(await fs.promises.readFile(cmdObj.tallyFile, { encoding: "utf8" })) as TallyData; const maciAddress = tallyData.maci || cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); - const tallyAddress = - tallyData.tallyAddress || - cmdObj.tallyAddress || - (await readContractAddress(`Tally-${cmdObj.pollId}`, network?.name)); await verify({ tallyData, pollId: cmdObj.pollId, maciAddress, - tallyAddress, quiet: cmdObj.quiet, signer, }); @@ -619,7 +610,6 @@ program .description("generate the proofs for a poll") .option("-sk, --privkey ", "your serialized MACI private key") .option("-x, --maci-address ", "the MACI contract address") - .option("-ta, --tally-address ", "the tally contract address") .requiredOption("-o, --poll-id ", "the poll id", BigInt) .requiredOption( "-t, --tally-file ", @@ -667,7 +657,6 @@ program tallyDatFile: cmdObj.tallyWitnessdat, coordinatorPrivKey: cmdObj.privkey, maciAddress: cmdObj.maciAddress, - tallyAddress: cmdObj.tallyAddress, transactionHash: cmdObj.transactionHash, processWasm: cmdObj.processWasm, tallyWasm: cmdObj.tallyWasm, @@ -731,8 +720,6 @@ program .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") - .option("-p, --message-processor-address ", "the message processor contract address") - .option("-ta, --tally-contract ", "the tally contract address") .requiredOption("-f, --proof-dir ", "the proof output directory from the genProofs subcommand") .action(async (cmdObj) => { try { @@ -743,8 +730,6 @@ program tallyFile: cmdObj.tallyFile, proofDir: cmdObj.proofDir, maciAddress: cmdObj.maciAddress, - messageProcessorAddress: cmdObj.messageProcessorAddress, - tallyAddress: cmdObj.tallyContract, quiet: cmdObj.quiet, signer, }); diff --git a/packages/cli/ts/utils/interfaces.ts b/packages/cli/ts/utils/interfaces.ts index 305f76ab85..f1054c0b83 100644 --- a/packages/cli/ts/utils/interfaces.ts +++ b/packages/cli/ts/utils/interfaces.ts @@ -599,11 +599,6 @@ export interface GenProofsArgs { */ maciAddress?: string; - /** - * The address of the Tally contract - */ - tallyAddress?: string; - /** * The transaction hash of the first transaction */ @@ -714,16 +709,6 @@ export interface ProveOnChainArgs { */ maciAddress?: string; - /** - * The address of the MessageProcessor contract - */ - messageProcessorAddress?: string; - - /** - * The address of the Tally contract - */ - tallyAddress?: string; - /** * Whether to log the output */ @@ -1029,11 +1014,6 @@ export interface IGetPollArgs { */ maciAddress: string; - /** - * The address of the Tally contract - */ - tallyAddress: string; - /** * The poll id. If not specified, latest poll id will be used */ @@ -1109,11 +1089,6 @@ export interface VerifyArgs { */ maciAddress: string; - /** - * The address of the Tally contract - */ - tallyAddress: string; - /** * Whether to log the output */ diff --git a/packages/contracts/contracts/MACI.sol b/packages/contracts/contracts/MACI.sol index ccf256c911..3b453b0dc4 100644 --- a/packages/contracts/contracts/MACI.sol +++ b/packages/contracts/contracts/MACI.sol @@ -214,7 +214,14 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { vkRegistry: IVkRegistry(_vkRegistry) }); - address p = pollFactory.deploy(_duration, _treeDepths, _messageBatchSize, _coordinatorPubKey, extContracts); + address p = pollFactory.deploy( + _duration, + _treeDepths, + _messageBatchSize, + _coordinatorPubKey, + extContracts, + emptyBallotRoots[voteOptionTreeDepth - 1] + ); address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, msg.sender, _mode); address tally = tallyFactory.deploy(_verifier, _vkRegistry, p, mp, msg.sender, _mode); @@ -246,7 +253,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { } /// @inheritdoc IMACI - function getStateRootOnIndexedSignUp(uint256 _index) external view returns (uint256) { - return stateRootsOnSignUp[_index]; + function getStateRootOnIndexedSignUp(uint256 _index) external view returns (uint256 stateRoot) { + stateRoot = stateRootsOnSignUp[_index]; } } diff --git a/packages/contracts/contracts/MessageProcessor.sol b/packages/contracts/contracts/MessageProcessor.sol index f0a3aefe2d..7ae1cad041 100644 --- a/packages/contracts/contracts/MessageProcessor.sol +++ b/packages/contracts/contracts/MessageProcessor.sol @@ -26,9 +26,6 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes error CurrentMessageBatchIndexTooLarge(); error BatchEndIndexTooLarge(); - // the number of children per node in the merkle trees - uint256 internal constant TREE_ARITY = 5; - /// @inheritdoc IMessageProcessor bool public processingComplete; diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index 23d1c358f9..f3932eb3a3 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -15,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, EmptyBallotRoots, IPoll { +contract Poll is Params, Utilities, SnarkCommon, IPoll { /// @notice Whether the Poll has been initialized bool internal isInit; @@ -34,6 +34,9 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, 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; @@ -119,13 +122,14 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { /// @param _messageBatchSize The message batch size /// @param _coordinatorPubKey The coordinator's public key /// @param _extContracts The external contracts - + /// @param _emptyBallotRoot The root of the empty ballot tree constructor( uint256 _duration, TreeDepths memory _treeDepths, uint8 _messageBatchSize, PubKey memory _coordinatorPubKey, - ExtContracts memory _extContracts + ExtContracts memory _extContracts, + uint256 _emptyBallotRoot ) payable { // check that the coordinator public key is valid if (!CurveBabyJubJub.isOnCurve(_coordinatorPubKey.x, _coordinatorPubKey.y)) { @@ -148,6 +152,8 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { treeDepths = _treeDepths; // Record the current timestamp deployTime = block.timestamp; + // store the empty ballot root + emptyBallotRoot = _emptyBallotRoot; } /// @notice A modifier that causes the function to revert if the voting period is @@ -356,12 +362,13 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll { // set merged to true so it cannot be called again stateMerged = true; - mergedStateRoot = InternalLazyIMT._root(pollStateTree); + uint256 _mergedStateRoot = InternalLazyIMT._root(pollStateTree); + mergedStateRoot = _mergedStateRoot; // Set currentSbCommitment uint256[3] memory sb; sb[0] = mergedStateRoot; - sb[1] = emptyBallotRoots[treeDepths.voteOptionTreeDepth - 1]; + sb[1] = emptyBallotRoot; sb[2] = uint256(0); currentSbCommitment = hash3(sb); diff --git a/packages/contracts/contracts/PollFactory.sol b/packages/contracts/contracts/PollFactory.sol index e8fd93dc6d..903a5e211c 100644 --- a/packages/contracts/contracts/PollFactory.sol +++ b/packages/contracts/contracts/PollFactory.sol @@ -20,10 +20,18 @@ contract PollFactory is Params, DomainObjs, IPollFactory { Params.TreeDepths calldata _treeDepths, uint8 _messageBatchSize, DomainObjs.PubKey calldata _coordinatorPubKey, - Params.ExtContracts calldata _extContracts + Params.ExtContracts calldata _extContracts, + uint256 _emptyBallotRoot ) public virtual returns (address pollAddr) { // deploy the poll - Poll poll = new Poll(_duration, _treeDepths, _messageBatchSize, _coordinatorPubKey, _extContracts); + Poll poll = new Poll( + _duration, + _treeDepths, + _messageBatchSize, + _coordinatorPubKey, + _extContracts, + _emptyBallotRoot + ); // init Poll poll.init(); diff --git a/packages/contracts/contracts/interfaces/IPollFactory.sol b/packages/contracts/contracts/interfaces/IPollFactory.sol index 0051dfd7ec..8e2fce385f 100644 --- a/packages/contracts/contracts/interfaces/IPollFactory.sol +++ b/packages/contracts/contracts/interfaces/IPollFactory.sol @@ -13,12 +13,14 @@ interface IPollFactory { /// @param _messageBatchSize The size of message batch /// @param _coordinatorPubKey The coordinator's public key /// @param _extContracts The external contracts interface references + /// @param _emptyBallotRoot The root of the empty ballot tree /// @return The deployed Poll contract function deploy( uint256 _duration, Params.TreeDepths calldata _treeDepths, uint8 _messageBatchSize, DomainObjs.PubKey calldata _coordinatorPubKey, - Params.ExtContracts calldata _extContracts + Params.ExtContracts calldata _extContracts, + uint256 _emptyBallotRoot ) external returns (address); } diff --git a/packages/contracts/tasks/deploy/poll/01-poll.ts b/packages/contracts/tasks/deploy/poll/01-poll.ts index f356d301a6..905e4a6b48 100644 --- a/packages/contracts/tasks/deploy/poll/01-poll.ts +++ b/packages/contracts/tasks/deploy/poll/01-poll.ts @@ -86,6 +86,9 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => address: tallyContractAddress, }); + // get the empty ballot root + const emptyBallotRoot = await pollContract.emptyBallotRoot(); + await Promise.all([ storage.register({ id: EContracts.Poll, @@ -102,6 +105,7 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => }, unserializedKey.asContractParam(), extContracts, + emptyBallotRoot.toString(), ], network: hre.network.name, }), diff --git a/packages/contracts/tests/PollFactory.test.ts b/packages/contracts/tests/PollFactory.test.ts index a388556cea..89d4565101 100644 --- a/packages/contracts/tests/PollFactory.test.ts +++ b/packages/contracts/tests/PollFactory.test.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { BaseContract, Signer } from "ethers"; import { Keypair } from "maci-domainobjs"; -import { deployPollFactory, getDefaultSigner } from "../ts"; +import { deployPollFactory, genEmptyBallotRoots, getDefaultSigner } from "../ts"; import { MACI, PollFactory, Verifier, VkRegistry } from "../typechain-types"; import { @@ -24,6 +24,9 @@ describe("pollFactory", () => { 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 }); @@ -43,6 +46,7 @@ describe("pollFactory", () => { messageBatchSize, coordinatorPubKey.asContractParam(), extContracts, + emptyBallotRoot, ); const receipt = await tx.wait(); expect(receipt?.status).to.eq(1); diff --git a/packages/integrationTests/ts/__tests__/integration.test.ts b/packages/integrationTests/ts/__tests__/integration.test.ts index 362b141e61..c3666f40f5 100644 --- a/packages/integrationTests/ts/__tests__/integration.test.ts +++ b/packages/integrationTests/ts/__tests__/integration.test.ts @@ -15,7 +15,6 @@ import { timeTravel, verify, DeployedContracts, - PollContracts, joinPoll, } from "maci-cli"; import { getDefaultSigner } from "maci-contracts"; @@ -59,7 +58,6 @@ describe("Integration tests", function test() { // global variables we need shared between tests let maciState: MaciState; let contracts: DeployedContracts; - let pollContracts: PollContracts; let pollId: bigint; let signer: Signer; const coordinatorKeypair = new Keypair(); @@ -106,7 +104,7 @@ describe("Integration tests", function test() { contracts = await deploy({ stateTreeDepth: STATE_TREE_DEPTH, initialVoiceCredits, signer }); // 4. create a poll - pollContracts = await deployPoll({ + await deployPoll({ pollDuration: duration, intStateTreeDepth: INT_STATE_TREE_DEPTH, messageBatchSize: MESSAGE_BATCH_SIZE, @@ -323,8 +321,6 @@ describe("Integration tests", function test() { tallyFile: path.resolve(__dirname, "../../../cli/tally.json"), proofDir: path.resolve(__dirname, "../../../cli/proofs"), maciAddress: contracts.maciAddress, - messageProcessorAddress: pollContracts.messageProcessor, - tallyAddress: pollContracts.tally, signer, }), ).to.not.be.rejected; @@ -335,7 +331,6 @@ describe("Integration tests", function test() { pollId, tallyData, maciAddress: contracts.maciAddress, - tallyAddress: pollContracts.tally, signer, }), ).to.not.be.rejected; From 539bf9525b366d3bf5ef7360bae0b2a155856340 Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:27:42 -0600 Subject: [PATCH 11/14] fix: check if poll joining is withing voting deadline --- packages/circuits/circom/core/non-qv/processMessages.circom | 4 ---- packages/circuits/circom/core/qv/processMessages.circom | 4 ---- packages/contracts/contracts/Poll.sol | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/circuits/circom/core/non-qv/processMessages.circom b/packages/circuits/circom/core/non-qv/processMessages.circom index 24e754d90e..56ecb79d32 100644 --- a/packages/circuits/circom/core/non-qv/processMessages.circom +++ b/packages/circuits/circom/core/non-qv/processMessages.circom @@ -179,10 +179,6 @@ include "../../trees/incrementalQuinaryTree.circom"; var computedCommandsPackedCommandOut[batchSize][PACKED_CMD_LENGTH]; for (var i = 0; i < batchSize; i++) { - for (var j = 0; j < MSG_LENGTH; j++) { - message[j] = msgs[i][j]; - } - ( computedCommandsStateIndex[i], computedCommandsNewPubKey[i], diff --git a/packages/circuits/circom/core/qv/processMessages.circom b/packages/circuits/circom/core/qv/processMessages.circom index ba1579b8f5..a1d0219612 100644 --- a/packages/circuits/circom/core/qv/processMessages.circom +++ b/packages/circuits/circom/core/qv/processMessages.circom @@ -174,10 +174,6 @@ template ProcessMessages( var computedCommandsPackedCommandOut[batchSize][PACKED_CMD_LENGTH]; for (var i = 0; i < batchSize; i++) { - for (var j = 0; j < MSG_LENGTH; j++) { - message[j] = msgs[i][j]; - } - ( computedCommandsStateIndex[i], computedCommandsNewPubKey[i], diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index f3932eb3a3..ff77028167 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -283,7 +283,7 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { uint256 _newVoiceCreditBalance, uint256 _stateRootIndex, uint256[8] calldata _proof - ) external { + ) public virtual isWithinVotingDeadline { // Whether the user has already joined if (pollNullifier[_nullifier]) { revert UserAlreadyJoined(); From e33c5dc784ae9126a286cc002e5e5128f428bca3 Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:14:10 -0600 Subject: [PATCH 12/14] fix: tests for poll joining --- packages/cli/ts/commands/joinPoll.ts | 2 +- packages/contracts/tests/Poll.test.ts | 98 ++++++++++++++------------- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/packages/cli/ts/commands/joinPoll.ts b/packages/cli/ts/commands/joinPoll.ts index 7bb4347326..c8dc02af6a 100644 --- a/packages/cli/ts/commands/joinPoll.ts +++ b/packages/cli/ts/commands/joinPoll.ts @@ -119,7 +119,7 @@ const joiningCircuitInputs = ( const [pubKeyX, pubKeyY] = pubKey.asArray(); const stateLeafArray = [pubKeyX, pubKeyY, voiceCreditBalance, timestamp]; - if (credits <= voiceCreditBalance) { + if (credits > voiceCreditBalance) { logError("Credits must be lower than signed up credits"); } diff --git a/packages/contracts/tests/Poll.test.ts b/packages/contracts/tests/Poll.test.ts index 2859ca54cd..1bd6b5b4b0 100644 --- a/packages/contracts/tests/Poll.test.ts +++ b/packages/contracts/tests/Poll.test.ts @@ -182,6 +182,55 @@ describe("Poll", () => { }); }); + describe("Poll join", () => { + it("The users have joined the poll", async () => { + const iface = pollContract.interface; + const pubkey = keypair.pubKey.asContractParam(); + const mockProof = [0, 0, 0, 0, 0, 0, 0, 0]; + + for (let i = 0; i < NUM_USERS; i += 1) { + const mockNullifier = AbiCoder.defaultAbiCoder().encode(["uint256"], [i]); + const voiceCreditBalance = AbiCoder.defaultAbiCoder().encode(["uint256"], [i]); + + const response = await pollContract.joinPoll(mockNullifier, pubkey, voiceCreditBalance, i, mockProof); + const receipt = await response.wait(); + const logs = receipt!.logs[0]; + const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { + args: { _pollStateIndex: bigint }; + }; + const index = event.args._pollStateIndex; + + expect(receipt!.status).to.eq(1); + + const block = await signer.provider!.getBlock(receipt!.blockHash); + const { timestamp } = block!; + + const expectedIndex = maciState.polls + .get(pollId) + ?.joinPoll(BigInt(mockNullifier), keypair.pubKey, BigInt(voiceCreditBalance), BigInt(timestamp)); + + expect(index).to.eq(expectedIndex); + } + }); + + it("Poll state tree size after user's joining", async () => { + const pollStateTree = await pollContract.pollStateTree(); + const size = Number(pollStateTree.numberOfLeaves); + expect(size).to.eq(maciState.polls.get(pollId)?.pollStateLeaves.length); + }); + + it("The first user has been rejected for the second join", async () => { + const mockNullifier = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]); + const pubkey = keypair.pubKey.asContractParam(); + const voiceCreditBalance = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]); + const mockProof = [0, 0, 0, 0, 0, 0, 0, 0]; + + await expect( + pollContract.joinPoll(mockNullifier, pubkey, voiceCreditBalance, 0, mockProof), + ).to.be.revertedWithCustomError(pollContract, "UserAlreadyJoined"); + }); + }); + describe("publishMessage", () => { it("should publish a message to the Poll contract", async () => { const command = new PCommand(1n, keypair.pubKey, 0n, 9n, 1n, pollId, 0n); @@ -302,54 +351,7 @@ describe("Poll", () => { }); }); - describe("Poll join", () => { - it("The users have joined the poll", async () => { - const iface = pollContract.interface; - const pubkey = keypair.pubKey.asContractParam(); - const mockProof = [0, 0, 0, 0, 0, 0, 0, 0]; - - for (let i = 0; i < NUM_USERS; i += 1) { - const mockNullifier = AbiCoder.defaultAbiCoder().encode(["uint256"], [i]); - const voiceCreditBalance = AbiCoder.defaultAbiCoder().encode(["uint256"], [i]); - - const response = await pollContract.joinPoll(mockNullifier, pubkey, voiceCreditBalance, i, mockProof); - const receipt = await response.wait(); - const logs = receipt!.logs[0]; - const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { - args: { _pollStateIndex: bigint }; - }; - const index = event.args._pollStateIndex; - - expect(receipt!.status).to.eq(1); - - const block = await signer.provider!.getBlock(receipt!.blockHash); - const { timestamp } = block!; - - const expectedIndex = maciState.polls - .get(pollId) - ?.joinPoll(BigInt(mockNullifier), keypair.pubKey, BigInt(voiceCreditBalance), BigInt(timestamp)); - - expect(index).to.eq(expectedIndex); - } - }); - - it("Poll state tree size after user's joining", async () => { - const pollStateTree = await pollContract.pollStateTree(); - const size = Number(pollStateTree.numberOfLeaves); - expect(size).to.eq(maciState.polls.get(pollId)?.pollStateLeaves.length); - }); - - it("The first user has been rejected for the second join", async () => { - const mockNullifier = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]); - const pubkey = keypair.pubKey.asContractParam(); - const voiceCreditBalance = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]); - const mockProof = [0, 0, 0, 0, 0, 0, 0, 0]; - - await expect( - pollContract.joinPoll(mockNullifier, pubkey, voiceCreditBalance, 0, mockProof), - ).to.be.revertedWithCustomError(pollContract, "UserAlreadyJoined"); - }); - + describe("Merge state", () => { it("should allow a Poll contract to merge the state tree (calculate the state root)", async () => { await timeTravel(signer.provider as unknown as EthereumProvider, Number(duration) + 1); From 145ef3f3f926a9e205486cfcea9157b910d044fd Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:44:58 -0600 Subject: [PATCH 13/14] chore: optimize message processing circuits --- .../circom/core/non-qv/processMessages.circom | 14 +------------- .../circuits/circom/core/qv/processMessages.circom | 14 +------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/packages/circuits/circom/core/non-qv/processMessages.circom b/packages/circuits/circom/core/non-qv/processMessages.circom index 56ecb79d32..178a313302 100644 --- a/packages/circuits/circom/core/non-qv/processMessages.circom +++ b/packages/circuits/circom/core/non-qv/processMessages.circom @@ -379,11 +379,6 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { // 5. Verify that currentVoteWeight exists in the ballot's vote option root // at cmdVoteOptionIndex. - var voiceCreditAmountValid = SafeGreaterEqThan(252)([ - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeight, - cmdNewVoteWeight - ]); - var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); var computedCurrentVoteWeightPathIndices[voteOptionTreeDepth] = QuinGeneratePathIndices(voteOptionTreeDepth)(cmdVoteOptionIndexMux); @@ -396,17 +391,10 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) { computedCurrentVoteWeightQip === ballot[BALLOT_VO_ROOT_IDX]; var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsValid); - var newSlVoiceCreditBalanceMux = Mux1()( - [ - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeight - cmdNewVoteWeight - ], - voiceCreditAmountValid - ); var voiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - newSlVoiceCreditBalanceMux + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeight - cmdNewVoteWeight ], computedIsValid ); diff --git a/packages/circuits/circom/core/qv/processMessages.circom b/packages/circuits/circom/core/qv/processMessages.circom index a1d0219612..364c76b55d 100644 --- a/packages/circuits/circom/core/qv/processMessages.circom +++ b/packages/circuits/circom/core/qv/processMessages.circom @@ -382,11 +382,6 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { currentVoteWeightSquare <== currentVoteWeight * currentVoteWeight; cmdNewVoteWeightSquare <== cmdNewVoteWeight * cmdNewVoteWeight; - var voiceCreditAmountValid = SafeGreaterEqThan(252)([ - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare, - cmdNewVoteWeightSquare - ]); - var cmdVoteOptionIndexMux = Mux1()([0, cmdVoteOptionIndex], computedIsVoteOptionIndexValid); var computedCurrentVoteWeightPathIndices[voteOptionTreeDepth] = QuinGeneratePathIndices(voteOptionTreeDepth)(cmdVoteOptionIndexMux); @@ -399,17 +394,10 @@ template ProcessOne(stateTreeDepth, voteOptionTreeDepth) { computedCurrentVoteWeightQip === ballot[BALLOT_VO_ROOT_IDX]; var voteWeightMux = Mux1()([currentVoteWeight, cmdNewVoteWeight], computedIsValid); - var newSlVoiceCreditBalanceMux = Mux1()( - [ - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare - cmdNewVoteWeightSquare - ], - voiceCreditAmountValid - ); var voiceCreditBalanceMux = Mux1()( [ stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX], - newSlVoiceCreditBalanceMux + stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + currentVoteWeightSquare - cmdNewVoteWeightSquare ], computedIsValid ); From 3139d323eda920de3c068df06ec80c1a02d5500e Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Wed, 4 Dec 2024 08:55:22 -0600 Subject: [PATCH 14/14] chore: optimize poll contract --- packages/contracts/contracts/Poll.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index ff77028167..d091152a1b 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -367,7 +367,7 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { // Set currentSbCommitment uint256[3] memory sb; - sb[0] = mergedStateRoot; + sb[0] = _mergedStateRoot; sb[1] = emptyBallotRoot; sb[2] = uint256(0);