Skip to main content

arkworks-backed Python library for BN254 Groth16: setup, witness, prove, verify, Solidity verifier export — no Node.js

Project description

groth16py

A general, arkworks-backed Python library for the BN254 Groth16 lifecycle — setup, witness generation, prove, verify, and Solidity verifier export in pure Rust.

Not tied to any one circuit: the number of public inputs, the VK, and the circuit inputs are all general. It spans both Groth16 generations — the modern circom-2 / snarkjs-ceremony stack and the legacy circom-1 / snarkjs-0.1.x / websnark stack — so it can generate a proof that an already-deployed legacy verifier accepts, with no redeploy and no Node. The tornado withdraw circuit is the driving example.

Almost every capability maps to an existing arkworks / circom crate; this repo is glue — PyO3 bindings, encoding adapters, and a Solidity template. The one deliberate exception is legacy proving, where no maintained crate exists (see the toolchain landscape).

Features

Verify in-process, pairing-only (no witness, no WASM); Ethereum bytes or snarkjs JSON proof layouts
Compile both generations in-process — circom-2 (compiler crates) and circom-1 (circom@0.0.35 run in a pure-Rust JS engine, wire-identical to the original); circomlib + sources vendored
Prove (modern) fully in-process from .circom — circom-2 compiler + wasmi, no wasmer/protoc/Node
Prove (legacy) import a websnark .bin or snarkjs-0.1.x .json key and produce proofs a deployed verifier accepts; circom-1 witness runs in a pure-Rust JS engine — no Node
Setup single-party from .r1cs/.circom (labelled unsafe), or import a snarkjs ceremony .zkey (production path)
Solidity export general over any BN254 VK; canonical snarkjs (a,b,c) or websnark bytes ABI; reproduces a deployed verifier's constants byte-for-byte; anvil-verified
Pure Rust no wasmer, no protoc, no clang; abi3 wheels install with no toolchain

Install

pip install groth16py     # abi3 wheels — no Rust, no Node, no build tools

Quick start

A complete prove-and-verify, start to finish: prove you know two factors a, b of a public product c, with the whole circom → witness → proof pipeline running in-process.

import groth16py

# 1. A tiny circuit: c = a * b, with c the public output.
with open("mul.circom", "w") as f:
    f.write("""
pragma circom 2.0.0;
template Mul() {
    signal input a;
    signal input b;
    signal output c;
    c <== a * b;
}
component main = Mul();
""")

# 2. Setup — compiles the circuit in-process (single-party; UNSAFE for mainnet funds).
pk, vk = groth16py.setup_circom("mul.circom")

# 3. Prove a=3, b=4 — the witness is computed in-process by running the circuit.
proof = groth16py.prove(pk, {"a": 3, "b": 4})       # -> Ethereum `bytes` proof

# 4. Verify against the public output c = 12.
verifier = groth16py.Verifier(vk)
assert verifier.verify(proof, [12]) is True
assert verifier.verify(proof, [13]) is False

# 5. Emit a Solidity verifier for on-chain verification.
with open("Verifier.sol", "w") as f:
    f.write(vk.to_solidity())

Other entry points for the common flows:

# Verify existing snarkjs/websnark proofs — no circuit needed:
vk = groth16py.VerifyingKey.from_snarkjs_file("verification_key.json")
groth16py.Verifier(vk).verify(proof_bytes, public_inputs)

# Prove against a trusted-setup ceremony key (modern production path):
pk, vk = groth16py.import_zkey("circuit.zkey", "circuit.circom")
proof = groth16py.prove(pk, inputs)

# Prove from a precomputed witness produced by any tool:
proof = groth16py.prove_wtns(pk, "circuit.wtns")

The toolchain landscape

Groth16 tooling comes in two generations that are wire-incompatible: a proof verifies only if the proving key, the witness, and the prover's FFT domain all agree. groth16py implements both generations and bridges the encodings, so you can meet an existing deployment where it is.

The tools

