Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#
# snarkjs -- development, build and test via make.
#
# Getting all three test layers running (Node/mocha, Hardhat, Foundry)
# takes several tools; these targets make each one a single command, and
# the nix-% pattern makes the whole thing zero-setup on any machine with
# Nix -- the flake supplies node, circom, forge and a pinned solc:
#
# make nix-test # unit tests, no local toolchain needed
# make nix-test-smart-contracts # Hardhat on-chain tests
# make nix-test-forge # Foundry on-chain tests
# make nix-test-all # everything
#
# Without Nix, install the tools yourself and drop the nix- prefix.
#

SHELL = /bin/bash

# ── Top-level targets ─────────────────────────────────────────────────

.PHONY: all install build test test-file test-grep test-smart-contracts
.PHONY: test-forge test-forge-all test-all clean clean-forge distclean
.PHONY: circuits circuit-groth16 circuit-circuit2 circuit-plonk circuit-fflonk
.PHONY: verifier-preview 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 the Powers of Tau ceremony,
# Groth16 / PLONK / FFLONK prove + off-chain verify, polynomial
# operations, and 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 -- including a test that feeds
# `exportSolidityCallData` output straight into the exported verifier,
# pinning the calldata ABI end-to-end.
test-smart-contracts:
cd smart_contract_tests && npm test

# On-chain verification of an exported Groth16 verifier under forge.
#
# Generates a zkey + proof from a bundled test circuit, exports the
# Solidity verifier, writes a self-contained forge test with the proof
# embedded -- packed with the same EIP-197 _pB coordinate swap that
# 'snarkjs zkey export soliditycalldata' performs -- and runs
# 'forge test'. Forge's revm enforces the EIP-197 point encodings
# strictly, so this catches calldata-packing mistakes that off-chain
# `groth16 verify` cannot see (it never crosses the EVM ABI).
#
# Self-contained: no submodules, no forge-std, no network access.
# Auto-skips with exit code 0 if forge is not installed.
#
# make test-forge # test/groth16 circuit
# make test-forge-all # test/groth16 + test/circuit2
# make test-forge CIRCUIT_DIR=test/circuit2 # any bundled Groth16 circuit
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: unit + Hardhat + forge.
test-all: test test-smart-contracts test-forge-all

# ── Test circuits (circom -> R1CS / WASM) ─────────────────────────────

# Most test circuits are checked in pre-compiled. Use these targets to
# recompile after editing a .circom file.

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 preview ─────────────────────────────────────────

# Dry-run: generate a Groth16 verifier from the template + a fresh zkey
# and print it to stdout. Useful for inspecting template changes.
#
# make verifier-preview
PTAU_FILE ?= test/plonk_circuit/powersOfTau15_final.ptau
R1CS_FILE ?= test/groth16/circuit.r1cs

verifier-preview:
@npx snarkjs zkey new $(R1CS_FILE) $(PTAU_FILE) /tmp/verifier_preview.zkey 2>/dev/null
@npx 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: clean-forge
rm -rf build cache
rm -rf smart_contract_tests/artifacts smart_contract_tests/cache smart_contract_tests/contracts

# Remove the generated forge project artifacts (verifier, test, build output).
clean-forge:
rm -rf forge_test/src forge_test/test forge_test/out forge_test/cache forge_test/build forge_test/lib

distclean: clean
rm -rf node_modules smart_contract_tests/node_modules

# ── Nix ───────────────────────────────────────────────────────────────

# Run any target inside the flake's dev shell, which supplies node,
# circom, forge and a pinned solc (so forge never downloads a compiler):
#
# make nix-test
# make nix-test-smart-contracts
# make nix-test-forge
# make nix-test-all
nix-%:
nix develop $(NIX_OPTS) --command make $*

# Print any make variable, e.g. `make print-PTAU_FILE`.
print-%:
@echo $* = "'$($*)'"
@echo $*\'s origin is $(origin $*)

FORCE:
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,59 @@ await wtnsCalculate(input, wasmFile, wtns, {memorySize: 0});



## Building and testing with make (and Nix)

Getting all three test layers running -- Node/mocha unit tests, Hardhat
on-chain tests, and Foundry on-chain tests -- takes several tools. The
Makefile turns each into one command:

