Skip to content

Commit

Permalink
chore: add circom tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmad committed Nov 14, 2023
1 parent fa708db commit f3a3487
Show file tree
Hide file tree
Showing 19 changed files with 486 additions and 259 deletions.
70 changes: 70 additions & 0 deletions .github/workflows/circom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Circom checks
on: [push, pull_request]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
checks:
strategy:
fail-fast: false
matrix:
node-version: [18]
command: ["prettier", "types", "test:coverage"]

runs-on: ubuntu-latest

timeout-minutes: 240

steps:
- uses: actions/checkout@v4

- name: Clone circom repository
uses: actions/checkout@v4
with:
repository: iden3/circom
path: ./circom

- uses: pnpm/action-setup@v2
with:
version: latest

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- uses: dtolnay/rust-toolchain@nightly
with:
components: "clippy, rustfmt"

- name: Cache circom
uses: actions/cache@v3
id: circom-cache
continue-on-error: true
with:
path: |
~/.cargo/bin/circom
key: ${{ runner.os }}-circom-${{ hashFiles('./circom/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-circom-
- name: Install circom
if: steps.circom-cache.outputs.cache-hit != 'true'
run: |
cargo build --release
cargo install --path circom
working-directory: ./circom


- name: Build javascript
run: |
pnpm install --no-frozen-lockfile --prefer-offline
pnpm run build
working-directory: "./javascript"

- name: Check circom
run: |
pnpm install --no-frozen-lockfile --prefer-offline
pnpm run ${{ matrix.command }}
working-directory: "./circuits/circom"
6 changes: 0 additions & 6 deletions .github/workflows/javascript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,3 @@ jobs:
pnpm run build
pnpm run ${{ matrix.command }}
working-directory: "./javascript"

- name: Check circom
run: |
pnpm install --no-frozen-lockfile --prefer-offline
pnpm run ${{ matrix.command }}
working-directory: "./circuits/circom"
4 changes: 2 additions & 2 deletions circuits/circom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"prettier:fix": "prettier -w . --config ../../.prettierrc --ignore-path ../../.prettierignore",
"types": "tsc -p tsconfig.json --noEmit",
"postinstall": "bash flatten-nested-dependencies.sh",
"test": "NODE_OPTIONS=--max_old_space_size=8192 jest --runInBand",
"test:coverage": "exit 0"
"test": "NODE_OPTIONS=--max_old_space_size=8192 jest",
"test:coverage": "pnpm run test"
},
"license": "ISC",
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pragma circom 2.1.2;

include "../verify_nullifier.circom";
include "../../verify_nullifier.circom";

component main = sha256_12_coordinates(64, 4);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pragma circom 2.1.2;

include "../verify_nullifier.circom";
include "../../verify_nullifier.circom";

component main = a_div_b_pow_c(64, 4);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pragma circom 2.1.2;

include "../verify_nullifier.circom";
include "../../verify_nullifier.circom";

component main = compress_ec_point(64, 4);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pragma circom 2.1.2;

include "../verify_nullifier.circom";
include "../../verify_nullifier.circom";

component main = verify_ec_compression(64, 4);
5 changes: 5 additions & 0 deletions circuits/circom/test/circuits/hash_to_curve_test.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.2;

include "../../node_modules/secp256k1_hash_to_curve_circom/circom/hash_to_curve.circom";

component main = HashToCurve(62);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pragma circom 2.1.2;

include "../verify_nullifier.circom";
include "../../verify_nullifier.circom";

component main = plume_v1(64, 4, 29);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pragma circom 2.1.2;

include "../verify_nullifier.circom";
include "../../verify_nullifier.circom";

component main = plume_v2(64, 4, 29);
76 changes: 76 additions & 0 deletions circuits/circom/test/compression.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import path from "path";

import { Point } from "@noble/secp256k1";
import { wasm as wasm_tester } from "circom_tester";

import {
rPoint,
hashMPk,
hashedToCurveR,
nullifier,
testSecretKey,
} from "../../../javascript/test/consts";
import { hexToBigInt } from "../../../javascript/src/utils/encoding";

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

jest.setTimeout(20_000);

describe("Compression Circuit", () => {
const hashMPkPoint = new Point(
hexToBigInt(hashMPk.x.toString()),
hexToBigInt(hashMPk.y.toString()),
);

const sha_preimage_points: Point[] = [
Point.BASE,
Point.fromPrivateKey(testSecretKey),
hashMPkPoint,
nullifier,
rPoint,
hashedToCurveR,
];

test("Correct compressed values are calculated", async () => {
const p = path.join(__dirname, "./circuits/compression_test.circom");
const circuit = await wasm_tester(p, { json: true, sym: true });

for (let i = 0; i < sha_preimage_points.length; i++) {
const w = await circuit.calculateWitness(
{ uncompressed: pointToCircuitValue(sha_preimage_points[i]) },
true,
);

await circuit.checkConstraints(w);
await circuit.assertOut(w, {
compressed: Array.from(sha_preimage_points[i].toRawBytes(true)),
});
}
});

test("Compressed points are permitted if they are valid", async () => {
const p = path.join(
__dirname,
"./circuits/compression_verification_test.circom",
);
const circuit = await wasm_tester(p, { json: true, sym: true });

for (let i = 0; i < sha_preimage_points.length; i++) {
for (let j = 0; j <= i; j++) {
const inputs = {
uncompressed: pointToCircuitValue(sha_preimage_points[i]),
compressed: Array.from(sha_preimage_points[j].toRawBytes(true)),
};

if (i === j) {
const w = await circuit.calculateWitness(inputs, true);
await circuit.checkConstraints(w);
} else {
await expect(circuit.calculateWitness(inputs)).rejects.toThrow(
"Assert Failed",
);
}
}
}
});
});
14 changes: 14 additions & 0 deletions circuits/circom/test/conversion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { circuitValueToScalar, scalarToCircuitValue } from "../utils";

describe("Conversion", () => {
test("bigint <-> register conversion", async () => {
[
132467823045762934876529873465987623452222345n,
57574748379385798237094756982679876233455n,
55757457845857572n,
1n,
].forEach((value) => {
expect(circuitValueToScalar(scalarToCircuitValue(value))).toBe(value);
});
});
});
39 changes: 39 additions & 0 deletions circuits/circom/test/hashToCurve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import path from "path";

import { Point } from "@noble/secp256k1";
import { wasm as wasm_tester } from "circom_tester";
import { generate_inputs_from_array } from "secp256k1_hash_to_curve_circom/ts/generate_inputs";
import { utils } from "ffjavascript";

import {
hashMPk,
testMessage,
testPublicKey,
} from "../../../javascript/test/consts";
import { hexToBigInt } from "../../../javascript/src/utils/encoding";

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

jest.setTimeout(2_000_000);

describe("Hash to curve", () => {
const public_key_bytes = Array.from(testPublicKey);
const message_bytes = Array.from(testMessage);

const hashMPkPoint = new Point(
hexToBigInt(hashMPk.x.toString()),
hexToBigInt(hashMPk.y.toString()),
);

const hash_to_curve_inputs = utils.stringifyBigInts(
generate_inputs_from_array(message_bytes.concat(public_key_bytes)),
);

test("should outputs same value", async () => {
const p = path.join(__dirname, "./circuits/hash_to_curve_test.circom");
const circuit = await wasm_tester(p, { json: true, sym: true });
const w = await circuit.calculateWitness({ ...hash_to_curve_inputs }, true);
await circuit.checkConstraints(w);
await circuit.assertOut(w, { out: pointToCircuitValue(hashMPkPoint) });
});
});
5 changes: 0 additions & 5 deletions circuits/circom/test/hash_to_curve_test.circom

This file was deleted.

75 changes: 75 additions & 0 deletions circuits/circom/test/sha256Circuit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import crypto from "crypto";
import path from "path";

import { Point } from "@noble/secp256k1";
import { wasm as wasm_tester } from "circom_tester";
import { bufToSha256PaddedBitArr } from "secp256k1_hash_to_curve_circom/ts/utils";

import {
c_v1,
rPoint,
hashMPk,
hashedToCurveR,
nullifier,
testSecretKey,
} from "../../../javascript/test/consts";
import {
hexToBigInt,
concatUint8Arrays,
} from "../../../javascript/src/utils/encoding";

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

jest.setTimeout(2_000_000);

describe("SHA256 Circuit", () => {
const hashMPkPoint = new Point(
hexToBigInt(hashMPk.x.toString()),
hexToBigInt(hashMPk.y.toString()),
);

const sha_preimage_points: Point[] = [
Point.BASE,
Point.fromPrivateKey(testSecretKey),
hashMPkPoint,
nullifier,
rPoint,
hashedToCurveR,
];

const v1_sha256_preimage_bits = bufToSha256PaddedBitArr(
Buffer.from(
concatUint8Arrays(
sha_preimage_points.map((point) => point.toRawBytes(true)),
),
),
);
const v1_sha256_preimage_bit_length = parseInt(
v1_sha256_preimage_bits.slice(-64),
2,
);

const v1_binary_c = BigInt("0x" + c_v1)
.toString(2)
.split("")
.map(Number);

test("Correct sha256 value", async () => {
const coordinates = [];
sha_preimage_points.forEach((point) => {
const cv = pointToCircuitValue(point);
coordinates.push(cv[0]);
coordinates.push(cv[1]);
});

const p = path.join(__dirname, "./circuits/12_point_sha_256_test.circom");
const circuit = await wasm_tester(p, { json: true, sym: true });

const w = await circuit.calculateWitness(
{ coordinates, preimage_bit_length: v1_sha256_preimage_bit_length },
true,
);
await circuit.checkConstraints(w);
await circuit.assertOut(w, { out: v1_binary_c });
});
});
41 changes: 41 additions & 0 deletions circuits/circom/test/subcircuit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import path from "path";

import { Point } from "@noble/secp256k1";
import { wasm as wasm_tester } from "circom_tester";

import {
c_v1,
rPoint,
s_v1,
testSecretKey,
} from "../../../javascript/test/consts";
import { hexToBigInt } from "../../../javascript/src/utils/encoding";

import { pointToCircuitValue, scalarToCircuitValue } from "../utils";

jest.setTimeout(2_000_000);

// This tests that our circuit correctly computes g^s/(g^sk)^c = g^r, and that the first two equations are
// implicitly verified correctly.
describe("a/b^c subcircuit", () => {
test("should check circuit calculation", async () => {
const p = path.join(__dirname, "./circuits/a_div_b_pow_c_test.circom");
const circuit = await wasm_tester(p);

// Verify that gPowS/pkPowC = gPowR outside the circuit, as a sanity check
const gPowS = Point.fromPrivateKey(s_v1);
const pkPowC = Point.fromPrivateKey(testSecretKey).multiply(
hexToBigInt(c_v1),
);
expect(gPowS.add(pkPowC.negate()).equals(rPoint)).toBe(true);

// Verify that circuit calculates g^s / pk^c = g^r
const w = await circuit.calculateWitness({
a: pointToCircuitValue(gPowS),
b: pointToCircuitValue(Point.fromPrivateKey(testSecretKey)),
c: scalarToCircuitValue(hexToBigInt(c_v1)),
});
await circuit.checkConstraints(w);
await circuit.assertOut(w, { out: pointToCircuitValue(rPoint) });
});
});
Loading

0 comments on commit f3a3487

Please sign in to comment.