diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..bc81a9cf --- /dev/null +++ b/Makefile @@ -0,0 +1,157 @@ +# +# snarkjs — zkSNARKs in JavaScript (Groth16, PLONK, FFLONK) +# +# Node.js / circom / Foundry-based development, test, and build. +# + +SHELL = /bin/bash + +# ── Top-level targets ───────────────────────────────────────────────── + +.PHONY: all install build test test-smart-contracts test-forge test-all clean +.PHONY: circuits circuit-groth16 circuit-circuit2 circuit-plonk circuit-fflonk +.PHONY: verifier-preview +.PHONY: print-% + +all: install build test + +install: + npm install --no-audit --no-fund --loglevel=error + cd smart_contract_tests && npm install --no-audit --no-fund --loglevel=error + +build: + npm run build + +# ── Tests ───────────────────────────────────────────────────────────── + +# Unit / integration tests (mocha). Covers: +# - Powers of Tau ceremony (full process) +# - Groth16 prove + off-chain verify +# - PLONK prove + off-chain verify +# - FFLONK prove + off-chain verify +# - Polynomial operations +# - Keypair derivation +test: + npm test + +# Run a single mocha test file or grep pattern. +# make test-file FILE=test/fullprocess.js +# make test-grep GREP="Groth16 smart contract" +test-file: + npx mocha $(FILE) + +test-grep: + npx mocha --grep "$(GREP)" + +# Smart-contract (Hardhat) tests. Generates a Groth16 zkey from scratch, +# exports a Solidity verifier, compiles + deploys it via Hardhat, and +# calls verifyProof() on-chain. This is the test that validates EIP-197 +# G2 encoding is correct end-to-end. +test-smart-contracts: + cd smart_contract_tests && npm test + +# ── Forge (Foundry) EVM verification test ────────────────────────────── +# +# Lightweight on-chain verification test using Forge's built-in revm EVM +# — the SAME precompile engine that rejected the faulty G2 encoding. +# +# Auto-skips with exit code 0 if Forge is not installed. No npm deps +# beyond snarkjs itself (no Hardhat, no ethers, no waffle). A single +# static Foundry binary is the only external requirement. +# +# make test-forge # 1-input circuit (test/groth16) +# make test-forge-all # both 1-input and 3-input circuits +# make nix-test-forge # inside Nix environment +# +# Override circuit / ptau: +# make test-forge CIRCUIT_DIR=test/circuit2 + +test-forge: + bash scripts/forge_verify_test.sh + +test-forge-all: + CIRCUIT_DIR=test/groth16 bash scripts/forge_verify_test.sh + CIRCUIT_DIR=test/circuit2 bash scripts/forge_verify_test.sh + +# Run ALL tests: off-chain unit tests + on-chain smart-contract tests. +test-all: test test-smart-contracts + +# ── Test circuits (circom → R1CS / WASM) ────────────────────────────── + +# Most test circuits are checked in pre-compiled. Use these targets to +# recompile them after editing a .circom file. +# +# make circuits # recompile all test circuits +# make circuit-groth16 # recompile only the groth16 1-input circuit + +CIRCUITS_DIR = test +CIRCOM = circom +CIRCOM_OPTS = --r1cs --wasm --sym + +circuits: circuit-groth16 circuit-circuit2 circuit-plonk circuit-fflonk + +circuit-groth16: + $(CIRCOM) $(CIRCOM_OPTS) $(CIRCUITS_DIR)/groth16/circuit.circom -o $(CIRCUITS_DIR)/groth16 + +circuit-circuit2: + $(CIRCOM) $(CIRCOM_OPTS) $(CIRCUITS_DIR)/circuit2/circuit.circom -o $(CIRCUITS_DIR)/circuit2 + +circuit-plonk: + $(CIRCOM) $(CIRCOM_OPTS) $(CIRCUITS_DIR)/plonk_circuit/circuit.circom -o $(CIRCUITS_DIR)/plonk_circuit + +circuit-fflonk: + $(CIRCOM) $(CIRCOM_OPTS) $(CIRCUITS_DIR)/fflonk/circuit.circom -o $(CIRCUITS_DIR)/fflonk + +# ── Verifier template checks ────────────────────────────────────────── + +# Dry-run: generate a Groth16 verifier from the template + a zkey and +# print it to stdout. Useful for inspecting the EIP-197 G2 encoding. +# +# Requires: make install (for snarkjs CLI), a compiled circuit, and a +# ptau file (use test/plonk_circuit/powersOfTau15_final.ptau). +# +# make verifier-preview +PTAU_FILE ?= test/plonk_circuit/powersOfTau15_final.ptau +R1CS_FILE ?= test/groth16/circuit.r1cs + +verifier-preview: + @snarkjs zkey new $(R1CS_FILE) $(PTAU_FILE) /tmp/verifier_preview.zkey 2>/dev/null + @snarkjs zkey export solidityverifier /tmp/verifier_preview.zkey /tmp/verifier_preview.sol 2>/dev/null + @cat /tmp/verifier_preview.sol + @rm -f /tmp/verifier_preview.zkey /tmp/verifier_preview.sol + +# ── Cleanup ─────────────────────────────────────────────────────────── + +clean: + rm -rf build cache + rm -rf smart_contract_tests/artifacts smart_contract_tests/cache smart_contract_tests/contracts + +distclean: clean + rm -rf node_modules smart_contract_tests/node_modules + +# +# nix-...: +# +# Use a Nix flake environment to execute the make target, e.g.: +# +# make nix-test +# make nix-test-smart-contracts +# make nix-test-all +# +nix-%: + @if [ -n "$(TARGET)" ]; then \ + nix develop .#$(TARGET) $(NIX_OPTS) --command make $*; \ + else \ + nix develop $(NIX_OPTS) --command make $*; \ + fi + +# +# Target to allow the printing of 'make' variables, e.g.: +# +# make print-PTAU_FILE +# +print-%: + @echo $* = "'$($*)'" + @echo $*\'s origin is $(origin $*) + +FORCE: diff --git a/README.md b/README.md index fd7ae016..6e865c79 100644 --- a/README.md +++ b/README.md @@ -649,6 +649,63 @@ await wtnsCalculate(input, wasmFile, wtns, {memorySize: 0}); +## Development + +To work on snarkjs itself: + +```sh +git clone https://github.com/iden3/snarkjs.git +cd snarkjs +npm install +``` + +### Run tests + +**Unit & integration tests** (off-chain proving/verifying): + +```sh +npm test # mocha: powers of tau, groth16, plonk, fflonk, polynomials +``` + +**Smart-contract tests** (on-chain verification via Hardhat): + +```sh +cd smart_contract_tests && npm install && npm test +``` + +### Forge-based on-chain verification (lightweight, auto-skips) + +A minimal Foundry-based test that validates exported Groth16 verifiers +against a real EVM precompile engine (revm). No Hardhat, no ethers — +just a single `forge` binary. If Foundry isn't installed the test +prints `SKIP` and exits cleanly. + +```sh +make test-forge # 1-input circuit +make test-forge-all # both 1-input and 3-input circuits +``` + +Install Foundry: + +```sh +curl -L https://foundry.paradigm.xyz | bash +foundryup +``` + +### Nix flake (reproducible dev environment) + +A `flake.nix` provides `node`, `circom`, `forge`, `solc`, and all npm +dependencies in a single shell: + +```sh +nix develop # enter the dev shell +make nix-test # run unit tests +make nix-test-forge # run forge EVM verification +make nix-test-smart-contracts # run Hardhat on-chain tests +``` + +All `make nix-` commands delegate to `nix develop --command make `. + ## Further resources - [Announcing the Perpetual Powers of Tau Ceremony to benefit all zk-SNARK projects](https://medium.com/coinmonks/announcing-the-perpetual-powers-of-tau-ceremony-to-benefit-all-zk-snark-projects-c3da86af8377) - [Scalable Multi-party Computation for zk-SNARK Parameters in diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..ad4683c9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1780453794, + "narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..98949484 --- /dev/null +++ b/flake.nix @@ -0,0 +1,80 @@ +{ + description = "snarkjs — zkSNARKs implementation in JavaScript (Groth16/PLONK/FFLONK)"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + commonInputs = with pkgs; [ + # Core tools + cacert + git + gnumake + gnused + bash + bash-completion + which + jq + curl + gh + + # Node.js — runtime and build tool + nodejs_22 + + # circom — compile .circom test circuits → R1CS / WASM + circom + + # Foundry — forge (compiler/test), anvil (local node), cast (CLI) + # Provides solc for Hardhat-based smart-contract tests + foundry + + # Standalone solc — for IDE/LSP support; Hardhat + Foundry also bundle solc + solc + ]; + in { + devShells.default = pkgs.mkShell { + buildInputs = commonInputs; + shellHook = '' + export SOLC_PATH="${pkgs.solc}/bin/solc" + + echo "snarkjs — zkSNARKs Development Environment" + echo "" + printf " %-12s %s\n" "node" "$(node --version 2>/dev/null)" + printf " %-12s %s\n" "circom" "$(circom --version 2>/dev/null | head -1)" + printf " %-12s %s\n" "forge" "$(forge --version 2>/dev/null | head -1)" + printf " %-12s %s\n" "cast" "$(cast --version 2>/dev/null | head -1)" + printf " %-12s %s\n" "solc" "$(solc --version 2>/dev/null | tail -1)" + printf " %-12s %s\n" "gh" "$(gh --version 2>/dev/null | head -1)" + + # Install root npm deps (snarkjs build deps: rollup, mocha, etc.) + if [ ! -d node_modules ] && [ -f package.json ]; then + echo "" + echo "[flake] installing root npm dev deps ..." + npm install --no-audit --no-fund --loglevel=error + fi + + # Install smart-contract test deps (hardhat, ethers, chai, etc.) + if [ -f smart_contract_tests/package.json ] && [ ! -d smart_contract_tests/node_modules ]; then + echo "" + echo "[flake] installing smart-contract test npm deps ..." + ( cd smart_contract_tests && npm install --no-audit --no-fund --loglevel=error ) + fi + + # Prepend local node_modules/.bin so snarkjs CLI is on PATH + if [ -x node_modules/.bin/snarkjs ]; then + export PATH="$PWD/node_modules/.bin:$PATH" + printf " %-12s %s\n" "snarkjs" "$(snarkjs --version 2>/dev/null || echo 'installed')" + fi + + echo "" + echo "Commands: make all | make test | make test-forge | make nix-test-forge | make build" + ''; + }; + }); +} diff --git a/forge_test/.gitignore b/forge_test/.gitignore new file mode 100644 index 00000000..755cd098 --- /dev/null +++ b/forge_test/.gitignore @@ -0,0 +1,6 @@ +# Generated files from forge verify test +src/ +test/ +out/ +cache/ +lib/ diff --git a/forge_test/foundry.lock b/forge_test/foundry.lock new file mode 100644 index 00000000..7521bfd0 --- /dev/null +++ b/forge_test/foundry.lock @@ -0,0 +1,8 @@ +{ + "lib/forge-std": { + "tag": { + "name": "v1.16.1", + "rev": "620536fa5277db4e3fd46772d5cbc1ea0696fb43" + } + } +} \ No newline at end of file diff --git a/forge_test/foundry.toml b/forge_test/foundry.toml new file mode 100644 index 00000000..d97c1617 --- /dev/null +++ b/forge_test/foundry.toml @@ -0,0 +1,13 @@ +[profile.default] + src = "src" + out = "out" + libs = ["lib"] + test = "test" + solc = "0.8.17" + evm_version = "istanbul" + optimizer = true + optimizer_runs = 999999 + +# The Groth16 verifier uses inline assembly (bn256Pairing precompile +# at addresses 6, 7, 8). These precompiles are available on all +# post-Byzantium EVMs; Istanbul is a safe default for revm/anvil. diff --git a/scripts/forge_verify_test.sh b/scripts/forge_verify_test.sh new file mode 100755 index 00000000..0aa48ad3 --- /dev/null +++ b/scripts/forge_verify_test.sh @@ -0,0 +1,307 @@ +#!/usr/bin/env bash +# +# forge_verify_test.sh — End-to-end Groth16 on-chain verification via Forge +# +# Generates a zkey + proof from a test circuit, exports a Solidity +# Groth16 verifier, and tests it against Forge's built-in revm EVM +# (the same precompile implementation that caught the EIP-197 G2 +# encoding bug). +# +# Auto-skips if Forge is not installed: +# SKIP: forge not found in PATH +# +# Usage: +# bash scripts/forge_verify_test.sh +# bash scripts/forge_verify_test.sh --circuit test/groth16 +# bash scripts/forge_verify_test.sh --circuit test/circuit2 +# +# Environment: +# FORGE_TEST_DIR forge project root (default: forge_test/) +# PTAU_FILE Powers of Tau file (default: test/plonk_circuit/powersOfTau15_final.ptau) +# CIRCUIT_DIR circuit + witness directory (default: test/groth16) +# VERBOSE set to 1 for verbose output + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# ── Configuration ───────────────────────────────────────────────────── + +FORGE_TEST_DIR="${FORGE_TEST_DIR:-$REPO_ROOT/forge_test}" +PTAU_FILE="${PTAU_FILE:-$REPO_ROOT/test/plonk_circuit/powersOfTau15_final.ptau}" +CIRCUIT_DIR="${CIRCUIT_DIR:-$REPO_ROOT/test/groth16}" +VERBOSE="${VERBOSE:-0}" + +R1CS_FILE="$CIRCUIT_DIR/circuit.r1cs" +WTNS_FILE="${WTNS_FILE:-$CIRCUIT_DIR/witness.wtns}" +# Some circuits don't have pre-built witnesses; generate from input.json +INPUT_JSON="$CIRCUIT_DIR/input.json" +WASM_FILE="$CIRCUIT_DIR/circuit.wasm" + +# Temp files +ZKEY_FILE="$(mktemp /tmp/forge_verify_zkey.XXXXXX)" +PROOF_FILE="$(mktemp /tmp/forge_verify_proof.XXXXXX)" +PUBLIC_FILE="$(mktemp /tmp/forge_verify_public.XXXXXX)" + +# ── Colour helpers ──────────────────────────────────────────────────── + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +pass_msg() { echo -e "${GREEN}PASS${NC} $*"; } +fail_msg() { echo -e "${RED}FAIL${NC} $*"; } +skip_msg() { echo -e "${YELLOW}SKIP${NC} $*"; } +info_msg() { echo "INFO $*"; } + +# ── Pre-flight ──────────────────────────────────────────────────────── + +# Find snarkjs CLI. npm only creates the bin symlink when snarkjs is +# installed as a *dependency* (e.g. smart_contract_tests/node_modules/.bin/), +# not in the package's own node_modules/.bin/. Use the source cli.js +# directly — it's an ES module that works without any build step. +SNARKJS="node $REPO_ROOT/cli.js" +if [ -x "$REPO_ROOT/smart_contract_tests/node_modules/.bin/snarkjs" ]; then + SNARKJS="$REPO_ROOT/smart_contract_tests/node_modules/.bin/snarkjs" +elif [ -x "$REPO_ROOT/node_modules/.bin/snarkjs" ]; then + SNARKJS="$REPO_ROOT/node_modules/.bin/snarkjs" +elif command -v snarkjs >/dev/null 2>&1; then + SNARKJS="$(command -v snarkjs)" +fi + +# Check that required inputs exist. +for f in "$R1CS_FILE" "$PTAU_FILE"; do + if [ ! -f "$f" ]; then + fail_msg "missing required file: $f" + exit 1 + fi +done + +# Generate witness if wtns file doesn't exist. +if [ ! -f "$WTNS_FILE" ]; then + if [ ! -f "$INPUT_JSON" ] || [ ! -f "$WASM_FILE" ]; then + fail_msg "no witness file ($WTNS_FILE) and cannot generate one (need $INPUT_JSON and $WASM_FILE)" + exit 1 + fi + info_msg "generating witness from $INPUT_JSON ..." + WTNS_FILE="$CIRCUIT_DIR/witness.wtns" + node -e " + const snarkjs = require('$REPO_ROOT/main.js'); + snarkjs.wtns.calculate( + JSON.parse(require('fs').readFileSync('$INPUT_JSON','utf8')), + '$WASM_FILE', + '$WTNS_FILE' + ).then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1); }); + " +fi + +# ── Forge check ─────────────────────────────────────────────────────── + +check_forge() { + if ! command -v forge >/dev/null 2>&1; then + return 1 + fi + return 0 +} + +# ── Forge-std bootstrap ─────────────────────────────────────────────── + +ensure_forge_std() { + local lib_dir="$FORGE_TEST_DIR/lib/forge-std" + if [ -d "$lib_dir" ]; then + return 0 + fi + info_msg "installing forge-std ..." + ( cd "$FORGE_TEST_DIR" && forge install foundry-rs/forge-std >/dev/null 2>&1 ) || { + fail_msg "could not install forge-std (network issue?)" + return 1 + } +} + +# ── Solidity helper ─────────────────────────────────────────────────── + +# Format a BigInt / decimal string for inclusion in Solidity source. +# snarkjs outputs decimal strings; Solidity needs them as-is for uint256. +solidity_uint() { + printf '%s' "$1" +} + +# ── Generate verifier + proof ───────────────────────────────────────── + +generate_artifacts() { + info_msg "generating zkey from $R1CS_FILE ..." + "$SNARKJS" zkey new "$R1CS_FILE" "$PTAU_FILE" "$ZKEY_FILE" 2>/dev/null + + info_msg "generating proof ..." + "$SNARKJS" groth16 prove "$ZKEY_FILE" "$WTNS_FILE" "$PROOF_FILE" "$PUBLIC_FILE" 2>/dev/null + + # Export Verifier to Forge src/ + mkdir -p "$FORGE_TEST_DIR/src" + info_msg "exporting Solidity verifier to $FORGE_TEST_DIR/src/Groth16Verifier.sol ..." + "$SNARKJS" zkey export solidityverifier "$ZKEY_FILE" "$FORGE_TEST_DIR/src/Groth16Verifier.sol" 2>/dev/null +} + +# ── Write Forge test ────────────────────────────────────────────────── + +write_forge_test() { + local test_file="$FORGE_TEST_DIR/test/Groth16Verifier.t.sol" + mkdir -p "$(dirname "$test_file")" + + # Parse proof.json and public.json to extract values. + local proof_json public_json + proof_json="$(cat "$PROOF_FILE")" + public_json="$(cat "$PUBLIC_FILE")" + + # Extract proof components (ffjavascript order). + local pi_a_0 pi_a_1 pi_b_0_0 pi_b_0_1 pi_b_1_0 pi_b_1_1 pi_c_0 pi_c_1 + pi_a_0="$(echo "$proof_json" | jq -r '.pi_a[0]')" + pi_a_1="$(echo "$proof_json" | jq -r '.pi_a[1]')" + pi_b_0_0="$(echo "$proof_json" | jq -r '.pi_b[0][0]')" + pi_b_0_1="$(echo "$proof_json" | jq -r '.pi_b[0][1]')" + pi_b_1_0="$(echo "$proof_json" | jq -r '.pi_b[1][0]')" + pi_b_1_1="$(echo "$proof_json" | jq -r '.pi_b[1][1]')" + pi_c_0="$(echo "$proof_json" | jq -r '.pi_c[0]')" + pi_c_1="$(echo "$proof_json" | jq -r '.pi_c[1]')" + + # Count public inputs + local n_public + n_public="$(echo "$public_json" | jq 'length')" + + # Build public signals array literal — each element individually + # wrapped with uint256() for explicit Solidity type conversion. + local pub_signals="" + for (( i=0; i "$test_file" <=0.7.0 <0.9.0; + +import {Test, console} from "forge-std/Test.sol"; +import {Groth16Verifier} from "../src/Groth16Verifier.sol"; + +contract Groth16VerifierTest is Test { + Groth16Verifier public verifier; + + function setUp() public { + verifier = new Groth16Verifier(); + } + + /// @dev Valid proof MUST verify on-chain. + function test_validProofVerifies() public view { + uint256[2] memory pA = [uint256($pi_a_0), uint256($pi_a_1)]; + uint256[2][2] memory pB = [ + [uint256($pi_b_0_0), uint256($pi_b_0_1)], + [uint256($pi_b_1_0), uint256($pi_b_1_1)] + ]; + uint256[2] memory pC = [uint256($pi_c_0), uint256($pi_c_1)]; + uint256[$n_public] memory pubSignals = [$pub_signals]; + + bool ok = verifier.verifyProof(pA, pB, pC, pubSignals); + assertTrue(ok, "valid proof did not verify on-chain"); + } + + /// @dev Proof with aliased (wrapped) public input MUST be rejected. + function test_aliasedInputFails() public view { + uint256[2] memory pA = [uint256($pi_a_0), uint256($pi_a_1)]; + uint256[2][2] memory pB = [ + [uint256($pi_b_0_0), uint256($pi_b_0_1)], + [uint256($pi_b_1_0), uint256($pi_b_1_1)] + ]; + uint256[2] memory pC = [uint256($pi_c_0), uint256($pi_c_1)]; + uint256[$n_public] memory pubSignals = [$aliased_signals]; + + bool ok = verifier.verifyProof(pA, pB, pC, pubSignals); + assertFalse(ok, "aliased public input was not rejected"); + } +} +SOL +} + +# ── Run Forge tests ─────────────────────────────────────────────────── + +run_forge_test() { + info_msg "running forge test ..." + ( cd "$FORGE_TEST_DIR" && forge test -vvv 2>&1 ) || { + fail_msg "forge test failed — EVM rejected the proof" + return 1 + } +} + +# ── Cleanup ─────────────────────────────────────────────────────────── + +cleanup() { + rm -f "$ZKEY_FILE" "$PROOF_FILE" "$PUBLIC_FILE" +} +trap cleanup EXIT + +# ── Main ────────────────────────────────────────────────────────────── + +main() { + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " snarkjs Groth16 — Forge On-Chain Verification Test" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo " Circuit: $CIRCUIT_DIR" + echo " PTAU: $PTAU_FILE" + echo " Forge dir: $FORGE_TEST_DIR" + echo "" + + if ! check_forge; then + skip_msg "forge not found in PATH — skipping EVM verification test" + echo "" + echo " Install Foundry: curl -L https://foundry.paradigm.xyz | bash" + echo " Then: foundryup" + echo "" + exit 0 + fi + + ensure_forge_std || exit 1 + generate_artifacts + write_forge_test + run_forge_test + + echo "" + pass_msg "Groth16 on-chain verification test passed" + echo "" +} + +main "$@" diff --git a/smart_contract_tests/test/smart_contracts.test.js b/smart_contract_tests/test/smart_contracts.test.js index d24f8bcf..737b5236 100644 --- a/smart_contract_tests/test/smart_contracts.test.js +++ b/smart_contract_tests/test/smart_contracts.test.js @@ -92,7 +92,7 @@ describe("Smart contracts test suite", function () { const { proof: proof, publicSignals: publicInputs } = await snarkjs.groth16.prove(zkeyFilename, wtnsFilename); const proofA = [proof.pi_a[0], proof.pi_a[1]]; - const proofB = [[proof.pi_b[0][1], proof.pi_b[0][0]], [proof.pi_b[1][1], proof.pi_b[1][0]]]; + const proofB = [proof.pi_b[0], proof.pi_b[1]]; const proofC = [proof.pi_c[0], proof.pi_c[1]]; // Generate groth16 verifier solidity file from groth16 template + zkey @@ -118,7 +118,7 @@ describe("Smart contracts test suite", function () { const { proof: proof, publicSignals: publicInputs } = await snarkjs.groth16.prove(zkeyFilename, wtnsFilename); const proofA = [proof.pi_a[0], proof.pi_a[1]]; - const proofB = [[proof.pi_b[0][1], proof.pi_b[0][0]], [proof.pi_b[1][1], proof.pi_b[1][0]],]; + const proofB = [proof.pi_b[0], proof.pi_b[1]]; const proofC = [proof.pi_c[0], proof.pi_c[1]]; // Generate groth16 verifier solidity file from groth16 template + zkey diff --git a/src/groth16_exportsoliditycalldata.js b/src/groth16_exportsoliditycalldata.js index 754670aa..1b897303 100644 --- a/src/groth16_exportsoliditycalldata.js +++ b/src/groth16_exportsoliditycalldata.js @@ -38,7 +38,7 @@ export default async function groth16ExportSolidityCallData(_proof, _pub) { let S; S=`[${p256(proof.pi_a[0])}, ${p256(proof.pi_a[1])}],` + - `[[${p256(proof.pi_b[0][1])}, ${p256(proof.pi_b[0][0])}],[${p256(proof.pi_b[1][1])}, ${p256(proof.pi_b[1][0])}]],` + + `[[${p256(proof.pi_b[0][0])}, ${p256(proof.pi_b[0][1])}],[${p256(proof.pi_b[1][0])}, ${p256(proof.pi_b[1][1])}]],` + `[${p256(proof.pi_c[0])}, ${p256(proof.pi_c[1])}],` + `[${inputs}]`; diff --git a/templates/verifier_groth16.sol.ejs b/templates/verifier_groth16.sol.ejs index fda549e3..1b33982c 100644 --- a/templates/verifier_groth16.sol.ejs +++ b/templates/verifier_groth16.sol.ejs @@ -104,11 +104,11 @@ contract Groth16Verifier { mstore(_pPairing, calldataload(pA)) mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) - // B - mstore(add(_pPairing, 64), calldataload(pB)) - mstore(add(_pPairing, 96), calldataload(add(pB, 32))) - mstore(add(_pPairing, 128), calldataload(add(pB, 64))) - mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + // B (snarkjs pi_b = [x_re, x_im, y_re, y_im]; EIP-197 expects [x_im, x_re, y_im, y_re]) + mstore(add(_pPairing, 64), calldataload(add(pB, 32))) + mstore(add(_pPairing, 96), calldataload(pB)) + mstore(add(_pPairing, 128), calldataload(add(pB, 96))) + mstore(add(_pPairing, 160), calldataload(add(pB, 64))) // alpha1 mstore(add(_pPairing, 192), alphax)