Tool Gen Role Artifact / convention
circom-1 (circom@0.0.35, Node) legacy circuit compiler one JSON (e.g. withdraw.json): R1CS + templates as eval-able JS witness code
circom-2 (iden3/circom, Rust) modern circuit compiler .r1cs + a .wasm witness calculator
snarkjs 0.1.x ("groth", Node) legacy setup / prove / verify / Solidity proving-key JSON (polsA/B/C, A/B1/B2/C, hExps, vk_*); FFT generator g=5
websnark (browser, WASM, Node) legacy prover binary proving key .bin (Montgomery); FFT generator g=7
snarkjs (modern) modern ceremony / prove / verify .zkey (Montgomery binary); CircomReduction QAP
arkworks the crypto core groth16py is built on native two-adic root of unity = generator g=5

The classic legacy pipeline (followed for example by Tornado): circom-1 compiles → snarkjs setup makes the proving/verification keys → websnark buildpkey re-encodes the PK JSON to .bin → the browser websnark prover proves → snarkjs generateverifier emits Verifier.sol.

The four axes that must line up

Get any one of these wrong and the pairing check fails:

  1. Witness calculator. circom-2 emits a .wasm module. circom-1 emits JavaScript embedded in the circuit JSON — template functions eval-ed against a ctx host object (setSignal/getSignal/callFunction/…) with a big-integer-style API. There is no .wasm; running it needs a JS engine.

  2. FFT domain generator. Setup places the R1CS constraints on the powers of a root of unity ω; the prover must interpolate the quotient polynomial H on the same ω. snarkjs 0.1.x and arkworks derive ω from generator g=5; websnark uses g=7 (buildFFT(…, 7)). A key made under one and proven under the other yields an off-domain H → invalid proof. The generator is not stored in the key.

  3. Proving-key encoding.

    field form point at infinity Fp2 order
    snarkjs 0.1.x JSON decimal, standard form z == 0 (projective [x, y, z]) (c0, c1)
    websnark .bin Montgomery, little-endian (0, 1) (c0, c1)
    modern .zkey Montgomery, binary framed (0, 0) (c0, c1)

    …plus the Ethereum G2 Fp2 word swap in proof/VK byte layouts.

  4. QAP reduction. LibsnarkReduction (arkworks native), CircomReduction (snarkjs modern .zkey), and websnark's H = top-N coefficients of A·B on the g-domain are three different H layouts for the same circuit.

How groth16py replicates each

