Skip to content

Commit

Permalink
test(ceremony): add circuit tests with ceremony params
Browse files Browse the repository at this point in the history
Add processMessages and tallyVotes tests in the circuits package to ensure that we validate they
work with the ceremony parameters
  • Loading branch information
ctrlc03 committed Dec 15, 2023
1 parent 1600ee4 commit dd9b026
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js setVerifyingKeys
--msg-tree-depth 8 \
--vote-option-tree-depth 3 \
--msg-batch-depth 2 \
--process-messages-zkey ./zkeys/processmessages_6-8-2-3_final.zkey \
--tally-votes-zkey ./zkeys/tallyvotes_6-2-3_final.zkey \
--process-messages-zkey ./zkeys/processMessages_6-8-2-3.zkey \
--tally-votes-zkey ./zkeys/tallyVotes_6-2-3.zkey \
-q true
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js create -s 6 -q true
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js deployPoll \
-pk macipk.c974f4f168b79727ac98bfd53a65ea0b4e45dc2552fe73df9f8b51ebb0930330 \
-t 30 -g 25 -mv 25 -i 2 -m 8 -b 2 -v 3 -q true
--duration 30 \
--max-messages 390625 \
--max-vote-options 125 \
--int-state-tree-depth 2 \
--msg-tree-depth 8 \
--msg-batch-depth 2 \
--vote-option-tree-depth 3 \
-q true
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js signup \
--pubkey macipk.3e7bb2d7f0a1b7e980f1b6f363d1e3b7a12b9ae354c2cd60a9cfa9fd12917391 \
-q true
Expand Down Expand Up @@ -47,8 +54,8 @@ HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js genProofs \
--privkey macisk.49953af3585856f539d194b46c82f4ed54ec508fb9b882940cbe68bbc57e59e \
--poll-id 0 \
--rapidsnark ~/rapidsnark/build/prover \
--process-zkey ./zkeys/processmessages_6-8-2-3_final.zkey \
--tally-zkey ./zkeys/tallyvotes_6-2-3_final.zkey \
--process-zkey ./zkeys/processMessages_6-8-2-3.zkey \
--tally-zkey ./zkeys/tallyVotes_6-2-3.zkey \
--tally-file tally.json \
--output proofs/ \
--tally-witnessgen ./zkeys/tallyVotes_6-2-3 \
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/download-ceremony-artifacts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cd "$(dirname "$0")"
cd ..
mkdir -p ../cli/zkeys

URL=https://maci-develop-fra.s3.eu-central-1.amazonaws.com/v1.2.0/maci_keys_6-8-2-3_ceremony.tar.gz
URL=https://maci-develop-fra.s3.eu-central-1.amazonaws.com/v1.2.0/maci-ceremony-artifacts-v1.2.0.tar.gz
DIR_NAME="maci_keys.tar.gz"
OUT_DIR=../cli/

Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/nightly-ceremony.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
name: Nightly Ceremony

on:
push:
pull_request:
schedule:
- cron: 0 0 * * *

jobs:
test-with-ceremony-keys:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v4
with:
ref: dev

- name: Use Node.js 20
uses: actions/setup-node@v4
Expand Down Expand Up @@ -47,8 +49,8 @@ jobs:
- name: Download ceremony artifacts
run: ./.github/scripts/download-ceremony-artifacts.sh

- name: Run tests
run: ./.github/scripts/ceremony-param-tests.sh
- name: Run e2e tests
run: ./.github/scripts/ceremony-param-tests-c-witness.sh

- name: Stop Hardhat
run: kill $(lsof -t -i:8545)
10 changes: 10 additions & 0 deletions circuits/circom/test/ceremonyParams/processMessages_test.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma circom 2.0.0;
include "../../processMessages.circom";
/*
stateTreeDepth,
msgTreeDepth,
msgSubTreeDepth
voteOptionTreeDepth,
*/

component main {public [inputHash]} = ProcessMessages(6, 8, 2, 3);
7 changes: 7 additions & 0 deletions circuits/circom/test/ceremonyParams/tallyVotes_test.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pragma circom 2.0.0;
include "../../tallyVotes.circom";

