Skip to content

Commit

Permalink
fix: add coordinator public key hash public input
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmad authored and ctrlc03 committed Aug 5, 2024
1 parent 3c4b9d9 commit 9766bbf
Show file tree
Hide file tree
Showing 17 changed files with 94 additions and 201 deletions.
6 changes: 4 additions & 2 deletions circuits/circom/circuits.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"currentSbCommitment",
"newSbCommitment",
"pollEndTimestamp",
"actualStateTreeDepth"
"actualStateTreeDepth",
"coordinatorPublicKeyHash"
]
},
"ProcessMessagesNonQv_10-2-1-2_test": {
Expand All @@ -26,7 +27,8 @@
"currentSbCommitment",
"newSbCommitment",
"pollEndTimestamp",
"actualStateTreeDepth"
"actualStateTreeDepth",
"coordinatorPublicKeyHash"
]
},
"TallyVotes_10-1-2_test": {
Expand Down
7 changes: 4 additions & 3 deletions circuits/circom/core/non-qv/processMessages.circom
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ include "../../trees/incrementalQuinaryTree.circom";
signal input msgSubrootPathElements[msgTreeDepth - msgBatchDepth][MESSAGE_TREE_ARITY - 1];
// The coordinator's private key.
signal input coordPrivKey;
// The cooordinator's public key (derived from the contract).
signal input coordPubKey[2];
// The ECDH public key per message.
signal input encPubKeys[batchSize][2];
// The current state root (before the processing).
Expand All @@ -74,6 +72,8 @@ include "../../trees/incrementalQuinaryTree.circom";
signal input batchEndIndex;
// The batch index of current message batch
signal input index;
// The coordinator public key hash
signal input coordinatorPublicKeyHash;

// The state leaves upon which messages are applied.
// transform(currentStateLeaf[4], message5) => newStateLeaf4
Expand Down Expand Up @@ -185,7 +185,8 @@ include "../../trees/incrementalQuinaryTree.circom";
// based on the given private key - that is, the prover knows the
// coordinator's private key.
var derivedPubKey[2] = PrivToPubKey()(coordPrivKey);
derivedPubKey === coordPubKey;
var derivedPubKeyHash = PoseidonHasher(2)(derivedPubKey);
derivedPubKeyHash === coordinatorPublicKeyHash;

// Decrypt each Message into a Command.
// The command i-th is composed by the following fields.
Expand Down
7 changes: 4 additions & 3 deletions circuits/circom/core/qv/processMessages.circom
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ template ProcessMessages(
signal input msgSubrootPathElements[msgTreeDepth - msgBatchDepth][MESSAGE_TREE_ARITY - 1];
// The coordinator's private key.
signal input coordPrivKey;
// The cooordinator's public key (derived from the contract).
signal input coordPubKey[2];
// The ECDH public key per message.
signal input encPubKeys[batchSize][2];
// The current state root (before the processing).
Expand All @@ -74,6 +72,8 @@ template ProcessMessages(
signal input batchEndIndex;
// The batch index of current message batch
signal input index;
// The coordinator public key hash
signal input coordinatorPublicKeyHash;

// The state leaves upon which messages are applied.
// transform(currentStateLeaf[4], message5) => newStateLeaf4
Expand Down Expand Up @@ -180,7 +180,8 @@ template ProcessMessages(
// based on the given private key - that is, the prover knows the
// coordinator's private key.
var derivedPubKey[2] = PrivToPubKey()(coordPrivKey);
derivedPubKey === coordPubKey;
var derivedPubKeyHash = PoseidonHasher(2)(derivedPubKey);
derivedPubKeyHash === coordinatorPublicKeyHash;

// Decrypt each Message into a Command.
// The command i-th is composed by the following fields.
Expand Down
2 changes: 1 addition & 1 deletion circuits/circom/test/ProcessMessages_10-2-1-2_test.circom
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ pragma circom 2.0.0;

include ".././core/qv/processMessages.circom";

component main {public[numSignUps, index, batchEndIndex, msgRoot, currentSbCommitment, newSbCommitment, pollEndTimestamp, actualStateTreeDepth]} = ProcessMessages(10, 2, 1, 2);
component main {public[numSignUps, index, batchEndIndex, msgRoot, currentSbCommitment, newSbCommitment, pollEndTimestamp, actualStateTreeDepth, coordinatorPublicKeyHash]} = ProcessMessages(10, 2, 1, 2);
2 changes: 1 addition & 1 deletion circuits/ts/__tests__/CeremonyParams.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("Ceremony param tests", () => {
"msgs",
"msgSubrootPathElements",
"coordPrivKey",
"coordPubKey",
"coordinatorPublicKeyHash",
"encPubKeys",
"currentStateRoot",
"currentStateLeaves",
Expand Down
2 changes: 1 addition & 1 deletion circuits/ts/__tests__/ProcessMessages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe("ProcessMessage circuit", function test() {
"msgs",
"msgSubrootPathElements",
"coordPrivKey",
"coordPubKey",
"coordinatorPublicKeyHash",
"encPubKeys",
"currentStateRoot",
"currentStateLeaves",
Expand Down
2 changes: 1 addition & 1 deletion circuits/ts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface IProcessMessagesInputs {
msgs: bigint[];
msgSubrootPathElements: bigint[][];
coordPrivKey: bigint;
coordPubKey: [bigint, bigint];
coordinatorPublicKeyHash: bigint;
encPubKeys: bigint[];
currentStateRoot: bigint;
currentStateLeaves: bigint[];
Expand Down
12 changes: 4 additions & 8 deletions cli/ts/commands/proveOnChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Verifier__factory as VerifierFactory,
} from "maci-contracts/typechain-types";
import { MESSAGE_TREE_ARITY, STATE_TREE_ARITY } from "maci-core";
import { G1Point, G2Point, hashLeftRight } from "maci-crypto";
import { G1Point, G2Point } from "maci-crypto";
import { VerifyingKey } from "maci-domainobjs";

import fs from "fs";
Expand Down Expand Up @@ -214,13 +214,9 @@ export const proveOnChain = async ({
logError("currentSbCommitment mismatch.");
}

const coordPubKeyHashOnChain = BigInt(await pollContract.coordinatorPubKeyHash());
if (
hashLeftRight(
BigInt((circuitInputs.coordPubKey as BigNumberish[])[0]),
BigInt((circuitInputs.coordPubKey as BigNumberish[])[1]),
).toString() !== coordPubKeyHashOnChain.toString()
) {
const coordPubKeyHashOnChain = await pollContract.coordinatorPubKeyHash();

if (circuitInputs.coordinatorPublicKeyHash.toString() !== coordPubKeyHashOnChain.toString()) {
logError("coordPubKey mismatch.");
}

Expand Down
8 changes: 5 additions & 3 deletions contracts/contracts/MessageProcessor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes
) public view override returns (uint256[] memory publicInputs) {
(, uint8 messageTreeSubDepth, uint8 messageTreeDepth, uint8 voteOptionTreeDepth) = poll.treeDepths();
(, AccQueue messageAq) = poll.extContracts();
uint256 coordinatorPubKeyHash = poll.coordinatorPubKeyHash();
uint256 messageBatchSize = TREE_ARITY ** messageTreeSubDepth;
(uint256 numSignUps, uint256 numMessages) = poll.numSignUpsAndMessages();
(uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration();
Expand All @@ -155,15 +156,16 @@ contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMes
batchEndIndex = numMessages;
}

publicInputs = new uint256[](8);
publicInputs = new uint256[](9);
publicInputs[0] = numSignUps;
publicInputs[1] = deployTime + duration;
publicInputs[2] = messageAq.getMainRoot(messageTreeDepth);
publicInputs[3] = poll.actualStateTreeDepth();
publicInputs[4] = batchEndIndex;
publicInputs[5] = _currentMessageBatchIndex;
publicInputs[6] = (sbCommitment == 0 ? poll.currentSbCommitment() : sbCommitment);
publicInputs[7] = _newSbCommitment;
publicInputs[6] = coordinatorPubKeyHash;
publicInputs[7] = (sbCommitment == 0 ? poll.currentSbCommitment() : sbCommitment);
publicInputs[8] = _newSbCommitment;
}

/// @notice Verify the proof for processMessage
Expand Down
10 changes: 2 additions & 8 deletions contracts/tasks/helpers/Prover.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-console, no-await-in-loop */
import { G1Point, G2Point, hashLeftRight } from "maci-crypto";
import { G1Point, G2Point } from "maci-crypto";
import { VerifyingKey } from "maci-domainobjs";

import type { IVerifyingKeyStruct, Proof } from "../../ts/types";
Expand Down Expand Up @@ -156,13 +156,7 @@ export class Prover {

this.validateCommitment(circuitInputs.currentSbCommitment as BigNumberish, currentSbCommitmentOnChain);

const coordPubKeyHashOnChain = BigInt(coordinatorPubKeyHash);
const coordPubKeyHashOffChain = hashLeftRight(
BigInt((circuitInputs.coordPubKey as BigNumberish[])[0]),
BigInt((circuitInputs.coordPubKey as BigNumberish[])[1]),
).toString();

if (coordPubKeyHashOffChain !== coordPubKeyHashOnChain.toString()) {
if (circuitInputs.coordinatorPublicKeyHash.toString() !== coordinatorPubKeyHash.toString()) {
throw new Error("coordPubKey mismatch.");
}

Expand Down
7 changes: 3 additions & 4 deletions contracts/ts/genMaciState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,13 @@ export const genMaciStateFromContract = async (
const pollContractAddress = pollContractAddresses.get(pollId)!;
const pollContract = PollFactory.connect(pollContractAddress, provider);

const [coordinatorPubKeyOnChain, [deployTime, duration], onChainTreeDepths] = await Promise.all([
pollContract.coordinatorPubKey(),
const [coordinatorPubKeyHashOnChain, [deployTime, duration], onChainTreeDepths] = await Promise.all([
pollContract.coordinatorPubKeyHash(),
pollContract.getDeployTimeAndDuration().then((values) => values.map(Number)),
pollContract.treeDepths(),
]);

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 treeDepths = {
intStateTreeDepth: Number(onChainTreeDepths.intStateTreeDepth),
Expand Down
2 changes: 1 addition & 1 deletion core/ts/Poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,7 @@ 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;
};
Expand Down Expand Up @@ -747,7 +748,6 @@ export class Poll implements IPoll {
msgs,
msgSubrootPathElements: messageSubrootPath.pathElements,
coordPrivKey: this.coordinatorKeypair.privKey.asCircuitInputs(),
coordPubKey: this.coordinatorKeypair.pubKey.asCircuitInputs(),
encPubKeys: encPubKeys.map((x) => x.asCircuitInputs()),
currentStateRoot,
currentBallotRoot,
Expand Down
2 changes: 1 addition & 1 deletion core/ts/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ export interface IProcessMessagesCircuitInputs {
index: string;
maxVoteOptions: string;
msgRoot: string;
coordinatorPublicKeyHash: string;
msgs: string[];
msgSubrootPathElements: string[][];
coordPrivKey: string;
coordPubKey: string;
encPubKeys: string[];
currentStateRoot: string;
currentBallotRoot: string;
Expand Down
93 changes: 16 additions & 77 deletions website/versioned_docs/version-v2.0_alpha/core-concepts/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -663,16 +663,16 @@ The state tree, message tree, and vote option trees all have an arity of 5. As s

| Input signal | Description |
| -------------------------------- | --------------------------------------------------------------------------------------- |
| `inputHash` | The SHA256 hash of inputs supplied by the contract |
| `packedVals` | As described below |
| `numSignUps` | Number of users that have completed the sign up |
| `index` | The batch index of current message batch |
| `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` | As described below |
| `coordinatorPubKeyHash` | $\mathsf{poseidon_2}([cPk_x, cPk_y])$ |
| `coordinatorPublicKeyHash` | $\mathsf{poseidon_2}([cPk_x, cPk_y])$ |
| `newSbCommitment` | As described below |
| `coordPrivKey` | The coordinator's private key |
| `coordPubKey` | The coordinator's public key |
| `batchEndIndex` | The last batch index |
| `encPubKeys` | The public keys used to generate shared ECDH encryption keys to encrypt the messages |
| `currentStateRoot` | The state root before the commands are applied |
| `currentStateLeaves` | The state leaves upon which messages are applied |
Expand All @@ -687,35 +687,6 @@ The state tree, message tree, and vote option trees all have an arity of 5. As s
| `currentVoteWeights` | The existing vote weight for the vote option in the ballot which each command refers to |
| `currentVoteWeightsPathElements` | The Merkle path from each vote weight to the vote option root in its ballot |

##### `inputHash`

All inputs to this circuit are private except for `inputHash`. To save gas during verification, the `PollProcessorAndTallyer` contract hashes the following values using SHA256 and uses the hash as the sole element of $ic$:

1. `packedVals`
2. `coordinatorPubKeyHash`
3. `msgRoot`
4. `currentSbCommitment`
5. `newSbCommitment`
6. `pollEndTimestamp`

The hash is computed using the `sha256` Solidity function and is then subject to modulo $p$.

##### `packedVals`

`packedVals` is the following values represented as one field element. Consider that a field element is roughly 253 bits. The big-endian bit-representation is as such:

| Bits | Value |
| ----------- | -------------------------- |
| 1st 53 bits | `0` |
| 2nd 50 bits | `batchEndIndex` |
| 3rd 50 bits | `currentMessageBatchIndex` |
| 4th 50 bits | `numSignUps` |
| 5th 50 bits | `maxVoteOptions` |

For instance, if `maxVoteOptions` is 25 and `batchEndIndex` is `5`, and all other values are 0, the following is the `packedVals` representation in hexadecimal:

`140000000000000000000000000000000000019`

##### `currentSbCommitment` and `newSbCommitment`

The `currentSbCommitment` is the $\mathsf{poseidon_3}$ hash of the state tree root, the ballot tree root, and a random salt. The purpose of the random salt, which should be unique to each batch, is to ensure that the value of `currentSbCommitment` always changes even if all the commands in a batch are invalid and therefore do not change the state tree or ballot tree root.
Expand Down Expand Up @@ -754,14 +725,12 @@ This method requires fewer circuit constraints than if we verified a Merkle proo

#### Statements that the circuit proves

1. That the prover knows the preimage to `inputHash` (see above)
2. That the prover knows the preimage to `currentSbCommitment` (that is, the state root, ballot root, and `currentSbSalt`)
3. That `maxVoteOptions <= (5 ^ voteOptionTreeDepth)`
4. That `numSignUps <== (5 ^ stateTreeDepth)`
5. That `coordPubKey` is correctly derived from `coordPrivKey`
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`
1. That the prover knows the preimage to `currentSbCommitment` (that is, the state root, ballot root, and `currentSbSalt`)
2. That `maxVoteOptions <= (5 ^ voteOptionTreeDepth)`
3. That `numSignUps <== (5 ^ stateTreeDepth)`
4. That `coordinatorPublicKeyHash` is a hash of public key that is correctly derived from `coordPrivKey`
5. That each message in `msgs` exists in the message tree
6. 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`

#### How messages are decrypted and applied

Expand Down Expand Up @@ -858,8 +827,8 @@ The coordinator uses the ballot tallying circuit (`tallyVotes.circom`) to genera

| Input signal | Description |
| --------------------------------------- | ---------------------------------------------------------------- |
| `inputHash` | The SHA256 hash of inputs supplied by the contract |
| `packedVals` | As described below |
| `numSignUps` | The number of users that signup |
| `index` | Start index of given batch |
| `sbCommitment` | As described below |
| `currentTallyCommitment` | As described below |
| `newTallyCommitment` | As described below |
Expand All @@ -879,35 +848,6 @@ The coordinator uses the ballot tallying circuit (`tallyVotes.circom`) to genera
| `newPerVOSpentVoiceCreditsRootSalt` | A random value |
| `newSpentVoiceCreditSubtotalSalt` | A random value |

##### `inputHash`

All inputs to this circuit are private except for `inputHash`. To save gas during verification, the `PollProcessorAndTallyer` contract hashes the following values using SHA256 and uses the hash as the sole element of $ic$:

1. `packedVals`
2. `sbCommitment`
3. `currentTallyCommitment`
4. `newTallyCommitment`

The hash is computed using the `sha256` Solidity function and is then subject to modulo $p$.

##### `packedVals`

`packedVals` is the following values represented as one field element. Consider that a field element is roughly 253 bits. The big-endian bit-representation is as such:

| Bits | Value |
| ----------- | ----------------- |
| 1st 53 bits | `0` |
| 2nd 50 bits | `0` |
| 3rd 50 bits | `0` |
| 4th 50 bits | `numSignUps` |
| 5th 50 bits | `batchStartIndex` |

`numSignUps`, a value provided by the contract, is the number of users who have signed up. This is one less than the number of leaves inserted in the state tree (since the 0th state leaf is a blank state leaf [2.8.1]). `batchStartIndex` is the ballot tree index at which the batch begins.

For instance, if `numSignUps` is 25 and the batch index is `5`, and all other values are 0, the following is the `packedVals` representation in hexadecimal:

`64000000000005`

##### `sbCommitment`

The commitment to `stateRoot`, `ballotRoot`, and `sbSalt`:
Expand All @@ -934,11 +874,10 @@ $\mathsf{poseidon_3}([tc_r, tc_t, tc_p])$
#### Statements that the circuit proves

1. That the coordinator knows the preimage of `sbCommitment` (see above)
2. That the coordinator knows the preimage of `inputHash` (see above)
3. That `batchStartIndex` is less than or equal to `numSignUps`
4. That each ballot in `ballots` is in a member of the ballot tree with the Merkle root `ballotRoot` at indices `batchStartIndex` to `batchStartIndex + (5 ** intStateTreeDepth)`
5. That each set of votes (`votes[i]`) has the Merkle root $blt_r$ whose value equals `ballots[i][1]`
6. That the tally is valid, which is:
2. That `index` is less than or equal to `numSignUps`
3. That each ballot in `ballots` is in a member of the ballot tree with the Merkle root `ballotRoot` at indices `batchStartIndex` to `batchStartIndex + (5 ** intStateTreeDepth)`
4. That each set of votes (`votes[i]`) has the Merkle root $blt_r$ whose value equals `ballots[i][1]`
5. That the tally is valid, which is:
- That the sum of votes per vote option is correct
- That the sum of voice credits per vote option is correct
- That the subtotal of the spent voice credits is correct
Loading

0 comments on commit 9766bbf

Please sign in to comment.