To replicate How API
circom-2 compile + witness circom compiler crates → .r1cs + .wasmwasmi interpreter setup_circom, prove
circom-1 compile runs the actual circom@0.0.35 compiler in boa (pure-Rust JS engine, no Node) — output is wire-identical to the original, so it stays compatible with the deployed keys compile_legacy
circom-1 witness runs the eval-able templates in boa prove_legacy
modern .zkey proving CircomReduction import_zkey + prove
websnark / snarkjs-0.1.x proving a manual g=7/g=5 NTT Groth16 prover; the domain is auto-detected (build on each, keep the one that verifies against the key's own VK) import_websnark_pk / import_snarkjs_pk + prove_legacy / prove_wtns
all three key encodings encoding adapters normalize Montgomery↔standard, the infinity conventions, and the Fp2/word order into arkworks points the importers above
snarkjs / websnark verifier pure-arkworks pairing + both Solidity ABIs Verifier, to_solidity

Both circomlibs and the circuit sources are vendored in the wheel (circomlib_path(1|2), circuit_path(name, 1|2)), so either track runs from a bare pip install — no npm, no Node, no external checkout.

Recipes

Modern (Track 2) — fresh circuit, one deploy (vendored circom-2 circuit + circomlib, nothing external):

pk, vk = groth16py.setup_circom(groth16py.circuit_path("withdraw", 2),
                                [groth16py.circomlib_path(2)])   # or your own .circom / import_zkey(...)
proof  = groth16py.prove(pk, inputs)                             # circom-2 witness via wasmi
open("Verifier.sol", "w").write(vk.to_solidity(style="bytes"))  # deploy this, then verify on-chain

Legacy (Track 1) — target an already-deployed verifier, no redeploy, no Node, no committed artifacts:

# 1. Recompile the circom-1 circuit from source — wire-identical to circom@0.0.35, so it stays
#    compatible with the deployed keys (both circuit source + circomlib are vendored):
groth16py.compile_legacy(groth16py.circuit_path("withdraw"),   # circom-1 source
                         [groth16py.circomlib_path(1)],        # circom-1 circomlib
                         out="withdraw.json")
# 2. Import the deployed key (websnark .bin; a snarkjs-0.1.x .json works the same way):
pk, vk = groth16py.import_websnark_pk("withdraw_proving_key.bin",
                                      "withdraw_verification_key.json", "withdraw.json")
# 3. circom-1 witness in-process (boa), prove on the auto-detected FFT domain, verify:
proof = groth16py.prove_legacy(pk, "withdraw.json", inputs)    # inputs: {signal: int | nested[]}
assert groth16py.Verifier(vk).verify(proof, public_inputs)

Both flows are validated end-to-end on the Tornado withdraw circuit: Track 1's recompiled circuit is byte-identical (constraints + wire order) to the committed one and its proof verifies on-chain against the deployed Verifier.sol (anvil verifyProof == true); Track 2's proof verifies against a freshly emitted verifier.

This is validated end-to-end: a prove_legacy proof for the Tornado withdraw circuit verifies on-chain against the deployed Verifier.sol (anvil verifyProof == true; tampered inputs → false), with no Node.js anywhere. If you already have a legacy-order witness from any tool, use prove_wtns(pk, "withdraw.wtns") instead of prove_legacy.

Build tiers

  • Wheels / maturin bundle the native feature (circom-2 compiler + wasmi + boa) and the vendored circomlibs/sources, so pip install users get in-process setup_circom / compile_legacy / prove / prove_legacy with no build tools and nothing external. Building the wheel from source compiles the circom and boa crates (pure Rust; a few minutes, no protoc/clang).
  • cargo test / the crypto core use the default (no-native) features — encoding, verify, setup, prove_wtns, and the Solidity emitter are pure arkworks, GPL-tooling-free, and build in seconds.

Solidity verifier styles

The emitter is a template substitution, not a reimplementation, and is general over any BN254 Groth16 VK (nPublic, IC length, and constants are all parameterized):

  • style="snarkjs" (default) — canonical verifyProof(uint[2], uint[2][2], uint[2], uint[N]), matching snarkjs zkey export solidityverifier. Use for fresh deployments.
  • style="bytes" — websnark/tornado verifyProof(bytes, uint256[N]), decoding uint256[8] with the Fp2 word order. Reproduces a deployed tornado verifier's constants byte-for-byte.

Potential improvements

Two that would be genuinely fun to build:

  • Node-free trusted setup (ceremony). Today setup/setup_circom are single-party and leak toxic waste (dev-grade only); production keys come from an external ceremony imported via import_zkey. A pure-Rust phase-2 (Groth16) MPC contribution flow — accept/verify/append contributions, in-process, no Node/snarkjs — would let groth16py run a ceremony end to end, not just consume its output.
  • Seeded / reproducible setup. setup(..., seed=…) (a deterministic CSPRNG instead of system randomness) so a dev verifier can be regenerated bit-for-bit from source without shipping key blobs — handy for CI and reproducible builds. Explicitly dev-only: seeding real ceremony entropy would be a security footgun, and it does not make the deployed legacy keys reproducible (those need the original ceremony).

Smaller polish:

  • Faster / cached witness generation. Both witness backends are pure-Rust interpreters, so witness computation dominates large-circuit proving: wasmi for circom-2, and boa for circom-1 (~3–4 s for the 28k-constraint withdraw circuit, much of it re-parsing the ~19 MB circuit JSON on every call). Caching the parsed circuit / .r1cs / .wasm (or a JIT backend) would cut this; prove_wtns with an externally-computed witness is the escape hatch when latency matters.
  • Faster circom-1 compilation. compile_legacy runs the whole circom@0.0.35 compiler under boa (~2–3 min for withdraw), since boa is an interpreter. It's a one-time, off-the-hot-path step (compile once, reuse the .json), so this is low priority — but a faster JS engine or a native front-end would help.
  • Proof/VK interop formats. Proofs are emitted as the Ethereum bytes blob; a snarkjs-JSON proof + publicSignals export (and its inverse) would round out interop.
  • Ergonomics. Richer input types (hex/decimal strings, not just ints), a fully-typed inputs mapping in the stubs, and finer-grained error messages.

Security

Native setup is single-party, not a ceremony — never use its output for mainnet value; import ceremony VKs (or a legacy deployed key) for production. On-curve + correct-subgroup checks are enforced on all imported points. The proving RNG is seeded from a secure source, never a fixed seed. Legacy proving self-checks each candidate proof against the key's own VK before returning, so a domain/encoding mismatch fails closed rather than emitting a bad proof.

License

GPL-3.0. In-process witness generation links the circom compiler (GPL-3.0), and the legacy witness harness is derived from snarkjs 0.1.x (GPL-3.0), so the whole library is distributed under GPL-3.0 — see LICENSE. The emitted Solidity verifier templates carry their own MIT headers (Christian Reitwiessner / OKIMS) and are independent outputs.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

groth16py-0.1.0.tar.gz (1.5 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

groth16py-0.1.0-cp39-abi3-win_amd64.whl (8.0 MB view details)

Uploaded CPython 3.9+Windows x86-64

groth16py-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (7.7 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

groth16py-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (7.2 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

groth16py-0.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (13.1 MB view details)

Uploaded CPython 3.9+macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file groth16py-0.1.0.tar.gz.

File metadata

  • Download URL: groth16py-0.1.0.tar.gz
  • Upload date:
  • Size: 1.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for groth16py-0.1.0.tar.gz
Algorithm Hash digest
SHA256 fa5a3609ceade8e8427c5d607e3bea33c1d4f773b5f29976172da5bfb8ffaeb0
MD5 79f6019e3a8aec086ef5e52ead3b4054
BLAKE2b-256 055fbec8f49b7de000ad0e75d24cb430cca9c65ec7872c68a5337695fa5e43fa

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.1.0.tar.gz:

Publisher: wheels.yml on luksgrin/groth16py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file groth16py-0.1.0-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: groth16py-0.1.0-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 8.0 MB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for groth16py-0.1.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 6caa4e3838cb065c995db9250e8d2a192fc2fdacef4618eb8bac0ce5765046ee
MD5 c94a27e1f8b1023309f1739e44c052fa
BLAKE2b-256 67c19401dd6bd38b98a6e989db02e63f29d048c4a63befb710cac97698f2eaa9

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.1.0-cp39-abi3-win_amd64.whl:

Publisher: wheels.yml on luksgrin/groth16py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file groth16py-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for groth16py-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 ee9334af580e7a0db319c2521f5136d2c84ebb1ed6937b0cc967fce6cd2a7c0f
MD5 82c0c2936120319b968618d379f42699
BLAKE2b-256 f8c479bd6a890c54497b852e757f56d780dc5ca035692e2bd39b6554529296e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: wheels.yml on luksgrin/groth16py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file groth16py-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for groth16py-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 0a0141e334986d911a7c4c21b1ab8eddd77ad2c4821f4daf6bc9030dcbdcf126
MD5 706445dbb58d6d167048aac57b9cab35
BLAKE2b-256 5c4444f7adcbf214105354640c256d38f467dedc8d7d3cc5cb9caf7571a0680e

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: wheels.yml on luksgrin/groth16py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file groth16py-0.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for groth16py-0.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 18d070c4415809ac61549295c7d95971b2b3b46dacd1709275c364ab91a47d42
MD5 fd5cea3992ffb8addb701bf9ec7b9b37
BLAKE2b-256 bce9e13953b73d53d504ea66d69c8a96033809dba3b23bb64ea7c01cfb6049c8

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: wheels.yml on luksgrin/groth16py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page