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:
-
Witness calculator. circom-2 emits a
.wasmmodule. circom-1 emits JavaScript embedded in the circuit JSON — template functionseval-ed against actxhost object (setSignal/getSignal/callFunction/…) with abig-integer-style API. There is no.wasm; running it needs a JS engine. -
FFT domain generator. Setup places the R1CS constraints on the powers of a root of unity ω; the prover must interpolate the quotient polynomial
Hon 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-domainH→ invalid proof. The generator is not stored in the key. -
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 .binMontgomery, little-endian (0, 1)(c0, c1)modern .zkeyMontgomery, binary framed (0, 0)(c0, c1)…plus the Ethereum G2 Fp2 word swap in proof/VK byte layouts.
-
QAP reduction.
LibsnarkReduction(arkworks native),CircomReduction(snarkjs modern.zkey), and websnark'sH = top-N coefficients of A·Bon the g-domain are three differentHlayouts for the same circuit.
How groth16py replicates each
| To replicate | How | API |
|---|---|---|
| circom-2 compile + witness | circom compiler crates → .r1cs + .wasm → wasmi 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 /
maturinbundle thenativefeature (circom-2 compiler +wasmi+boa) and the vendored circomlibs/sources, sopip installusers get in-processsetup_circom/compile_legacy/prove/prove_legacywith 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) — canonicalverifyProof(uint[2], uint[2][2], uint[2], uint[N]), matchingsnarkjs zkey export solidityverifier. Use for fresh deployments.style="bytes"— websnark/tornadoverifyProof(bytes, uint256[N]), decodinguint256[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_circomare single-party and leak toxic waste (dev-grade only); production keys come from an external ceremony imported viaimport_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:
wasmifor circom-2, andboafor circom-1 (~3–4 s for the 28k-constraintwithdrawcircuit, 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_wtnswith an externally-computed witness is the escape hatch when latency matters. - Faster circom-1 compilation.
compile_legacyruns the wholecircom@0.0.35compiler under boa (~2–3 min forwithdraw), 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
bytesblob; a snarkjs-JSON proof +publicSignalsexport (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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa5a3609ceade8e8427c5d607e3bea33c1d4f773b5f29976172da5bfb8ffaeb0
|
|
| MD5 |
79f6019e3a8aec086ef5e52ead3b4054
|
|
| BLAKE2b-256 |
055fbec8f49b7de000ad0e75d24cb430cca9c65ec7872c68a5337695fa5e43fa
|
Provenance
The following attestation bundles were made for groth16py-0.1.0.tar.gz:
Publisher:
wheels.yml on luksgrin/groth16py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
groth16py-0.1.0.tar.gz -
Subject digest:
fa5a3609ceade8e8427c5d607e3bea33c1d4f773b5f29976172da5bfb8ffaeb0 - Sigstore transparency entry: 2150058557
- Sigstore integration time:
-
Permalink:
luksgrin/groth16py@2b29fa50198617ea9815f736efb08871f3fffc9a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/luksgrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@2b29fa50198617ea9815f736efb08871f3fffc9a -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6caa4e3838cb065c995db9250e8d2a192fc2fdacef4618eb8bac0ce5765046ee
|
|
| MD5 |
c94a27e1f8b1023309f1739e44c052fa
|
|
| BLAKE2b-256 |
67c19401dd6bd38b98a6e989db02e63f29d048c4a63befb710cac97698f2eaa9
|
Provenance
The following attestation bundles were made for groth16py-0.1.0-cp39-abi3-win_amd64.whl:
Publisher:
wheels.yml on luksgrin/groth16py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
groth16py-0.1.0-cp39-abi3-win_amd64.whl -
Subject digest:
6caa4e3838cb065c995db9250e8d2a192fc2fdacef4618eb8bac0ce5765046ee - Sigstore transparency entry: 2150060022
- Sigstore integration time:
-
Permalink:
luksgrin/groth16py@2b29fa50198617ea9815f736efb08871f3fffc9a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/luksgrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@2b29fa50198617ea9815f736efb08871f3fffc9a -
Trigger Event:
push
-
Statement type:
File details
Details for the file groth16py-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: groth16py-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 7.7 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee9334af580e7a0db319c2521f5136d2c84ebb1ed6937b0cc967fce6cd2a7c0f
|
|
| MD5 |
82c0c2936120319b968618d379f42699
|
|
| BLAKE2b-256 |
f8c479bd6a890c54497b852e757f56d780dc5ca035692e2bd39b6554529296e3
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
groth16py-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
ee9334af580e7a0db319c2521f5136d2c84ebb1ed6937b0cc967fce6cd2a7c0f - Sigstore transparency entry: 2150060453
- Sigstore integration time:
-
Permalink:
luksgrin/groth16py@2b29fa50198617ea9815f736efb08871f3fffc9a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/luksgrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@2b29fa50198617ea9815f736efb08871f3fffc9a -
Trigger Event:
push
-
Statement type:
File details
Details for the file groth16py-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: groth16py-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 7.2 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a0141e334986d911a7c4c21b1ab8eddd77ad2c4821f4daf6bc9030dcbdcf126
|
|
| MD5 |
706445dbb58d6d167048aac57b9cab35
|
|
| BLAKE2b-256 |
5c4444f7adcbf214105354640c256d38f467dedc8d7d3cc5cb9caf7571a0680e
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
groth16py-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
0a0141e334986d911a7c4c21b1ab8eddd77ad2c4821f4daf6bc9030dcbdcf126 - Sigstore transparency entry: 2150059119
- Sigstore integration time:
-
Permalink:
luksgrin/groth16py@2b29fa50198617ea9815f736efb08871f3fffc9a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/luksgrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@2b29fa50198617ea9815f736efb08871f3fffc9a -
Trigger Event:
push
-
Statement type:
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
- Download URL: groth16py-0.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
- Upload date:
- Size: 13.1 MB
- Tags: CPython 3.9+, macOS 10.12+ universal2 (ARM64, x86-64), macOS 10.12+ x86-64, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18d070c4415809ac61549295c7d95971b2b3b46dacd1709275c364ab91a47d42
|
|
| MD5 |
fd5cea3992ffb8addb701bf9ec7b9b37
|
|
| BLAKE2b-256 |
bce9e13953b73d53d504ea66d69c8a96033809dba3b23bb64ea7c01cfb6049c8
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
groth16py-0.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl -
Subject digest:
18d070c4415809ac61549295c7d95971b2b3b46dacd1709275c364ab91a47d42 - Sigstore transparency entry: 2150060706
- Sigstore integration time:
-
Permalink:
luksgrin/groth16py@2b29fa50198617ea9815f736efb08871f3fffc9a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/luksgrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@2b29fa50198617ea9815f736efb08871f3fffc9a -
Trigger Event:
push
-
Statement type: