From f3a3487c17db61ae24225bdc956b538932b54bef Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:02:25 +0300 Subject: [PATCH] chore: add circom tests --- .github/workflows/circom.yml | 70 +++++ .github/workflows/javascript.yml | 6 - circuits/circom/package.json | 4 +- .../12_point_sha_256_test.circom | 2 +- .../{ => circuits}/a_div_b_pow_c_test.circom | 2 +- .../{ => circuits}/compression_test.circom | 2 +- .../compression_verification_test.circom | 2 +- .../test/circuits/hash_to_curve_test.circom | 5 + .../circom/test/{ => circuits}/v1_test.circom | 2 +- .../circom/test/{ => circuits}/v2_test.circom | 2 +- circuits/circom/test/compression.test.ts | 76 ++++++ circuits/circom/test/conversion.test.ts | 14 + circuits/circom/test/hashToCurve.test.ts | 39 +++ .../circom/test/hash_to_curve_test.circom | 5 - circuits/circom/test/sha256Circuit.test.ts | 75 ++++++ circuits/circom/test/subcircuit.test.ts | 41 +++ circuits/circom/test/v1.test.ts | 81 ++++++ circuits/circom/test/v2.test.ts | 77 ++++++ circuits/circom/test/vfy_nullifier.test.ts | 240 ------------------ 19 files changed, 486 insertions(+), 259 deletions(-) create mode 100644 .github/workflows/circom.yml rename circuits/circom/test/{ => circuits}/12_point_sha_256_test.circom (63%) rename circuits/circom/test/{ => circuits}/a_div_b_pow_c_test.circom (60%) rename circuits/circom/test/{ => circuits}/compression_test.circom (61%) rename circuits/circom/test/{ => circuits}/compression_verification_test.circom (63%) create mode 100644 circuits/circom/test/circuits/hash_to_curve_test.circom rename circuits/circom/test/{ => circuits}/v1_test.circom (59%) rename circuits/circom/test/{ => circuits}/v2_test.circom (59%) create mode 100644 circuits/circom/test/compression.test.ts create mode 100644 circuits/circom/test/conversion.test.ts create mode 100644 circuits/circom/test/hashToCurve.test.ts delete mode 100644 circuits/circom/test/hash_to_curve_test.circom create mode 100644 circuits/circom/test/sha256Circuit.test.ts create mode 100644 circuits/circom/test/subcircuit.test.ts create mode 100644 circuits/circom/test/v1.test.ts create mode 100644 circuits/circom/test/v2.test.ts delete mode 100644 circuits/circom/test/vfy_nullifier.test.ts diff --git a/.github/workflows/circom.yml b/.github/workflows/circom.yml new file mode 100644 index 0000000..1c8bf4d --- /dev/null +++ b/.github/workflows/circom.yml @@ -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" diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 86e15e0..c1a619f 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -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" diff --git a/circuits/circom/package.json b/circuits/circom/package.json index f74bb1c..2b5bf42 100644 --- a/circuits/circom/package.json +++ b/circuits/circom/package.json @@ -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": { diff --git a/circuits/circom/test/12_point_sha_256_test.circom b/circuits/circom/test/circuits/12_point_sha_256_test.circom similarity index 63% rename from circuits/circom/test/12_point_sha_256_test.circom rename to circuits/circom/test/circuits/12_point_sha_256_test.circom index 9bcad08..13b8f54 100644 --- a/circuits/circom/test/12_point_sha_256_test.circom +++ b/circuits/circom/test/circuits/12_point_sha_256_test.circom @@ -1,5 +1,5 @@ pragma circom 2.1.2; -include "../verify_nullifier.circom"; +include "../../verify_nullifier.circom"; component main = sha256_12_coordinates(64, 4); diff --git a/circuits/circom/test/a_div_b_pow_c_test.circom b/circuits/circom/test/circuits/a_div_b_pow_c_test.circom similarity index 60% rename from circuits/circom/test/a_div_b_pow_c_test.circom rename to circuits/circom/test/circuits/a_div_b_pow_c_test.circom index edf3f24..e13699f 100644 --- a/circuits/circom/test/a_div_b_pow_c_test.circom +++ b/circuits/circom/test/circuits/a_div_b_pow_c_test.circom @@ -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); diff --git a/circuits/circom/test/compression_test.circom b/circuits/circom/test/circuits/compression_test.circom similarity index 61% rename from circuits/circom/test/compression_test.circom rename to circuits/circom/test/circuits/compression_test.circom index 2a55239..14e805f 100644 --- a/circuits/circom/test/compression_test.circom +++ b/circuits/circom/test/circuits/compression_test.circom @@ -1,5 +1,5 @@ pragma circom 2.1.2; -include "../verify_nullifier.circom"; +include "../../verify_nullifier.circom"; component main = compress_ec_point(64, 4); diff --git a/circuits/circom/test/compression_verification_test.circom b/circuits/circom/test/circuits/compression_verification_test.circom similarity index 63% rename from circuits/circom/test/compression_verification_test.circom rename to circuits/circom/test/circuits/compression_verification_test.circom index f208b53..d95b92e 100644 --- a/circuits/circom/test/compression_verification_test.circom +++ b/circuits/circom/test/circuits/compression_verification_test.circom @@ -1,5 +1,5 @@ pragma circom 2.1.2; -include "../verify_nullifier.circom"; +include "../../verify_nullifier.circom"; component main = verify_ec_compression(64, 4); diff --git a/circuits/circom/test/circuits/hash_to_curve_test.circom b/circuits/circom/test/circuits/hash_to_curve_test.circom new file mode 100644 index 0000000..0e4ead8 --- /dev/null +++ b/circuits/circom/test/circuits/hash_to_curve_test.circom @@ -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); diff --git a/circuits/circom/test/v1_test.circom b/circuits/circom/test/circuits/v1_test.circom similarity index 59% rename from circuits/circom/test/v1_test.circom rename to circuits/circom/test/circuits/v1_test.circom index 6b65ce7..6824f88 100644 --- a/circuits/circom/test/v1_test.circom +++ b/circuits/circom/test/circuits/v1_test.circom @@ -1,5 +1,5 @@ pragma circom 2.1.2; -include "../verify_nullifier.circom"; +include "../../verify_nullifier.circom"; component main = plume_v1(64, 4, 29); diff --git a/circuits/circom/test/v2_test.circom b/circuits/circom/test/circuits/v2_test.circom similarity index 59% rename from circuits/circom/test/v2_test.circom rename to circuits/circom/test/circuits/v2_test.circom index 2b8a02e..338bdc7 100644 --- a/circuits/circom/test/v2_test.circom +++ b/circuits/circom/test/circuits/v2_test.circom @@ -1,5 +1,5 @@ pragma circom 2.1.2; -include "../verify_nullifier.circom"; +include "../../verify_nullifier.circom"; component main = plume_v2(64, 4, 29); diff --git a/circuits/circom/test/compression.test.ts b/circuits/circom/test/compression.test.ts new file mode 100644 index 0000000..1557f4a --- /dev/null +++ b/circuits/circom/test/compression.test.ts @@ -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", + ); + } + } + } + }); +}); diff --git a/circuits/circom/test/conversion.test.ts b/circuits/circom/test/conversion.test.ts new file mode 100644 index 0000000..980f3f0 --- /dev/null +++ b/circuits/circom/test/conversion.test.ts @@ -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); + }); + }); +}); diff --git a/circuits/circom/test/hashToCurve.test.ts b/circuits/circom/test/hashToCurve.test.ts new file mode 100644 index 0000000..912154c --- /dev/null +++ b/circuits/circom/test/hashToCurve.test.ts @@ -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) }); + }); +}); diff --git a/circuits/circom/test/hash_to_curve_test.circom b/circuits/circom/test/hash_to_curve_test.circom deleted file mode 100644 index 9583914..0000000 --- a/circuits/circom/test/hash_to_curve_test.circom +++ /dev/null @@ -1,5 +0,0 @@ -pragma circom 2.1.2; - -include "../node_modules/secp256k1_hash_to_curve_circom/circom/hash_to_curve.circom"; - -component main = HashToCurve(62); diff --git a/circuits/circom/test/sha256Circuit.test.ts b/circuits/circom/test/sha256Circuit.test.ts new file mode 100644 index 0000000..d15b9ed --- /dev/null +++ b/circuits/circom/test/sha256Circuit.test.ts @@ -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 }); + }); +}); diff --git a/circuits/circom/test/subcircuit.test.ts b/circuits/circom/test/subcircuit.test.ts new file mode 100644 index 0000000..70a04f8 --- /dev/null +++ b/circuits/circom/test/subcircuit.test.ts @@ -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) }); + }); +}); diff --git a/circuits/circom/test/v1.test.ts b/circuits/circom/test/v1.test.ts new file mode 100644 index 0000000..10d7d69 --- /dev/null +++ b/circuits/circom/test/v1.test.ts @@ -0,0 +1,81 @@ +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 { bufToSha256PaddedBitArr } from "secp256k1_hash_to_curve_circom/ts/utils"; +import { utils } from "ffjavascript"; + +import { + c_v1, + rPoint, + hashMPk, + hashedToCurveR, + nullifier, + s_v1, + testMessage, + testPublicKey, + testSecretKey, +} from "../../../javascript/test/consts"; +import { + hexToBigInt, + concatUint8Arrays, +} from "../../../javascript/src/utils/encoding"; + +import { pointToCircuitValue, scalarToCircuitValue } from "../utils"; + +jest.setTimeout(4_000_000); + +describe("V1 Circuit", () => { + 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)), + ); + + 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, + ); + + test("V1 circuit works", async () => { + const p = path.join(__dirname, "./circuits/v1_test.circom"); + const circuit = await wasm_tester(p); + + const { msg: _, ...htci } = hash_to_curve_inputs; + const w = await circuit.calculateWitness({ + // Main circuit inputs + c: scalarToCircuitValue(hexToBigInt(c_v1)), + s: scalarToCircuitValue(hexToBigInt(s_v1)), + plume_message: message_bytes, + pk: pointToCircuitValue(Point.fromPrivateKey(testSecretKey)), + nullifier: pointToCircuitValue(nullifier), + ...htci, + sha256_preimage_bit_length: v1_sha256_preimage_bit_length, + }); + await circuit.checkConstraints(w); + }); +}); diff --git a/circuits/circom/test/v2.test.ts b/circuits/circom/test/v2.test.ts new file mode 100644 index 0000000..00570ef --- /dev/null +++ b/circuits/circom/test/v2.test.ts @@ -0,0 +1,77 @@ +import crypto from "crypto"; +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 { + c_v2, + rPoint, + hashedToCurveR, + nullifier, + s_v2, + testMessage, + testPublicKey, + testSecretKey, +} from "../../../javascript/test/consts"; +import { + hexToBigInt, + concatUint8Arrays, +} from "../../../javascript/src/utils/encoding"; + +import { pointToCircuitValue, scalarToCircuitValue } from "../utils"; + +jest.setTimeout(4_000_000); + +describe("V2 Circuit", () => { + const public_key_bytes = Array.from(testPublicKey); + const message_bytes = Array.from(testMessage); + + const hash_to_curve_inputs = utils.stringifyBigInts( + generate_inputs_from_array(message_bytes.concat(public_key_bytes)), + ); + + test("V2 circuit works", async () => { + const p = path.join(__dirname, "./circuits/v2_test.circom"); + const circuit = await wasm_tester(p); + + const { msg: _, ...htci } = hash_to_curve_inputs; + + const w = await circuit.calculateWitness({ + // Main circuit inputs + c: scalarToCircuitValue(hexToBigInt(c_v2)), + s: scalarToCircuitValue(hexToBigInt(s_v2)), + plume_message: message_bytes, + pk: pointToCircuitValue(Point.fromPrivateKey(testSecretKey)), + nullifier: pointToCircuitValue(nullifier), + ...htci, + }); + await circuit.checkConstraints(w); + /* assertOut builds a huge json string containing the whole witness and fails + with "Cannot create a string longer than 0x1fffffe8 characters" */ + /* Instead we just slice into the witness, and the outputs start at 1 + (where 0 always equals 1 due to a property of the underlying proof system) */ + expect(w.slice(1, 5)).toEqual(pointToCircuitValue(rPoint)[0]); + expect(w.slice(5, 9)).toEqual(pointToCircuitValue(rPoint)[1]); + expect(w.slice(9, 13)).toEqual(pointToCircuitValue(hashedToCurveR)[0]); + expect(w.slice(13, 17)).toEqual(pointToCircuitValue(hashedToCurveR)[1]); + + // In v2 we check the challenge point c outside the circuit + /* Note, in a real application you would get the nullifier, + g^r, and h^r as public outputs/inputs of the proof */ + expect( + crypto + .createHash("sha256") + .update( + concatUint8Arrays([ + nullifier.toRawBytes(true), + rPoint.toRawBytes(true), + hashedToCurveR.toRawBytes(true), + ]), + ) + .digest("hex"), + ).toEqual(c_v2); + }); +}); diff --git a/circuits/circom/test/vfy_nullifier.test.ts b/circuits/circom/test/vfy_nullifier.test.ts deleted file mode 100644 index 98a9387..0000000 --- a/circuits/circom/test/vfy_nullifier.test.ts +++ /dev/null @@ -1,240 +0,0 @@ -import crypto from "crypto"; -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 { bufToSha256PaddedBitArr } from "secp256k1_hash_to_curve_circom/ts/utils"; -import { utils } from "ffjavascript"; - -import { - c_v1, - c_v2, - rPoint, - hashMPk, - hashedToCurveR, - nullifier, - s_v1, - s_v2, - testMessage, - testPublicKey, - testSecretKey, -} from "../../../javascript/test/consts"; -import { - hexToBigInt, - concatUint8Arrays, -} from "../../../javascript/src/utils/encoding"; - -import { - circuitValueToScalar, - pointToCircuitValue, - scalarToCircuitValue, -} from "../utils"; - -jest.setTimeout(2_000_000); - -describe("Nullifier Circuit", () => { - 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)), - ); - - 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("hash_to_curve outputs same value", async () => { - const p = path.join(__dirname, "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) }); - }); - - 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, "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 }); - }); - - test("Correct compressed values are calculated", async () => { - const p = path.join(__dirname, "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 iff they are valid", async () => { - const p = path.join(__dirname, "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", - ); - } - } - } - }); - - test("V1 circuit works", async () => { - const p = path.join(__dirname, "v1_test.circom"); - const circuit = await wasm_tester(p); - - const { msg: _, ...htci } = hash_to_curve_inputs; - const w = await circuit.calculateWitness({ - // Main circuit inputs - c: scalarToCircuitValue(hexToBigInt(c_v1)), - s: scalarToCircuitValue(hexToBigInt(s_v1)), - plume_message: message_bytes, - pk: pointToCircuitValue(Point.fromPrivateKey(testSecretKey)), - nullifier: pointToCircuitValue(nullifier), - ...htci, - sha256_preimage_bit_length: v1_sha256_preimage_bit_length, - }); - await circuit.checkConstraints(w); - }); - - test("V2 circuit works", async () => { - const p = path.join(__dirname, "v2_test.circom"); - const circuit = await wasm_tester(p); - - const { msg: _, ...htci } = hash_to_curve_inputs; - - const w = await circuit.calculateWitness({ - // Main circuit inputs - c: scalarToCircuitValue(hexToBigInt(c_v2)), - s: scalarToCircuitValue(hexToBigInt(s_v2)), - plume_message: message_bytes, - pk: pointToCircuitValue(Point.fromPrivateKey(testSecretKey)), - nullifier: pointToCircuitValue(nullifier), - ...htci, - }); - await circuit.checkConstraints(w); - /* assertOut builds a huge json string containing the whole witness and fails - with "Cannot create a string longer than 0x1fffffe8 characters" */ - /* Instead we just slice into the witness, and the outputs start at 1 - (where 0 always equals 1 due to a property of the underlying proof system) */ - expect(w.slice(1, 5)).toEqual(pointToCircuitValue(rPoint)[0]); - expect(w.slice(5, 9)).toEqual(pointToCircuitValue(rPoint)[1]); - expect(w.slice(9, 13)).toEqual(pointToCircuitValue(hashedToCurveR)[0]); - expect(w.slice(13, 17)).toEqual(pointToCircuitValue(hashedToCurveR)[1]); - - // In v2 we check the challenge point c outside the circuit - /* Note, in a real application you would get the nullifier, - g^r, and h^r as public outputs/inputs of the proof */ - expect( - crypto - .createHash("sha256") - .update( - concatUint8Arrays([ - nullifier.toRawBytes(true), - rPoint.toRawBytes(true), - hashedToCurveR.toRawBytes(true), - ]), - ) - .digest("hex"), - ).toEqual(c_v2); - }); - - // This tests that our circuit correctly computes g^s/(g^sk)^c = g^r, and that the first two equations are - // implicitly verified correctly. - test("a/b^c subcircuit", async () => { - const p = path.join(__dirname, "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), - ); - console.log(gPowS instanceof Point, pkPowC instanceof Point); - 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) }); - }); - - test("bigint <-> register conversion", async () => { - [ - 132467823045762934876529873465987623452222345n, - 57574748379385798237094756982679876233455n, - 55757457845857572n, - 1n, - ].forEach((value) => { - expect(circuitValueToScalar(scalarToCircuitValue(value))).toBe(value); - }); - }); -});