zorch_12

groth16-zorch

snark1

Groth16 prover in Python using zorch — the Python counterpart of RabbitSNARK. Input/output compatible with the circom/snarkjs ecosystem; also proves from gnark binary exports.

Run in Playground ↗GitHub ↗

Quickstart

# Groth16 — prove a circom circuit on the GPU, then verify the proof.
#
# groth16-zorch takes circom/snarkjs artifacts as they are: the proving key is
# the same .zkey snarkjs proves with, and the proof serializes straight back to
# snarkjs-compatible JSON. Az/Bz are evaluated in pure frx over the BN254 field
# dtype, so the R1CS mat-vec runs on the GPU alongside the five MSMs — no native
# library involved.
#
# multiplier_3 proves knowledge of a, b, c behind the public product a*b*c.
# Change A, B, C below and re-run: the witness — the six wires
# [1, out, a, b, c, a*b] — is built straight from your inputs, so no witness
# calculator or .wtns file is needed for this circuit. The .zkey is
# trusted-setup output, so it stays inlined at the bottom. Pass no_zk=True for
# the deterministic (r = s = 0) proof.
import base64
import gzip
import pathlib
import tempfile

import numpy as np
from zk_dtypes import bn254_sf, bn254_sf_mont

from groth16_zorch.circom.zkey import parse_zkey
from groth16_zorch.circom.zkey_to_terms import zkey_to_terms
from groth16_zorch.groth16 import compile_circom, write_public_signals
from groth16_zorch.groth16.verifier import VerificationKey, verify
from groth16_zorch.r1cs import compute_abc

A, B, C = 3, 4, 5  # the private inputs — edit and re-run


def multiplier_3_witness(a, b, c):
    """Wires for out === a*b*c: [1, out, a, b, c, inter=a*b]."""
    wires = [1, a * b * c, a, b, c, a * b]
    return np.array(wires, dtype=bn254_sf), wires


def main():
    zkey_path = pathlib.Path(tempfile.mkdtemp()) / "multiplier_3.zkey"
    zkey_path.write_bytes(gzip.decompress(base64.b64decode(ZKEY_GZ)))
    zkey = parse_zkey(zkey_path)

    witnesses, witness_ints = multiplier_3_witness(A, B, C)

    compiled = compile_circom(zkey)  # NTT twiddles + point arrays, once
    _, coefficients = zkey_to_terms(zkey)
    az, bz = compute_abc(  # R1CS mat-vec for Az/Bz, on the GPU
        witnesses.view(np.dtype(bn254_sf_mont)),
        compiled.terms,
        coefficients,
        compiled.domain_size,
    )

    signals = write_public_signals(witness_ints, compiled.config.num_public)
    proof, public_signals = compiled.prove(witnesses, az, bz, signals)

    vk = VerificationKey.from_zkey(zkey)
    snarkjs = proof.to_json()
    print("verifier accepts:", verify(vk, proof, public_signals))
    print("public output:", public_signals[0], "=", A, "*", B, "*", C)
    print("proof:", snarkjs["protocol"], "over", snarkjs["curve"])
    print("pi_a.x:", snarkjs["pi_a"][0])


# circom multiplier_3 proving key — the .zkey snarkjs proves with, gzip+base64.
ZKEY_GZ = """
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"""

main()

groth16-zorch

A Groth16 prover implementation in Python using Zorch. This is the Python counterpart of RabbitSNARK, which implements the Groth16 proving scheme using HLO via PrimeIR and ZKX.

ProjectLanguageBackend
rapidsnarkC++Native
RabbitSNARKC++HLO (ZKIR/ZKX)
groth16-zorchPythonFRX

Features

  • Parse circom proving key (.zkey) files
  • Circom witness from a pre-computed .wtns file (e.g. produced by snarkjs)
  • Load gnark binary exports for circuit proving
  • Az/Bz evaluated in pure FRX (frx.ops.segment_sum over the BN254 field dtype), so they run on the GPU alongside the prover — no native library needed
  • Groth16 proof generation with snarkjs-compatible JSON output

Installation

Python 3.11 on Linux x86_64, or macOS on Apple Silicon. (frxlib ships a cp311 wheel for those two platforms only — not 3.12/3.13, not Intel Macs.)

CPU

pip install groth16-zorch

GPU (CUDA 12)

pip install groth16-zorch 'frx[cuda12]' \
    --extra-index-url https://fractalyze.github.io/pypi/simple/

The extra index carries the CUDA plugin wheels, which are too large for PyPI's per-file limit. It is not needed for the CPU tier.

Verify

python -c "import frx, groth16_zorch; print(frx.devices()); print(groth16_zorch.__version__)"

[CpuDevice(id=0)] means the CPU tier; a CUDA install prints the GPU devices.

How to build

  1. Clone the repository

    git clone https://github.com/fractalyze/groth16-zorch.git
    
  2. Navigate to the project directory

    cd groth16-zorch
    
  3. Install the package

    pip install -e .
    

    For development with test dependencies:

    pip install -e ".[dev]"
    

How to run

CLI

Circom