component main {public [inputHash]} = TallyVotes(6, 2, 3);
/*stateTreeDepth,*/
/*intStateTreeDepth,*/
/*voteOptionTreeDepth*/
3 changes: 2 additions & 1 deletion circuits/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"test-privToPubKey": "ts-mocha --exit ts/__tests__/PrivToPubKey.test.ts",
"test-calculateTotal": "ts-mocha --exit ts/__tests__/CalculateTotal.test.ts",
"test-processMessages": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/ProcessMessages.test.ts",
"test-tallyVotes": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/TallyVotes.test.ts"
"test-tallyVotes": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/TallyVotes.test.ts",
"test-ceremonyParams": "ts-mocha --exit ts/__tests__/CeremonyParams.test.ts"
},
"dependencies": {
"circomlib": "https://github.com/weijiekoh/circomlib#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b",
Expand Down
283 changes: 283 additions & 0 deletions circuits/ts/__tests__/CeremonyParams.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import { MaciState, Poll, packProcessMessageSmallVals, STATE_TREE_ARITY } from "maci-core";
import { PrivKey, Keypair, PCommand, Message, Ballot } from "maci-domainobjs";
import { hash5, IncrementalQuinTree, stringifyBigInts, NOTHING_UP_MY_SLEEVE, AccQueue } from "maci-crypto";
import path from "path";
import { expect } from "chai";
import tester from "circom_tester";
import { generateRandomIndex, getSignal } from "./utils/utils";

describe("Ceremony param tests", function () {
const params = {
// processMessages and Tally
stateTreeDepth: 6,
// processMessages
messageTreeDepth: 8,
// processMessages
messageBatchTreeDepth: 2,
// processMessages and Tally
voteOptionTreeDepth: 3,
// Tally
stateLeafBatchDepth: 2,
};

const maxValues = {
maxUsers: STATE_TREE_ARITY ** params.stateTreeDepth,
maxMessages: STATE_TREE_ARITY ** params.messageTreeDepth,
maxVoteOptions: STATE_TREE_ARITY ** params.voteOptionTreeDepth,
};

const treeDepths = {
intStateTreeDepth: params.messageBatchTreeDepth,
messageTreeDepth: params.messageTreeDepth,
messageTreeSubDepth: params.messageBatchTreeDepth,
voteOptionTreeDepth: params.voteOptionTreeDepth,
};

const messageBatchSize = STATE_TREE_ARITY ** params.messageBatchTreeDepth;

const voiceCreditBalance = BigInt(100);
const duration = 30;

const coordinatorKeypair = new Keypair();

describe("ProcessMessage circuit", function () {
this.timeout(900000);

let circuit: tester.WasmTester;
let hasherCircuit: tester.WasmTester;

before(async () => {
const circuitPath = path.resolve(__dirname, "../../circom/test/ceremonyParams", `processMessages_test.circom`);
circuit = await tester.wasm(circuitPath);
const hasherCircuitPath = path.resolve(__dirname, "../../circom/test/", `processMessagesInputHasher_test.circom`);
hasherCircuit = await tester.wasm(hasherCircuitPath);
});

describe("1 user, 2 messages", () => {
const maciState = new MaciState(params.stateTreeDepth);
const voteWeight = BigInt(9);
const voteOptionIndex = BigInt(0);
let stateIndex: bigint;
let pollId: number;
let poll: Poll;
const messages: Message[] = [];
const commands: PCommand[] = [];

before(async () => {
// 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))),
);

pollId = maciState.deployPoll(
duration,
// BigInt(2 + duration),
BigInt(Math.floor(Date.now() / 1000) + duration),
maxValues,
treeDepths,
messageBatchSize,
coordinatorKeypair,
);

poll = maciState.polls[pollId];

// First command (valid)
const command = new PCommand(
stateIndex, //BigInt(1),
userKeypair.pubKey,
voteOptionIndex, // voteOptionIndex,
voteWeight, // vote weight
BigInt(2), // 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);
messages.push(message);
commands.push(command);

poll.publishMessage(message, ecdhKeypair.pubKey);

// Second command (valid)
const command2 = new PCommand(
stateIndex,
userKeypair.pubKey,
voteOptionIndex, // voteOptionIndex,
BigInt(1), // vote weight
BigInt(1), // nonce
BigInt(pollId),
);
const signature2 = command2.sign(userKeypair.privKey);

const ecdhKeypair2 = new Keypair();
const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey);
const message2 = command2.encrypt(signature2, sharedKey2);
messages.push(message2);
commands.push(command2);
poll.publishMessage(message2, ecdhKeypair2.pubKey);

// Use the accumulator queue to compare the root of the message tree
const accumulatorQueue: AccQueue = new AccQueue(
params.messageTreeDepth,
STATE_TREE_ARITY,
NOTHING_UP_MY_SLEEVE,
);
accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey));
accumulatorQueue.enqueue(message2.hash(ecdhKeypair2.pubKey));
accumulatorQueue.mergeSubRoots(0);
accumulatorQueue.merge(params.messageTreeDepth);

expect(poll.messageTree.root.toString()).to.be.eq(
accumulatorQueue.mainRoots[params.messageTreeDepth].toString(),
);
});

it("should produce the correct state root and ballot root", async () => {
// The current roots
const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth);
const emptyBallotHash = emptyBallot.hash();
const ballotTree = new IncrementalQuinTree(params.stateTreeDepth, emptyBallot.hash(), STATE_TREE_ARITY, hash5);
ballotTree.insert(emptyBallot.hash());

for (let i = 0; i < poll.stateLeaves.length; i++) {
ballotTree.insert(emptyBallotHash);
}

const currentStateRoot = maciState.stateTree.root;
const currentBallotRoot = ballotTree.root;

const generatedInputs = poll.processMessages(pollId);

// Calculate the witness
const witness = await circuit.calculateWitness(generatedInputs, true);
await circuit.checkConstraints(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());

const packedVals = packProcessMessageSmallVals(BigInt(maxValues.maxVoteOptions), BigInt(poll.numSignUps), 0, 2);

// Test the ProcessMessagesInputHasher circuit
const hasherCircuitInputs = stringifyBigInts({
packedVals,
coordPubKey: generatedInputs.coordPubKey,
msgRoot: generatedInputs.msgRoot,
currentSbCommitment: generatedInputs.currentSbCommitment,
newSbCommitment: generatedInputs.newSbCommitment,
pollEndTimestamp: generatedInputs.pollEndTimestamp,
});

const hasherWitness = await hasherCircuit.calculateWitness(hasherCircuitInputs, true);
await hasherCircuit.checkConstraints(hasherWitness);
const hash = await getSignal(hasherCircuit, hasherWitness, "hash");
expect(hash.toString()).to.be.eq(generatedInputs.inputHash.toString());
});
});

describe("TallyVotes circuit", function () {
this.timeout(900000);

let circuit: tester.WasmTester;

before(async () => {
const circuitPath = path.resolve(__dirname, "../../circom/test/ceremonyParams", `tallyVotes_test.circom`);
circuit = await tester.wasm(circuitPath);
});

describe("1 user, 2 messages", () => {
let stateIndex: bigint;
let pollId: number;
let poll: Poll;
let maciState: MaciState;
const voteWeight = BigInt(9);
const voteOptionIndex = BigInt(0);

beforeEach(async () => {
maciState = new MaciState(params.stateTreeDepth);
const messages: Message[] = [];
const commands: PCommand[] = [];
// Sign up and publish
const userKeypair = new Keypair();
stateIndex = BigInt(
maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))),
);

pollId = maciState.deployPoll(
duration,
BigInt(Math.floor(Date.now() / 1000) + duration),
maxValues,
treeDepths,
messageBatchSize,
coordinatorKeypair,
);

poll = maciState.polls[pollId];

// First command (valid)
const command = new PCommand(
stateIndex,
userKeypair.pubKey,
voteOptionIndex, // voteOptionIndex,
voteWeight, // vote weight
BigInt(1), // 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);
messages.push(message);
commands.push(command);

poll.publishMessage(message, ecdhKeypair.pubKey);
// Use the accumulator queue to compare the root of the message tree
const accumulatorQueue: AccQueue = new AccQueue(
params.messageTreeDepth,
STATE_TREE_ARITY,
NOTHING_UP_MY_SLEEVE,
);
accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey));
accumulatorQueue.mergeSubRoots(0);
accumulatorQueue.merge(params.messageTreeDepth);

expect(poll.messageTree.root.toString()).to.be.eq(
accumulatorQueue.mainRoots[params.messageTreeDepth].toString(),
);
// Process messages
poll.processMessages(pollId);
});

it("should produce the correct result commitments", async () => {
const generatedInputs = poll.tallyVotes();
const witness = await circuit.calculateWitness(generatedInputs);
await circuit.checkConstraints(witness);
});

it("should produce the correct result if the inital tally is not zero", async () => {
const generatedInputs = poll.tallyVotes();

// Start the tally from non-zero value
let randIdx = generateRandomIndex(Object.keys(generatedInputs).length);
while (randIdx === 0) {
randIdx = generateRandomIndex(Object.keys(generatedInputs).length);
}

generatedInputs.currentResults[randIdx] = "1";
const witness = await circuit.calculateWitness(generatedInputs);
await circuit.checkConstraints(witness);
});
});
});
});
});
Loading

0 comments on commit dd9b026

Please sign in to comment.