| target | what it runs |
| -------------------------- | --------------------------------------------------- |
| `make install` | `npm install` here and in `smart_contract_tests/` |
| `make build` | `npm run build` |
| `make test` | mocha unit/integration suite |
| `make test-smart-contracts`| Hardhat: export a verifier, deploy, `verifyProof()` |
| `make test-forge` | Foundry: same idea on forge's revm (see below) |
| `make test-all` | all three |
| `make test-file FILE=...` | a single mocha file |
| `make test-grep GREP=...` | mocha by pattern |
| `make circuits` | recompile the bundled test circuits after edits |
| `make verifier-preview` | print a freshly generated Groth16 verifier |
| `make clean` / `distclean` | build artifacts / also node_modules |

With Nix installed, no other setup is needed: prefix any target with
`nix-` and the flake supplies the whole toolchain (node, circom, forge,
and a pinned solc, so forge never downloads a compiler):

```sh
make nix-test # unit tests, zero local toolchain
make nix-test-smart-contracts # Hardhat on-chain tests
make nix-test-forge # Foundry on-chain tests
make nix-test-all # everything
```

or enter the shell once and use the plain targets: `nix develop`.

### Testing exported verifiers on-chain with Foundry

`make test-forge` proves a bundled test circuit, exports the Solidity
verifier, and runs it on a real EVM via `forge test`. The generated test
feeds `verifyProof` the proof with the same EIP-197 `_pB` coordinate swap
that `snarkjs zkey export soliditycalldata` performs, and checks that an
aliased public input is rejected. It is self-contained -- no submodules,
no forge-std, no network access -- and auto-skips (exit 0) when `forge`
is not installed.

```sh
make test-forge # test/groth16 circuit
make test-forge-all # test/groth16 and test/circuit2
make test-forge CIRCUIT_DIR=test/circuit2 # any bundled Groth16 circuit
```

Forge's revm enforces the EIP-197 point encodings strictly, so this layer
catches calldata-packing mistakes that off-chain `groth16 verify` cannot
see -- it never crosses the EVM ABI.

## 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
Expand Down
61 changes: 61 additions & 0 deletions flake.lock

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

62 changes: 62 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
description = "snarkjs development shell -- node, circom, foundry (forge), solc";

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};
in {
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# Core tools
cacert
git
gh
gnumake
bash
jq

# Node.js -- snarkjs runtime, build, and test driver
nodejs_22

# circom -- recompile the .circom test circuits if edited
circom

# Foundry -- forge (build/test), anvil (local node), cast (CLI)
foundry

# solc -- pinned compiler for forge via FOUNDRY_SOLC below, so
# 'make test-forge' never downloads a compiler (no network access)
solc
];
shellHook = ''
# Use the Nix-provided solc so forge does not fetch one via svm.
export FOUNDRY_SOLC="${pkgs.solc}/bin/solc"

echo "snarkjs development environment"
printf " %-8s %s\n" "node" "$(node --version 2>/dev/null)"
printf " %-8s %s\n" "circom" "$(circom --version 2>/dev/null | head -1)"
printf " %-8s %s\n" "forge" "$(forge --version 2>/dev/null | head -1)"
printf " %-8s %s\n" "solc" "$(solc --version 2>/dev/null | tail -1)"

# First-time setup: install the npm dev dependencies.
if [ -f package.json ] && [ ! -d node_modules ]; then
echo "[flake] installing npm dev deps ..."
npm install --no-audit --no-fund --loglevel=error
fi
if [ -f smart_contract_tests/package.json ] && [ ! -d smart_contract_tests/node_modules ]; then
echo "[flake] installing smart_contract_tests npm deps ..."
( cd smart_contract_tests && npm install --no-audit --no-fund --loglevel=error )
fi

echo ""
echo "Tests: npm test | make test-forge | make test-forge-all | (cd smart_contract_tests && npm test)"
'';
};
});
}
8 changes: 8 additions & 0 deletions forge_test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Everything under src/ and test/ is generated by
# scripts/forge_verify_test.sh; the rest by forge itself.
src/
test/
out/
cache/
build/
lib/
18 changes: 18 additions & 0 deletions forge_test/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[profile.default]
src = "src"
out = "out"
test = "test"
evm_version = "istanbul"
optimizer = true
optimizer_runs = 999999

# No dependencies: the generated test imports only the exported verifier,
# so there is no lib/, no remappings, and nothing to `forge install`.
#
# No solc pin: forge selects a compiler satisfying the exported verifier's
# pragma (>=0.7.0 <0.9.0). Inside the Nix dev shell, FOUNDRY_SOLC points
# at the Nix-provided solc, so no compiler download is needed.
#
# The verifier calls the bn254 precompiles (ecAdd/ecMul/ecPairing at
# 0x06/0x07/0x08), available on all post-Byzantium EVMs; evm_version
# istanbul keeps the bytecode PUSH0-free for older EVMs.
Loading