From 46181a9f1553f903f1c6e275b4832f6813841b8d Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:44:34 +0000 Subject: [PATCH] feat(revamp-docs) - sort out versioning and default /docs to v1 docs --- website/sidebars.ts | 39 --- .../quadratic_vote_tallying_circuit.md | 141 ++++++++++ .../state_root_transition_circuit.md | 241 ++++++++++++++++++ website/versioned_docs/version-v1.x/cli.md | 4 +- .../version-v1.x/installation.md | 1 - .../versioned_docs/version-v1.x/testing.md | 1 - 6 files changed, 384 insertions(+), 43 deletions(-) delete mode 100644 website/sidebars.ts create mode 100644 website/versioned_docs/version-v0.x/quadratic_vote_tallying_circuit.md create mode 100644 website/versioned_docs/version-v0.x/state_root_transition_circuit.md diff --git a/website/sidebars.ts b/website/sidebars.ts deleted file mode 100644 index 7d3c43dcd5..0000000000 --- a/website/sidebars.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; - -const sidebars: SidebarsConfig = { - docSidebar: [ - { - type: 'category', - label: 'v1.x', - items: [ - 'v1.x/introduction', - 'v1.x/installation', - 'v1.x/primitives', - 'v1.x/cli', - 'v1.x/contracts', - 'v1.x/circuits', - 'v1.x/trusted-setup', - 'v1.x/testing', - 'v1.x/integrating', - 'v1.x/audit', - 'v1.x/ci-pipeline', - 'v1.x/release', - 'v1.x/troubleshooting' - ] - }, - { - type: 'category', - label: 'v0.x', - items: [ - 'v0.x/introduction', - 'v0.x/contract', - 'v0.x/circuits', - 'v0.x/state_root_transition_circuit', - 'v0.x/quadratic_vote_tallying_circuit', - 'v0.x/faq' - ] - } - ], -} - -export default sidebars; diff --git a/website/versioned_docs/version-v0.x/quadratic_vote_tallying_circuit.md b/website/versioned_docs/version-v0.x/quadratic_vote_tallying_circuit.md new file mode 100644 index 0000000000..c6ab713def --- /dev/null +++ b/website/versioned_docs/version-v0.x/quadratic_vote_tallying_circuit.md @@ -0,0 +1,141 @@ +# The quadratic vote tallying circuit + + + + +- [Inputs](#inputs) +- [Circuit pseudocode](#circuit-pseudocode) +- [Circuit failure modes](#circuit-failure-modes) + + + +Quadratic voting is one of many types of vote tallying mechanisms. We chose it for the first version of MACI due to the high amount of interest that the community has shown for it. + +Quadratic voting allows users to express the strength of their preferences when they vote for options. Since users are allocated a limited number of _voice credits_, and the number of tallied votes per option is the square root of the number of voice credits spent on said option, quadratic voting [over-privileges neither concentrated nor diffuse interests](https://www.vitalik.ca/general/2019/12/07/quadratic.html). + +For instance, if a user has 99 voice credits, they may spend them this way (each row represents a command): + +| Option | Voice credits spent | +| ------ | ------------------- | +| A | 1 | +| A | 9 | +| B | 25 | +| C | 64 | + +The outcome is as such: + +| Option | Tallied votes | +| ------ | ------------- | +| A | 3.16 | +| B | 5 | +| C | 8 | + +Even though the user has a disproportionate preference for option C (64 voice credits), their impact on the tallied vote (8 votes) is merely the square root of the voice credits they have spent. This prevents them from having an outsized influence on the results simply by virtue of their willingness to spend as many voice credits on that option as they had. + +Additionally, we consider that votes are cumulative. This means that the user spent 10 voice credits on option A. + +The MACI contract's `quadraticVoteTally()` function should verify a proof created using this circuit to compute the results of tallying a set of state leaves. This also proves that these state leaves have an intermediate root `A`, as well that `A` is part of the tree with final state root `R`. This allows the coordinator to prove the final tally in batches. The function keeps track of the index of each intermediate root to ensure that they are processed consecutively. + +## Inputs + +| Pseudocode name | zk-SNARK input type | Description | Set by | +| ----------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------- | ----------- | +| `fullStateRoot` | Public | The final Merkle root of the state tree | Contract | +| `fullStateTreeDepth` | Hardcoded | The depth of the state tree | Contract | +| `intermediateStateTreeDepth` | Hardcoded | The depth of the intermediate state tree | Contract | +| `intermediateStateRoot` | Public | The intermediate Merkle root generated by the given state leaves | Contract | +| `intermediatePathElements[k]` | Private | The Merkle path elements from `intermediateStateRoot` to `stateRoot`. | Coordinator | +| `intermediatePathIndex` | Public | The Merkle path index from `intermediateStateRoot` to `stateRoot`. | Contract | +| `currentResults[n]` | Private | The vote tally of all prior batches of state leaves | Coordinator | +| `currentResultsSalt` | Private | A random value to hash with the vote tally for state leaves up to the current batch | Coordinator | +| `currentResultsCommitment` | Public | The salted commitment of the values in `currentResults` | Contract | +| `newResultsCommitment` | Public | The salted commitment of the vote tally for this batch of leaves plus the vote tally from `currentResults` | Contract | +| `salt` | Private | A random value to hash with the culmulate vote tally for this batch of state leaves | Coordinator | +| `stateLeaves[m][p]` | Private | The batch of leaves of the state tree to tally. | Coordinator | +| `voteLeaves[m][n]` | Private | The vote leaves for each user in this batch of state leaves. | Coordinator | + +`n` is the number of options in `voteOptionTree`. +`m` is the number of state leaves in this batch. +`k` is `fullStateTreeDepth - intermediateStateTreeDepth` +`p` is the message length + +A result commitment is the hash of a Merkle root of all the vote leaves, and a salt. For instance: + +```javascript +root = genTree(results); +hash(root, salt); +``` + +## Circuit pseudocode + +```javascript +// Alice votes for party A with 16 credits +// Bob votes for party A with 9 credits + +// Party A gets 7 tallied votes. NOT 5 votes. + +// Ensure via a constraint that the intermediate root is the +// correct Merkle root of the stateLeaves passed into this +// snark +assert(intermediateStateRoot == genTree(stateLeaves)) + +// Ensure via a constraint that the intermediate root is part of the full state tree +var x = generateMerkleRoot( + intermediatePathElements, + intermediatePathIndex, + intermediateRoot +) + +assert(x == stateRoot) + +// This variable stores the sum of the square roots of each +// user's voice credits per option. +var computedResults = currentResults + +var start = 1 +if intermediatePathIndex > 0: + start = 0 + +// For each user +for i as start to m: // we ignore leaf 0 on purpose + + // Ensure via a constraint that the voteLeaves for this + // user is correct (such that when each vote leaf is + // inserted into an MT, the Merkle root matches + // the `voteOptionTreeRoot` field of the state leaf) + + var computedVoteOptionTreeRoot = genTree(voteLeaves[i]) + assert(computedVoteOptionTreeRoot == stateLeaves[i].voteOptionTreeRoot) + + // Calculate the sum of votes for each option + for j as 0 to n. + // This adds to the subtotal from previous batches + // of state leaves + computedResults[j] += voteLeaves[i][j] + + +// Ensure via a constraint that the commitment to the current results is +// correct + +assert( + hash(genTree(currentResults), currentResultsSalt) == + currentResultsCommitment +) + +// Ensure via a constraint that the final result +// is correct +assert( + hash(genTree(computedResults), salt) == + newResultsCommitment +) +``` + +where `genTree` is pseudocode for a circuit which computes a Merkle root from a list of leaves. + +## Circuit failure modes + +| Condition | Outcome | +| -------------------------------------------------------------------------------------------------- | ------------------------------ | +| Invalid state leaves and/or intermediate state root | No such proof can be generated | +| Invalid vote option leaves | No such proof can be generated | +| Invalid Merkle path to the full state root from the intermediate state root for the batch of votes | No such proof can be generated | diff --git a/website/versioned_docs/version-v0.x/state_root_transition_circuit.md b/website/versioned_docs/version-v0.x/state_root_transition_circuit.md new file mode 100644 index 0000000000..f7fa2fcd57 --- /dev/null +++ b/website/versioned_docs/version-v0.x/state_root_transition_circuit.md @@ -0,0 +1,241 @@ +# The state root transition proof circuit + +This circuit proves the correctness of each state root transition. + + + + +- [Public Inputs](#public-inputs) +- [Private Inputs](#private-inputs) +- [Check 1: That the message has been encrypted with the correct key](#check-1-that-the-message-has-been-encrypted-with-the-correct-key) +- [Check 2: that the message is part of the message tree](#check-2-that-the-message-is-part-of-the-message-tree) +- [Check 3: that the new state root transition is the correct result of executing the given command — _or_ — that the command is invalid and the no-op flag is set to true.](#check-3-that-the-new-state-root-transition-is-the-correct-result-of-executing-the-given-command--or--that-the-command-is-invalid-and-the-no-op-flag-is-set-to-true) +- [Circuit logic](#circuit-logic) +- [Circuit failure modes](#circuit-failure-modes) + + + +## Public Inputs + +All public inputs are set by the contract. + +| Pseudocode name | Description | +| -------------------------- | --------------------------------------------------------------------------------------- | +| `coordinatorPubKey` | The coordinator's public key | +| `currentStateRoot` | The current state root | +| `msgTreeRoot` | The Merkle root of the message tree | +| `msgTreePathIndex` | The Merkle path index of the message in the message tree | +| `maxStateLeafIndex` | The maximum leaf index of the state tree | +| `userCurrentLeafPathIndex` | The Merkle path index from the user's latest valid state leaf to the current state root | +| `newStateRoot` | The new state root | + +## Private Inputs + +All private inputs are set by the coordinator. + +| Pseudocode name | Description | +| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `userCurrentLeafPathElements` | The Merkle path elements from the user's latest valid state leaf to the current state root | +| `currentVoteOptionPathElements[n]` | The Merkle path needed to prove the existence of the current vote option leaf. Size is `253` \* `vote_option_tree_depth` bits | +| `newVoteOptionPathElements[n]` | The Merkle path needed to update the vote option tree root in the state leaf. Size is `253` \* `vote_option_tree_depth` bits | +| `currentVoteWeight` | In the quadratic voting use case, this is the square root of the number of voice credits a user wishes to spend on this vote. Size is 32 bits. | +| `message` | The message | +| `msgTreePathElements` | The Merkle path elements to the message tree root from the message leaf | +| `randomLeaf` | Random data | +| `newStateTreePathIndex` | The Merkle path index to the new state root from the new state leaf | +| `newStateTreePathElements` | The Merkle path elements to the new state root from the new state leaf | +| `newStateTreePathElementsToZero` | The Merkle path elements to the new state root from leaf 0, **after** the new state leaf has been updated | +| `userCurrentLeaf` | The user's latest valid state leaf | +| `command` | The command to process. Includes all the details in the leaf. | +| `noOp` | The no-op flag | +| `userPubKey` | The public key associated with the private key used to sign the command | +| `encPubKey` | The ephermeral public key used to generate the ECDH shared key which was used to encrypt the command. | +| `coordinatorPrivKey` | The coordinator's private key. | + +For the sake of simplicity, in this specification, we assume that there is no batching of commands and we handle each command one at a time. + +## Check 1: That the message has been encrypted with the correct key + +```javascript +// Derive the coordinator's public key from +// their private key +var derivedCoordinatorPubKey = eddsaDerivePubKey(coordinatorPrivKey); + +// Ensure via a constraint that it matches the +// coordinator's public key given as an input +assert(derivedCoordinatorPubKey == coordinatorPubKey); + +// Generate the ECDH key +var ecdhSharedKey = genEcdhKey(coordinatorPrivKey, encPubKey); + +// Use the ECDH shared key to decrypt the message +var decryptedCommand = decrypt(ecdhSharedKey, message); + +// Ensure via a constraint that the message has been correctly decrypted +assert(decryptedCommand == command); +``` + +## Check 2: that the message is part of the message tree + +```javascript +var generatedMsgTreeRoot = generateMerkleRoot( + msgTreePathElements, + msgTreePathIndex, + message, +); + +assert(generatedMsgTreeRoot, msgTreeRoot); +``` + +## Check 3: that the new state root transition is the correct result of executing the given command — _or_ — that the command is invalid and the no-op flag is set to true. + +## Circuit logic + +The message should already have been decrypted to `decryptedCommand` (see above). + +```javascript +/*********************************** +This function generates a state leaf +***********************************/ +function generateStateLeaf( + command, + computedNewVoteOptionRoot, + newVoiceCreditBalance +) => { + + return [ + command.newPublicKeyX, + command.newPublicKeyY, + computedNewVoteOptionRoot, + newVoiceCreditBalance, + command.nonce + 1 + ] +} + +/************************* +// The main circuit logic: +**************************/ + +// Record in a variable that the new state leaf index is +// valid (i.e. it is leq to the maximum allowed value) +var validStateLeafIndex = newStateTreePathIndex <= maxStateLeafIndex && newStateTreePathIndex > 0 + +// Record in a variable if the signature is valid +var validSignature = verifyEddsa(signature, decryptedCommand, userPubKey) + +// Record in a variable if the nonce is correct +var correctNonce = decryptedCommand.nonce == userCurrentLeaf.nonce + 1 + +// Prove that the user's current leaf is part of the +// Merkle tree. Note that this check is independent of +// the noOp flag. As such, the coordinator cannot +// create an invalid proof by tampering with the +// Merkle proof and setting noOp to true; the Merkle proof +// *must* be valid. +var x = generateMerkleRoot( + userCurrentLeafPathElements, + userCurrentLeafPathIndex, + userCurrentLeaf +) + +assert(x == currentStateRoot) + +// Prove that the the current vote option weight (leaf) +// input is correct by checking that it exists in the +// tree at the given index +var y = generateMerkleRoot( + decryptedCommand.currentVoteOptionPathElements, + decryptedCommand.voteOptionIndex, + decryptedCommand.currentVoteWeight +) + +assert(y == userCurrentLeaf.voteOptionTreeRoot) + +// Record in a variable if the user has enough +// voice credits + +var newVoiceCreditBalance = + userCurrentLeaf.voiceCreditBalance + + (decryptedCommand.currentVoteWeight ^ 2) - + (decryptedCommand.newVoteWeight ^ 2) + +var enoughVoiceCredits = newVoiceCreditBalance >= 0 + +// Record in a variable if the new leaf's vote option +// tree root is the correct result of updating the +// vote option leaf. +var computedNewVoteOptionRoot = + updateMerkleTree( + command.voteOptionIndex, + command.voteOptionPath, + command.voteWeight, + decryptedCommand.newVoteWeight + ) + +assert(newStateLeaf.voteOptionRoot == computedNewVoteOptionRoot) + newStateLeaf.voteOptionRoot == computedNewVoteOptionRoot + +// Record in a variable if the vote option index is +// within a permissible range (0 to 2 ** vote option tree +// depth, inclusive) where VOTE_OPTION_TREE_DEPTH is +// not an input, but rather hardcoded during the trusted +// setup. +var validVoteOptionTreeIndex = command.voteOptionIndex < VOTE_OPTION_TREE_DEPTH + +var newStateLeaf = generateStateLeaf( + command, + computedNewVoteOptionRoot, + newVoiceCreditBalance +) + +if (enoughVoiceCredits && + correctNonce && + validSignature && + validStateLeafIndex && + validVoteOptionTreeIndex +): + // Use a constraint to ensure that the no-op flag + // is set to false + assert(noOp == false) + + + // Generate the new state root. + var s = merkleTreeUpdate( + newStateTreePathIndex + newStateLeaf, + currentStateRoot, + newStateTreePathElements + ) + + // Update the leaf at index `0` to generate a new state + // root, and ensure via a constraint that it is equal + // to the new state root passed to the snark as an + // input. + + var updatedStateRoot = merkleTreeUpdate( + 0, + randomLeaf, + s, + newStateTreePathElementsToZero + ) + + assert(updatedStateRoot == newStateRoot) + +else: + // Use a constraint to ensure that the no-op flag + // is set to true + assert(noOp == true) +``` + +## Circuit failure modes + +| Condition | noOp flag | Outcome | +| --------------------------------------------------- | --------- | --------------------------------------- | +| Insufficient voice credits | `true` | Valid proof, but only leaf 0 is updated | +| Invalid nonce | `true` | Valid proof, but only leaf 0 is updated | +| Invalid signature | `true` | Valid proof, but only leaf 0 is updated | +| Invalid new vote option root | `true` | Valid proof, but only leaf 0 is updated | +| Invalid state leaf index | `true` | Valid proof, but only leaf 0 is updated | +| Invalid vote option tree index | `true` | Valid proof, but only leaf 0 is updated | +| Invalid Merkle path to the current state root | N/A | No such proof can be generated | +| Invalid Merkle path to the current vote option root | N/A | No such proof can be generated | diff --git a/website/versioned_docs/version-v1.x/cli.md b/website/versioned_docs/version-v1.x/cli.md index 885b44f018..389f6f99e2 100644 --- a/website/versioned_docs/version-v1.x/cli.md +++ b/website/versioned_docs/version-v1.x/cli.md @@ -183,7 +183,7 @@ Example output: [i] Ephemeral private key: macisk.2631d585e46f059e4909ab35172451542ed7723a1ace120fcf49d68e27f935b0 ``` -### (Testing only) Coordinator: Time travel +### (Testing only) Coordinator: Time travel Example usage: @@ -671,4 +671,4 @@ Output: ```bash [i] on-chain tally commitment: 1ed004ac21a5397a512cbe749e7110934a434837f4818265043fd2e2e9cbec91 [✓] The on-chain tally commitment matches. -``` +``` \ No newline at end of file diff --git a/website/versioned_docs/version-v1.x/installation.md b/website/versioned_docs/version-v1.x/installation.md index a9ddd180c5..4f6af083ad 100644 --- a/website/versioned_docs/version-v1.x/installation.md +++ b/website/versioned_docs/version-v1.x/installation.md @@ -61,7 +61,6 @@ npm run build ``` - Install dependencies for and `zkey-manager`: ```bash diff --git a/website/versioned_docs/version-v1.x/testing.md b/website/versioned_docs/version-v1.x/testing.md index 05fa96cda4..af55a63284 100644 --- a/website/versioned_docs/version-v1.x/testing.md +++ b/website/versioned_docs/version-v1.x/testing.md @@ -289,7 +289,6 @@ The followingcompiled circuits and zkeys are available to download: ## contents of `*.tar.gz` It contains compiled result of the circuit: - ``` zkeys/ zkeys/ProcessMessages_7-9-3-4_test.sym