# Witness is a pre-computed .wtns (e.g. `snarkjs wtns calculate`)
groth16-zorch circom prove <circuit.zkey> <witness.wtns> <proof.json> <public.json>

groth16-zorch circom verify <vkey.json> <public.json> <proof.json>

Gnark

groth16-zorch gnark prove <export_dir> <proof.json> <public.json> [--no-zk] [--deterministic]
groth16-zorch gnark verify <export_dir> <public.json> <proof.json>

Python API

Circom (.zkey + .wtns)

import numpy as np
from zk_dtypes import bn254_sf_mont

from groth16_zorch.circom.wtns import parse_wtns
from groth16_zorch.circom.zkey import parse_zkey
from groth16_zorch.circom.zkey_to_terms import zkey_to_terms
from groth16_zorch.groth16 import compile_circom, write_public_signals
from groth16_zorch.r1cs import compute_abc

zkey = parse_zkey("path/to/circuit.zkey")
compiled = compile_circom(zkey)  # one-time: parse zkey, build term matrices + arrays

wtns = parse_wtns("path/to/circuit.wtns")
witness_mont = wtns.data._witnesses.view(np.dtype(bn254_sf_mont))
_terms, coefficients = zkey_to_terms(zkey)
az_mont, bz_mont = compute_abc(
    witness_mont, compiled.terms, coefficients, compiled.domain_size
)
z_std = wtns.data._witnesses
public_signals = write_public_signals(wtns.witnesses, compiled.config.num_public)
proof, public_signals = compiled.prove(z_std, az_mont, bz_mont, public_signals)

Gnark binary export

import numpy as np
from frx import lax
from zk_dtypes import bn254_sf, bn254_sf_mont

from groth16_zorch.gnark import load_gnark_export
from groth16_zorch.groth16 import compile_gnark

# The export carries the solved witness and Az/Bz (solution_a/b), both
# produced by gnark's Go solver — nothing is recomputed here.
data = load_gnark_export("path/to/export/")
compiled = compile_gnark(data)

z_std = np.asarray(
    lax.convert_element_type(data.witness_full.view(bn254_sf_mont), bn254_sf)
)
public_signals = [str(int(z_std[i])) for i in range(compiled.config.num_public)]
proof, public_signals = compiled.prove(
    z_std, data.az_mont, data.bz_mont, public_signals
)

The output proof.json and public.json follow the same JSON schema as snarkjs, so you can verify with:

snarkjs groth16 verify verification_key.json public.json proof.json

Compatibility

Circom / snarkjs

groth16-zorch is input/output compatible with the circom/snarkjs ecosystem:

snarkjsrapidsnarkgroth16-zorch
Input .zkeyyesyesyes
Input .wtnsyesyesyes
Output proof.jsonsnarkjs formatsnarkjs formatsnarkjs format
Verify with snarkjs groth16 verifyyesyesyes

Gnark

groth16-zorch loads binary exports produced by a gnark Go program (see tests/gnark/gen_fixture for a minimal example). The export must include witness_full.bin and solution_a/b.bin — the witness and Az/Bz that gnark's r1csTyped.Solve computes natively:

gnark (Go)groth16-zorch
Binary export (metadata.json + *.bin)producesconsumes
Proving key pointssetuploaded from export
Witness + Az/BzGo solverloaded from export
Proof generationgroth16.Prove()compiled.prove()
Verificationgroth16.Verify()verify(vk, proof, signals)

Benchmark

Prove time for SP1's final Groth16 verifier circuit — BN254, 15,965,950 constraints, domain 2²⁴ — on an RTX 5090. The reference is gnark's ICICLE GPU Groth16 prover on the same circuit and GPU. groth16-zorch's proof verifys and is deterministic (fixed output across runs).

Both provers consume the same gnark export, which already carries the solved witness and Az/Bz (gnark's Go solver produces them at export time). So this is a prove-only comparison — it excludes witness solving on both sides:

proverprove (median)speedup
groth16-zorch (FRX, GPU)1573 ms1.50×
gnark ICICLE (GPU)2355 ms1.00×

gnark's end-to-end run additionally re-solves the witness on every proof (~2.2 s), for ~4.5 s total; groth16-zorch loads that pre-solved witness/Az/Bz straight from the export. One-time setup for groth16-zorch (not counted in the prove time): ~4.4 s to load the 19 GB export and ~5.0 s to compile the 2²⁴ executable.

Reproduce with //benchmark:sp1_groth16 (see .github/workflows/benchmark.yml):

FRX_PLATFORMS=cuda,cpu bazel run //benchmark:sp1_groth16 -- \
    --export_dir=<sp1-groth16-export> --deterministic --circuit=sp1

How to test

bazel test //...

Test organization:

  • //tests/circom:e2e_test — Circom prove/verify via .wtns
  • //tests/gnark:e2e_test — Gnark export prove/verify
  • //tests/circom:zkey_test.zkey parser unit tests
  • //tests/circom:wtns_test.wtns parser unit tests

License

Licensed under the Apache License, Version 2.0 (see LICENSE).