Skip to main content

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)
Verifier export Solidity and Vyper (0.4.x), general over any BN254 VK; canonical snarkjs (a,b,c) or websnark bytes ABI; reproduces a deployed verifier's constants byte-for-byte; both anvil-verified against the same proof
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.

Verifier export — Solidity & Vyper

The emitters are template substitutions, not reimplementations, and are general over any BN254 Groth16 VK (nPublic, IC length, and constants are all parameterized). Two languages, two ABI styles each:

vk.to_solidity(style="snarkjs")   # or "bytes"        → Solidity (default pragma ^0.8.29)
vk.to_vyper(style="snarkjs")      # or "bytes"        → Vyper 0.4.x (default pragma ~=0.4.0)
  • style="snarkjs" (default) — canonical verifyProof(a, b, c, input), matching snarkjs zkey export solidityverifier. Use for fresh deployments.
  • style="bytes" — websnark/tornado verifyProof(bytes, input), decoding uint256[8] with the Fp2 word order. Reproduces a deployed tornado verifier's constants byte-for-byte.

The Vyper emitter targets 0.4.x only (~=0.4.0): G1 add/mul use the ecadd/ecmul builtins (alt_bn128 precompiles 6/7) and the pairing calls precompile 8 via raw_call. It emits the same VK constants and (c1, c0) G2 order as the Solidity emitter, so one proof verifies across both — validated on anvil (genuine → true, tampered → false) for all four combinations. (contract_name is cosmetic in Vyper — a .vy module is named by its filename — so it appears only in the header comment.)

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.

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.2.0.tar.gz (1.6 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.2.0-cp39-abi3-win_amd64.whl (8.0 MB view details)

Uploaded CPython 3.9+Windows x86-64

groth16py-0.2.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.2.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.2.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.2.0.tar.gz.

File metadata

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

File hashes

Hashes for groth16py-0.2.0.tar.gz
Algorithm Hash digest
SHA256 d815a9e92396d7b1cd6a6f936b38c361e4f8d53dd4a110c19cba05a53fcfcd8d
MD5 50384b02fadcfe3c01227320819317f5
BLAKE2b-256 1335ee51e02f14d27bcdf32ef69745630a2956b460611b6bdc2b6129f8214a72

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.2.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.2.0-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: groth16py-0.2.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.2.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 b8a6000b380e97bc6e33ab523ea0398d35c7bbd1f5010789060bac8c90a427cd
MD5 ec9481cac3552c0975a4114089b8d529
BLAKE2b-256 5adaf05784debe95cf8443608aa4c93ff05bfdc8396c2f703b3d9bf075f84176

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.2.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.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for groth16py-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 704b4a25a32d8d440127cd3b1fc456ad12a4417388623ce832329b70db0b9d1a
MD5 dbe30eadc80803b5cb830ed8da39e7df
BLAKE2b-256 84109f9a11532505a08261b0f5aa01e6b0364c5fe3d7f649f689fa78ee65210e

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.2.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.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for groth16py-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 87c7e7f0852e949293938472c3a2b4c8c382c091e67ef7311f52d7f3c12d2018
MD5 5455256898f032bae8bdebb6694f143c
BLAKE2b-256 d2892deabc20e538677f232a99031ef3d8a8d276c47eb1eb57ffbc800e2e35a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.2.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.2.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.2.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 5851912a4c200237a147d4bbf6f6a98ad694068f10a745fcb3d412b373aa135b
MD5 9b3dbc51348ef720e9f4dd94fb1a3e66
BLAKE2b-256 9c02056951a33f4e5ab1c6afce94b332e1b6161b4b64087d0ea02abad9544193

See more details on using hashes here.

Provenance

The following attestation bundles were made for groth16py-0.2.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.

Release history Release notifications | RSS feed

This release

0.2.0 This release

5 files

0.1.0

5 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page