Skip to content

Commit

Permalink
test(circuits): refactor and add tests for the circom circuits
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 committed Jan 11, 2024
1 parent 00f5e38 commit 95e084a
Show file tree
Hide file tree
Showing 26 changed files with 485 additions and 195 deletions.
15 changes: 4 additions & 11 deletions circuits/circom/tallyVotes.circom
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ include "./hasherSha256.circom";
include "./hasherPoseidon.circom";
include "./unpackElement.circom";

/*
* Tally votes in the ballots, batch by batch.
*/
// Tally votes in the ballots, batch by batch.
template TallyVotes(
stateTreeDepth,
intStateTreeDepth,
Expand Down Expand Up @@ -192,10 +190,8 @@ template TallyVotes(
}
}

/*
* Verifies the commitment to the current results. Also computes and outputs a
* commitment to the new results.
*/
// Verifies the commitment to the current results. Also computes and outputs a
// commitment to the new results.
template ResultCommitmentVerifier(voteOptionTreeDepth) {
var TREE_ARITY = 5;
var numVoteOptions = TREE_ARITY ** voteOptionTreeDepth;
Expand Down Expand Up @@ -257,7 +253,7 @@ template ResultCommitmentVerifier(voteOptionTreeDepth) {
currentTallyCommitmentHasher.in[1] <== currentSpentVoiceCreditsCommitment.hash;
currentTallyCommitmentHasher.in[2] <== currentPerVOSpentVoiceCreditsCommitment.hash;

/*currentTallyCommitmentHasher.hash === currentTallyCommitment;*/
// currentTallyCommitmentHasher.hash === currentTallyCommitment
// Check if the current tally commitment is correct only if this is not the first batch
component iz = IsZero();
iz.in <== isFirstBatch;
Expand Down Expand Up @@ -305,9 +301,6 @@ template ResultCommitmentVerifier(voteOptionTreeDepth) {
newTallyCommitmentHasher.in[1] <== newSpentVoiceCreditsCommitment.hash;
newTallyCommitmentHasher.in[2] <== newPerVOSpentVoiceCreditsCommitment.hash;

/*log(newResultsCommitment.hash);*/
/*log(newSpentVoiceCreditsCommitment.hash);*/
/*log(newPerVOSpentVoiceCreditsCommitment.hash);*/
newTallyCommitmentHasher.hash === newTallyCommitment;
}

Expand Down
36 changes: 2 additions & 34 deletions circuits/circom/trees/checkRoot.circom
Original file line number Diff line number Diff line change
@@ -1,40 +1,8 @@
pragma circom 2.0.0;
include "../hasherPoseidon.circom";

/*template QuinCheckRootWithSha256(levels, subLevels) {*/
/*// Given a quin Merkle root and a list of leaves, check if the root is the*/
/*// correct result of inserting all the leaves into the tree in the given*/
/*// order. Uses SHA256 instead of Poseidon to hash levels up to subLevels*/

/*assert(levels > 0);*/
/*assert(subLevels <= levels);*/
/*var LEAVES_PER_NODE = 5;*/

/*// The total number of leaves*/
/*var totalLeaves = LEAVES_PER_NODE ** levels;*/

/*signal input leaves[totalLeaves];*/
/*signal output root;*/

/*// The total number of hashers*/
/*var numHashers = 0;*/
/*for (var i = 0; i < levels; i ++) {*/
/*numHashers += LEAVES_PER_NODE ** i;*/
/*}*/

/*// The number of SHA256 hashers*/
/*var numShaHashers = 0;*/
/*for (var i = 0; i < subLevels; i ++) {*/
/*numShaHashers += LEAVES_PER_NODE ** i;*/
/*}*/

/*var numPoseidonHashers = numHashers - numShaHashers;*/


/*}*/

// Given a quin Merkle root and a list of leaves, check if the root is the
// correct result of inserting all the leaves into the tree in the given
// Given a list of leaves, compute the root of the merkle tree
// by inserting all the leaves into the tree in the given
// order.
template QuinCheckRoot(levels) {
var LEAVES_PER_NODE = 5;
Expand Down
10 changes: 5 additions & 5 deletions circuits/circom/trees/incrementalMerkleTree.circom
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma circom 2.0.0;
include "../../node_modules/circomlib/circuits/mux1.circom";
include "../hasherPoseidon.circom";

// recompute a merkle root from a leaf and a path
template MerkleTreeInclusionProof(n_levels) {
signal input leaf;
signal input path_index[n_levels];
Expand Down Expand Up @@ -41,8 +42,8 @@ template MerkleTreeInclusionProof(n_levels) {
root <== levelHashes[n_levels];
}

// Ensures that a leaf exists within a merkletree with given `root`
template LeafExists(levels){
// Ensures that a leaf exists within a merkletree with given `root`

// levels is depth of tree
signal input leaf;
Expand All @@ -62,11 +63,10 @@ template LeafExists(levels){
root === merkletree.root;
}

// Given a Merkle root and a list of leaves, check if the root is the
// correct result of inserting all the leaves into the tree (in the given
// order)
template CheckRoot(levels) {
// Given a Merkle root and a list of leaves, check if the root is the
// correct result of inserting all the leaves into the tree (in the given
// order)

// Circom has some perticularities which limit the code patterns we can
// use.

Expand Down
25 changes: 13 additions & 12 deletions circuits/circom/trees/incrementalQuinTree.circom
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ include "../utils.circom";
// circuit to hash leaves, which supports up to 5 input elements.

/*
Note: circom has some particularities which limit the code patterns we can use.
- You can only assign a value to a signal once.
- A component's input signal must only be wired to another component's output
signal.
- Variables can store linear combinations, and can also be used for loops,
declaring sizes of things, and anything that is not related to inputs of a
circuit.
- The compiler fails whenever you try to mix invalid elements.
- You can't use a signal as a list index.
Note: circom has some particularities which limit the code patterns we can use.
- You can only assign a value to a signal once.
- A component's input signal must only be wired to another component's output
signal.
- Variables can store linear combinations, and can also be used for loops,
declaring sizes of things, and anything that is not related to inputs of a
circuit.
- The compiler fails whenever you try to mix invalid elements.
- You can't use a signal as a list index.
*/

// Given a list of items and an index, output the item at the position denoted
Expand Down Expand Up @@ -137,6 +137,8 @@ template Splicer(numItems) {
}
}

// Given a list of leaves, as well as the path to the root,
// compute the root
template QuinTreeInclusionProof(levels) {
// Each node has 5 leaves
var LEAVES_PER_NODE = 5;
Expand Down Expand Up @@ -185,9 +187,8 @@ template QuinTreeInclusionProof(levels) {
root <== hashers[levels - 1].hash;
}

// Ensures that a leaf exists within a quintree with given `root`
template QuinLeafExists(levels){
// Ensures that a leaf exists within a quintree with given `root`

var LEAVES_PER_NODE = 5;
var LEAVES_PER_PATH_LEVEL = LEAVES_PER_NODE - 1;

Expand Down
23 changes: 23 additions & 0 deletions circuits/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion circuits/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"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:ceremonyParams": "ts-mocha --exit ts/__tests__/CeremonyParams.test.ts"
"test:ceremonyParams": "ts-mocha --exit ts/__tests__/CeremonyParams.test.ts",
"test:quinCheckRoot": "ts-mocha --exit ts/__tests__/QuinCheckRoot.test.ts",
"test:incrementalQuinTree": "ts-mocha --exit ts/__tests__/IncrementalQuinTree.test.ts"
},
"dependencies": {
"circomlib": "https://github.com/weijiekoh/circomlib#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b",
Expand All @@ -33,9 +35,11 @@
},
"devDependencies": {
"@types/chai": "^4.3.11",
"@types/chai-as-promised": "^7.1.8",
"@types/mocha": "^10.0.6",
"@types/node": "^20.10.8",
"chai": "^4.3.10",
"chai-as-promised": "^7.1.1",
"circom_tester": "^0.0.20",
"mocha": "^10.2.0",
"ts-mocha": "^10.0.0",
Expand Down
44 changes: 43 additions & 1 deletion circuits/ts/__tests__/Ecdh.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect } from "chai";
import chai, { expect } from "chai";
import chaiAsPromised from "chai-as-promised";
import tester from "circom_tester";
import { stringifyBigInts } from "maci-crypto";
import { Keypair } from "maci-domainobjs";
Expand All @@ -7,6 +8,8 @@ import path from "path";

import { getSignal } from "./utils/utils";

chai.use(chaiAsPromised);

describe("Public key derivation circuit", () => {
const circuitPath = path.resolve(__dirname, "../../circom/test", `ecdh_test.circom`);
let circuit: tester.WasmTester;
Expand Down Expand Up @@ -34,4 +37,43 @@ describe("Public key derivation circuit", () => {
expect(circuitEcdhSharedKey0.toString()).to.be.eq(ecdhSharedKey[0].toString());
expect(circuitEcdhSharedKey1.toString()).to.be.eq(ecdhSharedKey[1].toString());
});

it("should generate the same ECDH key given the same inputs", async () => {
const keypair = new Keypair();
const keypair2 = new Keypair();

const circuitInputs = stringifyBigInts({
privKey: keypair.privKey.asCircuitInputs(),
pubKey: keypair2.pubKey.asCircuitInputs(),
});

let witness = await circuit.calculateWitness(circuitInputs, true);
await circuit.checkConstraints(witness);

const circuitEcdhSharedKey0 = await getSignal(circuit, witness, "sharedKey[0]");
const circuitEcdhSharedKey1 = await getSignal(circuit, witness, "sharedKey[1]");

witness = await circuit.calculateWitness(circuitInputs, true);
await circuit.checkConstraints(witness);

const circuitEcdhSharedKey02 = await getSignal(circuit, witness, "sharedKey[0]");
const circuitEcdhSharedKey12 = await getSignal(circuit, witness, "sharedKey[1]");

expect(circuitEcdhSharedKey0.toString()).to.be.eq(circuitEcdhSharedKey02.toString());
expect(circuitEcdhSharedKey1.toString()).to.be.eq(circuitEcdhSharedKey12.toString());
});

it("should throw when given invalid inputs (pubKey too short)", async () => {
const keypair = new Keypair();
const keypair2 = new Keypair();

const circuitInputs = stringifyBigInts({
privKey: keypair.privKey.asCircuitInputs(),
pubKey: keypair2.pubKey.asCircuitInputs().slice(0, 1),
});

await expect(circuit.calculateWitness(circuitInputs, true)).to.be.rejectedWith(
"Not enough values for input signal pubKey",
);
});
});
31 changes: 25 additions & 6 deletions circuits/ts/__tests__/Hasher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("Poseidon hash circuits", function test() {
circuit = await tester.wasm(circuitPath);
});

it("correctly hashes two random values", async () => {
it("should correctly hash two random values", async () => {
const left = genRandomSalt();
const right = genRandomSalt();

Expand All @@ -41,7 +41,7 @@ describe("Poseidon hash circuits", function test() {
circuit = await tester.wasm(circuitPath);
});

it("correctly hashes 4 random values", async () => {
it("should correctly hash 4 random values", async () => {
const preImages: bigint[] = [];
for (let i = 0; i < 4; i += 1) {
preImages.push(genRandomSalt());
Expand All @@ -66,7 +66,8 @@ describe("Poseidon hash circuits", function test() {
const circuitPath = path.resolve(__dirname, "../../circom/test", `sha256Hasher6_test.circom`);
circuit = await tester.wasm(circuitPath);
});
it("correctly hashes 6 random values", async () => {

it("should correctly hash 6 random values", async () => {
const preImages: bigint[] = [];
for (let i = 0; i < 6; i += 1) {
preImages.push(genRandomSalt());
Expand All @@ -93,6 +94,7 @@ describe("Poseidon hash circuits", function test() {
const circuitPath = path.resolve(__dirname, "../../circom/test", `hasher5_test.circom`);
circuit = await tester.wasm(circuitPath);
});

it("correctly hashes 5 random values", async () => {
const preImages: bigint[] = [];
for (let i = 0; i < 5; i += 1) {
Expand Down Expand Up @@ -171,7 +173,7 @@ describe("Poseidon hash circuits", function test() {
circuit = await tester.wasm(circuitPath);
});

it("correctly hashes 13 random values", async () => {
it("should correctly hash 13 random values", async () => {
const preImages: bigint[] = [];
for (let i = 0; i < 13; i += 1) {
preImages.push(genRandomSalt());
Expand All @@ -196,7 +198,7 @@ describe("Poseidon hash circuits", function test() {
circuit = await tester.wasm(circuitPath);
});

it("correctly hashes two random values", async () => {
it("should correctly hash two random values", async () => {
const left = genRandomSalt();
const right = genRandomSalt();

Expand All @@ -210,6 +212,23 @@ describe("Poseidon hash circuits", function test() {

expect(output.toString()).to.be.eq(outputJS.toString());
});

it("should produce consistent results", async () => {
const left = genRandomSalt();
const right = genRandomSalt();

const circuitInputs = stringifyBigInts({ left, right });

let witness = await circuit.calculateWitness(circuitInputs, true);
await circuit.checkConstraints(witness);
const output = await getSignal(circuit, witness, "hash");

witness = await circuit.calculateWitness(circuitInputs, true);
await circuit.checkConstraints(witness);
const output2 = await getSignal(circuit, witness, "hash");

expect(output.toString()).to.be.eq(output2.toString());
});
});
});

Expand All @@ -219,7 +238,7 @@ describe("Poseidon hash circuits", function test() {
circuit = await tester.wasm(circuitPath);
});

it("correctly hashes a message", async () => {
it("should correctly hash a message", async () => {
const k = new Keypair();
const random50bitBigInt = (): bigint =>
// eslint-disable-next-line no-bitwise
Expand Down
Loading

0 comments on commit 95e084a

Please sign in to comment.