diff --git a/website/versioned_docs/version-v2.0_alpha/core-concepts/key-change.md b/website/versioned_docs/version-v2.0_alpha/core-concepts/key-change.md index 593c8b17b0..3c2538284e 100644 --- a/website/versioned_docs/version-v2.0_alpha/core-concepts/key-change.md +++ b/website/versioned_docs/version-v2.0_alpha/core-concepts/key-change.md @@ -5,9 +5,9 @@ sidebar_label: Key change sidebar_position: 8 --- -MACI's voters are identified by their MACI public key. Together with their private key, they can sign and submit messages to live Polls. +MACI's voters are globally identified by their MACI public key. Together with their private key, they can join live Polls. For each live Poll, the user wants to join, the user generates a new key pair. With the help of ZK proofs, this schema prevents the coordinator from creating a link between the user's public MACI key and the Poll-specific keys. With the Poll keys, the user can sign and submit messages to live Polls. -As MACI's main property is to provide collusion resistance in digital voting applications, it is important to have a mechanism for a user to change their voting key, should this become compromised, or they wish to revoke past actions. +As MACI's main property is to provide collusion resistance in digital voting applications, it is important to have a mechanism for a user to change their Poll voting key, should this become compromised, or they wish to revoke past actions. ## How MACI messages are processed @@ -23,31 +23,38 @@ Reverse processing was introduced to prevent a type of attack where a briber wou Let's take as an example the following: -1. Alice signs up with pub key $pub1$ -2. Bob (Briber) bribes Alice and asks her to submit a key change message to $pub2$ (owned by Bob) -3. Bob submits a vote with $pub2$ -4. Alice submits a vote with $pub1$ +1. Alice signs up with pub key $pub1$ and joins $Poll$ with pub key $pollPub1$ +2. Bob (Briber) bribes Alice and asks her to submit a Poll key change message to $pollPub2$ (owned by Bob) +3. Bob submits a vote with $pollPub2$ +4. Alice submits a vote with $pollPub1$ -If messages were processed in the same order as they were submitted, Alice's vote would not be valid, due to it being signed with a private key $priv1$ - which now would not be valid. +If messages were processed in the same order as they were submitted, Alice's vote would not be valid, due to it being signed with a private key $pollPriv1$ - which now would not be valid. -On the other hand, due to messages being processed in reverse order, Alice's last message would be counted as valid as the key change would have not been processed yet. Then, Bob's vote would not be counted as valid as the current key for Alice would be $pub1$. +On the other hand, due to messages being processed in reverse order, Alice's last message would be counted as valid as the key change would have not been processed yet. Then, Bob's vote would not be counted as valid as the current key for Alice would be $pollPub1$. > Note that a key change message should have the nonce set to 1 in order for it to be valid. We'll see a code example in the next sections. ## Then how can a voter change their key and submit a new vote? -A user, can submit a key change message, by simply sending a new message signed with their signup key, and setting the nonce to 1. This is because the code checks that the first message to be processed has the nonce set to 1. +A user can submit a key change message by simply sending a new message signed with their signup key, and setting the nonce to 1. This is because the code checks that the first message to be processed has the nonce set to 1. Let's take a look into a code example: -> We have two users, and three keypairs +> We have two users, and five keypairs -- Create three keypairs +- Create five keypairs ```ts +// Two MACI keypairs const user1Keypair = new Keypair(); const user2Keypair = new Keypair(); -const secondKeyPair = new Keypair(); + +// Two Poll keypairs for each user respectively +const pollUser1Keypair = new Keypair(); +const pollUser2Keypair = new Keypair(); + +// Second Poll keypair +const secondPollKeypair = new Keypair(); ``` - Votes will be @@ -74,10 +81,11 @@ project 1 = 3 * 3 -> 9 As seen above, we expect the first vote weight 9 to not be counted, but instead the second vote weight 5 to be counted. -- Deploy a MaciState locally and sign up +- Deploy a MaciState locally, sign up, and join the poll ```ts const maciState: MaciState = new MaciState(STATE_TREE_DEPTH); +const maciState: MaciState = new MaciState(STATE_TREE_DEPTH); // 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))); @@ -92,20 +100,29 @@ pollId = maciState.deployPoll( ); ``` -- User1 and user2 submit their first votes +- User1 and user2 join the poll and submit their first votes ```ts const poll = maciState.polls[pollId]; + +// Join poll +const nullifier1 = poseidon([user1Keypair.privKey.asCircuitInputs()]); +const nullifier2 = poseidon([user2Keypair.privKey.asCircuitInputs()]); + +poll.joinPoll(nullifier1, pollUser1Keypair.pubKey, newVoiceCreditBalance, Date.now()); +poll.joinPoll(nullifier2, pollUser2Keypair.pubKey, newVoiceCreditBalance, Date.now()); + +// Prepare and cast votes const command1 = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, + BigInt(user1PollStateIndex), + pollUser1Keypair.pubKey, user1VoteOptionIndex, user1VoteWeight, BigInt(1), BigInt(pollId), ); -const signature1 = command1.sign(user1Keypair.privKey); +const signature1 = command1.sign(pollUser1Keypair.privKey); const ecdhKeypair1 = new Keypair(); const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); @@ -115,14 +132,14 @@ poll.publishMessage(message1, ecdhKeypair1.pubKey); const command2 = new PCommand( BigInt(user2StateIndex), - user2Keypair.pubKey, + pollUser2Keypair.pubKey, user2VoteOptionIndex, user2VoteWeight, BigInt(1), BigInt(pollId), ); -const signature2 = command2.sign(user2Keypair.privKey); +const signature2 = command2.sign(pollUser2Keypair.privKey); const ecdhKeypair2 = new Keypair(); const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); @@ -136,15 +153,15 @@ poll.publishMessage(message2, ecdhKeypair2.pubKey); ```ts const poll = maciState.polls[pollId]; const command = new PCommand( - BigInt(user1StateIndex), - secondKeyPair.pubKey, + BigInt(user1PollStateIndex), + secondPollKeypair.pubKey, user1VoteOptionIndex, user1NewVoteWeight, BigInt(1), BigInt(pollId), ); -const signature = command.sign(user1Keypair.privKey); +const signature = command.sign(pollUser1Keypair.privKey); const ecdhKeypair = new Keypair(); const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); @@ -167,10 +184,10 @@ expect(poll.perVOSpentVoiceCredits[1].toString()).to.eq((user2VoteWeight * user2 ```ts const poll = maciState.polls[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 pollStateLeaf1 = poll.stateLeaves[user1PollStateIndex]; +const pollStateLeaf2 = poll.stateLeaves[user2PollStateIndex]; +expect(pollStateLeaf1.pubKey.equals(secondPollKeypair.pubKey)).to.eq(true); +expect(pollStateLeaf2.pubKey.equals(pollUser2Keypair.pubKey)).to.eq(true); ``` We see that is important that we set the final message (the one with the new vote) with nonce 1, as this vote would be counted as the first vote. @@ -178,3 +195,5 @@ We see that is important that we set the final message (the one with the new vot :::info Tests related to key changes have been added to the [core package](https://github.com/privacy-scaling-explorations/maci/blob/dev/core/ts/__tests__/) and to the [cli package](https://github.com/privacy-scaling-explorations/maci/blob/dev/cli/tests/). ::: + + diff --git a/website/versioned_docs/version-v2.0_alpha/core-concepts/maci-messages.md b/website/versioned_docs/version-v2.0_alpha/core-concepts/maci-messages.md index 491838de99..5fc99442b5 100644 --- a/website/versioned_docs/version-v2.0_alpha/core-concepts/maci-messages.md +++ b/website/versioned_docs/version-v2.0_alpha/core-concepts/maci-messages.md @@ -11,12 +11,12 @@ A command represents an action that a user may take, such as casting a vote in a | Symbol | Name | Size | Description | | ------------ | ----------------------- | ---- | --------------------------------------------------------------------------------------------------- | -| $cm_i$ | State index | 50 | State leaf index where the signing key is located | -| $cm_{p_{x}}$ | Public key x-coordinate | 253 | If no change is necessary this parameter should reflect the current public key's x-coordinate | -| $cm_{p_{y}}$ | Public key y-coordinate | 253 | If no change is necessary this parameter should reflect the current public key's y-coordinate | +| $cm_i$ | Poll state index | 50 | Poll state leaf index where the signing key is located | +| $cm_{p_{x}}$ | Public key x-coordinate | 253 | If no change is necessary this parameter should reflect the current poll public key's x-coordinate | +| $cm_{p_{y}}$ | Public key y-coordinate | 253 | If no change is necessary this parameter should reflect the current poll public key's y-coordinate | | $cm_{i_{v}}$ | Vote option index | 50 | Option state leaf index of preference to assign the vote for | | $cm_w$ | Voting weight | 50 | Voice credit balance allocation, this is an arbitrary value dependent on a user's available credits | -| $cm_n$ | Nonce | 50 | State leaf's index of actions committed plus one | +| $cm_n$ | Nonce | 50 | Poll state leaf's index of actions committed plus one | | $cm_{id}$ | Poll id | 50 | The poll's identifier to cast in regard to | | $cm_s$ | Salt | 253 | An entropy value to inhibit brute force attacks | diff --git a/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/Params.md b/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/Params.md index 965ac48655..a48c3f6ea5 100644 --- a/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/Params.md +++ b/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/Params.md @@ -27,7 +27,6 @@ struct MaxValues { /// deployment struct ExtContracts { IMACI maci; - AccQueue messageAq; } ``` diff --git a/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/Poll.md b/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/Poll.md index c85d036148..9c10b0a3b5 100644 --- a/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/Poll.md +++ b/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/Poll.md @@ -9,39 +9,75 @@ This contract allows users to submit their votes. The main functions of the contract are as follows: +- `joinPoll` - This function allows users to join the poll by adding new leaf to the poll state tree. Users submit a poll-specific public key, a nullifier derived from their MACI private key, and a ZK proof that proves correctness of the nullifier computation and inclusion of the original MACI public key in MACI state tree. - `publishMessage` - This function allows anyone to publish a message, and it accepts the message object as well as an ephemeral public key. This key together with the coordinator public key will be used to generate a shared ECDH key that will encrypt the message. - Before saving the message, the function will check that the voting deadline has not passed, as well as the max number of messages was not reached. + The function will check that the voting deadline has not passed, as well as the max number of messages was not reached. If everything is correct, the message chain hash is updated as $hash(currentChainHash, newMessageHash)$. If the new message order number is greater than the batch size for message processing, the message batch chain hash is also logged. - `publisMessageBatch` - This function allows to submit a batch of messages, and it accepts an array of messages with their corresponding public keys used in the encryption step. It will call the `publishMessage` function for each message in the array. +## JoinPoll + +The `joinPoll` function looks as follows: + +```js +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); + } +``` + ## PublishMessage The `publishMessage` function looks as follows: ```js 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 >= maxValues.maxMessages) revert TooManyMessages(); - // check if the public key is on the curve if (!CurveBabyJubJub.isOnCurve(_encPubKey.x, _encPubKey.y)) { - revert InvalidPubKey(); + revert InvalidPubKey(); } // cannot realistically overflow unchecked { - numMessages++; + 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); -} + } ``` ## MergeMaciState -After a Poll's voting period ends, the coordinator's job is to store the main state root, as well as some more information on the Poll contract using `mergeMaciState`: +After a Poll's voting period ends, the coordinator's job is to store the main state root, as well as some more information on the Poll contract using `mergeMaciState` and, if needed, pad last message hash batch using `padLastBatch`: ```js function mergeMaciState() public isAfterVotingDeadline { @@ -78,6 +114,15 @@ function mergeMaciState() public isAfterVotingDeadline { } ``` +```js +function padLastBatch() external isAfterVotingDeadline isNotPadded { + if (numMessages % messageBatchSize != 0) { + batchHashes.push(chainHash); + } + isBatchHashesPadded = true; + } +``` + The function will store the state root from the MACI contract, create a commitment by hashing this merkle root, an empty ballot root stored in the `emptyBallotRoots` mapping, and a zero as salt. The commitment will be stored in the `currentSbCommitment` variable. Finally, it will store the total number of signups, and calculate the actual depth of the state tree. This information will be used when processing messages and tally to ensure proof validity. The `emptyBallotRoots` mapping is used to store the empty ballot roots for each vote option tree depth. For instance, if the vote option tree depth is 5, a build script will generate the following values (this assumes that the state tree depth is 10): @@ -90,11 +135,6 @@ emptyBallotRoots[3] = uint256(49048286193070910082046722392313772904950026265341 emptyBallotRoots[4] = uint256(18694062287284245784028624966421731916526814537891066525886866373016385890569); ``` -At the same time, the coordinator can merge the message accumulator queue and generate its merkle root. This is achieved by calling the following functions: - -- `mergeMessageAqSubRoots` - merges the Poll's messages tree subroot -- `mergeMessageAq` - merges the Poll's messages tree - :::info Please be advised that the number of signups in this case includes the zero leaf. For this reason, when accounting for the real users signed up to the Poll, you should subtract one from the value stored in the Poll contract. ::: diff --git a/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/PollFactory.md b/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/PollFactory.md index e1db5c48fa..8bcaf4a87c 100644 --- a/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/PollFactory.md +++ b/website/versioned_docs/version-v2.0_alpha/developers-references/smart-contracts/PollFactory.md @@ -9,40 +9,27 @@ sidebar_position: 3 ```ts function deploy( - _duration, - MaxValues calldata _maxValues, + uint256 _duration, + uint256 _maxVoteOptions, TreeDepths calldata _treeDepths, + uint8 _messageBatchSize, PubKey calldata _coordinatorPubKey, - address _maci -) public virtual returns (address pollAddr) { - /// @notice Validate _maxValues + ExtContracts calldata _extContracts + ) public virtual returns (address pollAddr) { + /// @notice Validate _maxVoteOptions /// maxVoteOptions must be less than 2 ** 50 due to circuit limitations; /// it will be packed as a 50-bit value along with other values as one /// of the inputs (aka packedVal) - if (_maxValues.maxVoteOptions >= (2 ** 50)) { - revert InvalidMaxValues(); + if (_maxVoteOptions >= (2 ** 50)) { + revert InvalidMaxVoteOptions(); } - /// @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, _maxValues, _treeDepths, _coordinatorPubKey, extContracts); - - // 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(); pollAddr = address(poll); - } -``` - -Upon deployment, the following will happen: - -- ownership of the `messageAq` contract is transferred to the deployed poll contract +} +``` \ No newline at end of file diff --git a/website/versioned_docs/version-v2.0_alpha/developers-references/typescript-code/cli.md b/website/versioned_docs/version-v2.0_alpha/developers-references/typescript-code/cli.md index b710d363c6..59104a6de3 100644 --- a/website/versioned_docs/version-v2.0_alpha/developers-references/typescript-code/cli.md +++ b/website/versioned_docs/version-v2.0_alpha/developers-references/typescript-code/cli.md @@ -49,12 +49,12 @@ maci-cli --help | `genMaciKeyPair` | Generate a new MACI key pair | | `show` | Show the deployed contract addresses | | `publish` | Publish a new message to a MACI Poll contract | -| `mergeMessages` | Merge the message accumulator queue | -| `mergeSignups` | Merge the signups accumulator queue | | `timeTravel` | Fast-forward the time (only works for local hardhat testing) | | `extractVkToFile` | Extract verification keys (vKey) from zKey files | -| `signup` | Sign up to a MACI contract | +| `signup` | Sign up to a MACI contract +| `joinPoll` | Join to a specific MACI Poll | | `isRegisteredUser` | Checks if user is registered with public key | +| `isJoinedUser` | Checks if user is joined to a poll with poll public key | | `fundWallet` | Fund a wallet with Ether | | `verify` | Verify the results of a poll on-chain | | `genProofs` | Generate the proofs for a poll | diff --git a/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/joinPoll.md b/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/joinPoll.md new file mode 100644 index 0000000000..d9612b5c0b --- /dev/null +++ b/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/joinPoll.md @@ -0,0 +1,68 @@ +--- +title: MACI Poll Joining Circuit +description: Introduction to the core zk-SNARK circuits of MACI +sidebar_label: Poll Joining Circuits +sidebar_position: 5 +--- + +#### Poll Joining Circuit + +[**Repo link**](https://github.com/privacy-scaling-explorations/maci/blob/dev/circuits/circom/core) + +The [`pollJoining`](https://github.com/privacy-scaling-explorations/maci/blob/dev/circuits/circom/core/qv/pollJoining.circom) circuit allows the users to prove that they are allowed to join the Poll based on their MACI key. The circuit checks: +- That the Poll joining nullifier nullifier is correctly computed as a hash of a MACI private key associated with a leaf of the MACI state tree. +- That the MACI public key, derived from the private key, is included in the MACI state tree +- The knowledge of a private key associated with the new poll public key +- That the new credit balance associated with the new key is less then or equal to the original credit balance found in the MACI state leaf. + +The nullifier is computed as a Poseidon hash of the user's MACI private key. + +#### Parameters + +| # | Parameter | Description | +| --- | ------------------------ | ---------------------------------------------------- | +| 0 | State tree depth | Allows $(2^{n})$ joins. | + +#### Inputs + + // 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; + +| Input signal | Description | +| -------------------------------- | --------------------------------------------------------------------------------------- | +| `privKey` | User's MACI private key | +| `pollPrivKey` | The new Poll private key | +| `pollPubKey` | User's MACI private key | +| `stateLeaf` | The value of the leaf associated with the user int the MACI State tree | +| `siblings` | The Merkle path siblings in the MACI State tree | +| `indices` | The Merkle path indices in the MACI State tree | +| `nullifier` | Hash of user's MACI private key | +| `credits` | User's new credit balance in the Poll state leaf | +| `stateRoot` | MACI State tree root +hash | +| `actualStateTreeDepth` | Actual MACI state tree depth (related to Lazy Merkle Tree optimization) | +| `inputHash` | he SHA256 hash of inputs supplied by the contract | + +##### `inputHash` + +All inputs to this circuit are private except for `inputHash`. To save gas during verification, the `Poll` contract hashes the following values using SHA256 and uses the hash as the sole element of $ic$: + +1. `nullifier` +2. `credits` +3. `stateRoot` +4. `pollPubKey[0]` +5. `pollPubKey[1]` diff --git a/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/processMessages.md b/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/processMessages.md index a36743da8b..00a9170974 100644 --- a/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/processMessages.md +++ b/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/processMessages.md @@ -42,8 +42,9 @@ A version working with non quadratic voting (non-qv) is also [available](https:/ | `pollEndTimestamp` | The Unix timestamp at which the poll ends | | `msgRoot` | The root of the message tree | | `msgs` | The batch of messages as an array of arrays | -| `msgSubrootPathElements` | Described below | | `coordinatorPubKeyHash` | $poseidon_2([cPk_x, cPk_y])$ | +| `inputBatchHash` | The value of $chainHash$ at the beginning of batch | +| `outputBatchHash` | The value of $chainHash$ at the end of batch | | `newSbCommitment` | Described below | | `coordPrivKey` | The coordinator's private key | | `coordPubKey` | The coordinator's public key | @@ -67,10 +68,10 @@ All inputs to this circuit are private except for `inputHash`. To save gas durin 1. `packedVals` 2. `coordinatorPubKeyHash` -3. `msgRoot` +3. `outputBatchHash` 4. `currentSbCommitment` 5. `newSbCommitment` -6. `pollEndTimestamp` +6. `actualStateTreeDepth` The hash is computed using the `sha256` Solidity function and is then subject to modulo $p$. @@ -104,28 +105,6 @@ The salt used to produce `currentSbCommitment` (see above). The salt used to produce `newSbCommitment` (see above). -##### `msgSubrootPathElements` - -The index of each message in `msgs` is consecutive. As such, in order to prove that each message in `msgs` is indeed a leaf of the message tree, we compute the subtree root of `msgs`, and then verify that the subtree root is indeed a subroot of `msgRoot`. - -A simplified example using a tree of arity 2: - -``` - r - / \ - s ... - / \ - o o - / \ / \ - a b c d -``` - -To prove that `a...d` are leaves of the tree with root `r`, we prove that the leaves have the subroot `s` with depth 2, and _then_ prove that `s` is a member of `r` at depth 1. - -The implementation for this is in the `QuinBatchLeavesExists` circuit in `https://github.com/privacy-scaling-explorations/maci/blob/dev/circuits/circom/trees/incrementalQuinTree.circom`. - -This method requires fewer circuit constraints than if we verified a Merkle proof for each leaf. - #### Statements that the circuit proves 1. That the prover knows the preimage to `inputHash` (see above) @@ -136,3 +115,4 @@ This method requires fewer circuit constraints than if we verified a Merkle proo 6. That `coordPubKey` is the preimage to the Poseidon hash of `coordPubKey` (provided by the contract) 7. That each message in `msgs` exists in the message tree 8. That after decrypting and applying each message, in reverse order, to the corresponding state and ballot leaves, the new state root, new ballot root, and `newSbSalt` are the preimage to `newSbCommitment` +9. That the starting and ending message chain hashes match input and output batch chain hashes diff --git a/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/utilities.md b/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/utilities.md index 927b96211e..06a2d3e846 100644 --- a/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/utilities.md +++ b/website/versioned_docs/version-v2.0_alpha/developers-references/zk-snark-circuits/utilities.md @@ -2,7 +2,7 @@ title: MACI Utility Circuits description: Introduction to the core zk-SNARK circuits of MACI sidebar_label: Utility Circuits -sidebar_position: 5 +sidebar_position: 6 --- #### Process Messages Input Hasher @@ -14,11 +14,11 @@ A utility circuit used by the main `processMessages` circuit to hash its inputs. It outputs one field element, which is the SHA256 hash of the following inputs: 1. `packedVals` -2. `pollEndTimestamp` -3. `msgRoot` -4. `coordinatorPubKeyHash` +2. `coordinatorPubKeyHash` +3. `outputBatchHash` +4. `currentSbCommitment` 5. `newSbCommitment` -6. `currentSbCommitment` +6. `actualStateTreeDepth` #### Tally Votes Input Hasher diff --git a/website/versioned_docs/version-v2.0_alpha/overview/workflow.md b/website/versioned_docs/version-v2.0_alpha/overview/workflow.md index dcbf5c1f77..260e178980 100644 --- a/website/versioned_docs/version-v2.0_alpha/overview/workflow.md +++ b/website/versioned_docs/version-v2.0_alpha/overview/workflow.md @@ -25,7 +25,8 @@ A "User" is any voter in a MACI poll. In order to participate in a MACI poll, a user will perform at least 2 on-chain transactions: 1. Sign up with MACI -2. Vote on a poll +2. Join a poll +3. Vote on a poll @@ -73,7 +74,7 @@ The MACI contract is responsible for registering user signups by recording the i ### Poll.sol -The Poll contract is where users submit their votes (via the [`publishMessage` function](/docs/developers-references/smart-contracts/solidity-docs/Poll#publishmessage)). One MACI contract can be used for multiple Poll contracts. In other words, a user that signed up to the MACI contract can vote on multiple issues, with each issue represented by a distinct Poll contract. +The Poll contract is where users join (via the [`publishMessage` function](/docs/developers-references/smart-contracts/solidity-docs/Poll#joinPoll)) and submit their votes (via the [`publishMessage` function](/docs/developers-references/smart-contracts/solidity-docs/Poll#publishmessage)). One MACI contract can be used for multiple Poll contracts. In other words, a user that signed up to the MACI contract can vote on multiple issues, with each issue represented by a distinct Poll contract. ### MessageProcessor.sol and Tally.sol @@ -81,7 +82,7 @@ The MessageProcessor and Tally contracts are used by the coordinator to process ## Poll lifecycle -As described above, a core contract of MACI is a Poll. Coordinators can deploy polls and add vote options to polls, which users can then vote on. Although each instance of MACI can deploy multiple Polls, only one Poll can be active at a time. +As described above, a core contract of MACI is a Poll. Coordinators can deploy polls and add vote options to polls, which users can then vote on. In essence, each MACI Poll is a state machine which has 3 stages: @@ -102,6 +103,10 @@ Before a user can cast a vote, they must sign up by generating a MACI keypair an This registration process is necessary to fortify MACI against Sybil attacks. The particular criteria used to allow user signups is customizable, and can be configured using any [SignUpGatekeeper contract](https://github.com/privacy-scaling-explorations/maci/blob/dev/contracts/contracts/gatekeepers/SignUpGatekeeper.sol). This contract dictates the criteria a user must pass in order to participate in a poll. For example, a user might need to prove ownership of a certain NFT, or that they've received some attestation on EAS, or prove that they have passed some sort of proof-of-personhood verification. Note that MACI presumes an identity system where each legitimate member controls a unique private key - MACI does not specifically solve for this, but allows for customization on how this is configured. +#### Join + +To be able to vote, user has to explicitly join the poll. The user submits a new poll public key, new credit balance and a nullifier with ZK proof that the joining is allowed for the user. The nullifier testifies that the user hasn't join the poll yet. The specifics of the verification process are explained in [pollJoining](/docs/developers-references/zk-snark-circuits/pollJoining) circuit documentation. + #### Vote Once a user has signed up with MACI, they are eligible to vote on open polls.