Quickstart
# flock proves a binary-field R1CS over GF(2^128) in four stages over one
# Fiat-Shamir transcript: Ligerito commit+bind → zerocheck (the Hadamard gate
# a ∘ b = c) → lincheck (a = A·z, b = B·z) → batched Ligerito open. `prove_fast`
# fuses all four into one device call; `verify` is the dual — it replays the
# cheap sumcheck stages to recover the opening point, then checks the Ligerito
# opening. Below builds an identity R1CS (A = B = C = I, so a = b = c = z) entirely
# in Python — the witness packed by `pcs.pack`, no fixture — proves it, and
# verifies it. Every field multiply is a GF(2^128) carryless multiply on the GPU.
# Change M (>= 14) and re-run.
import numpy as np
import frx
frx.config.update("jax_enable_x64", True) # an F₂¹²⁸ element is two uint64 lanes
from flock_zorch import prover, verifier # noqa: E402
from flock_zorch.challenger import Challenger # noqa: E402
from flock_zorch.pcs.ligerito import commit_flock_ligerito # noqa: E402
from flock_zorch.pcs.pack import pack_witness, pack_z_lincheck_from_packed # noqa: E402
# 2^M R1CS rows; K_LOG = block size, K_SKIP = zerocheck skip. The zerocheck
# draws M - K_SKIP - 7 outer challenge coordinates and needs at least one.
M, K_LOG, K_SKIP = 14, 8, 6
DOMAIN = b"awesome-zorch-flock" # Fiat-Shamir domain, shared by prover and verifier
# The Ligerito recursive-open schedule: fold widths, code rate, query count, and
# grinding per level (initial commit + one recursive step).
CFG = dict(initial_k=2, recursive_ks=[2], log_inv_rates=[1, 2], queries=[4, 3],
grinding_bits=[1, 0], fold_grinding_bits=[1, 0], ood_samples=[0, 1],
recursive_steps=1)
# Any bit witness satisfies the identity gate z ∘ z = z (bit² = bit). Pack it into
# the two wire forms prove_fast reads: F₂¹²⁸ lanes (commit + zerocheck) and the
# lincheck bytes.
z_bits = np.random.default_rng(0).integers(0, 2, 1 << M).astype(np.uint8)
z_packed = pack_witness(z_bits, M)
z_lincheck = pack_z_lincheck_from_packed(z_packed, M, K_LOG)
eye = np.eye(1 << K_LOG, dtype=np.uint64) # A = B = I
statement = bytes(range(32)) # opaque R1CS instance digest
proof = prover.prove_fast(z_packed, M, K_LOG, K_SKIP, eye, eye,
z_lincheck, statement, CFG, domain=DOMAIN)
root = commit_flock_ligerito(CFG, z_packed)[0] # the commitment the verifier binds to
ok = verifier.verify(CFG, root, statement, proof, eye, eye, M, K_LOG, K_SKIP,
Challenger(DOMAIN))
print(f"R1CS proof over GF(2^128): 2^{M} rows, all four stages")
print(f" zerocheck : {len(proof.zerocheck.multilinear_rounds)} multilinear rounds")
print(f" lincheck : {len(proof.lincheck[0])} rounds")
print(f" PCS open : {len(proof.pcs_open.ring_switches)} ring-switched claims + Ligerito")
print("verifier accepts:", bool(ok))
flock-zorch
A GPU prover for flock's binary-field R1CS PIOP — the scheme from Flock: Fast Proving for Batch Boolean Computations (eprint 2026/1329) — built on Fractalyze's zorch stack. The whole prover is authored once in Python/FRX (Fractalyze's JAX fork), and the compiler emits the hardware code: the same readable source targets CPU and GPU, and its output matches the reference flock prover bit-for-bit.
The point is a single FRX/MLIR codebase, not a GPU rewrite of the proving
logic. flock's prover is written as a clean statement of the math; FRX lowers
it to StableHLO/MLIR, and the compiler — carrying native finite-field dtypes
(zk_dtypes) and the carryless-multiply lowerings for GF(2¹²⁸) — compiles that
down to each target. The expensive field-arithmetic optimization lives in
compiler passes, out of the prover, and the byte-match gate guarantees those
transforms never change the output. The same program can shard across multiple
devices (GSPMD) without hand-written communication.
flock is an R1CS-over-GF(2¹²⁸) prover: two sumcheck PIOPs (zerocheck + lincheck)
over a Ligerito polynomial commitment, with a SHA-256 Fiat-Shamir
transcript, targeting hash-circuit statements (Keccak-f[1600], Keccak3, SHA-256,
BLAKE3). flock-zorch assembles that specific prover from zorch's scheme-agnostic
blocks (the round protocols and drivers, Fiat-Shamir, Polynomial, PCS,
fold, zero-check) and adds
only the flock-specific pieces the byte-match needs (GHASH-basis field, the
round-1 URM, the ∞-trick round loop, F128↔bytes serialization). The full prover
prover.prove_fast produces the complete R1csProof — commit → bind →
zerocheck → lincheck → batched dual-claim open, one shared challenger,
device-resident — reproducing flock prove's proof bit-for-bit.
How the protocol is modelled — what a claim may state, which steps are roles
rather than functions, and what the tooling does not cover — is
docs/conventions.md.
How performance is measured — which numbers to trust, and the traps that have
each cost a session — is docs/measurement.md.
The rules every change must respect — the proof-level byte gates and the
porting/dtype/kernel gotchas — are
docs/development.md.
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.)
Run with JAX_ENABLE_X64=true — the GF(2¹²⁸) dtypes are 64-bit lane pairs and
x32 truncates them.
CPU
pip install flock-zorch
GPU (CUDA 12)
pip install flock-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
JAX_ENABLE_X64=true python -c \
"import frx, flock_zorch.prover; print(frx.devices()); print(flock_zorch.__version__)"
[CpuDevice(id=0)] means the CPU tier; a CUDA install prints the GPU devices.
Importing flock_zorch.prover rather than the package is deliberate: the package
__init__ is a docstring, so a bare import stays green on an x32 interpreter and
on a zk-dtypes too old for the binary-field dtypes.
Setup
Install the git hooks with both stages named. Plain pre-commit install wires
only the pre-commit stage, which leaves the commit-message linter inactive —
a malformed commit message then sails through to CI:
pre-commit install --install-hooks --hook-type pre-commit --hook-type commit-msg
Commit messages follow Conventional Commits:
a valid type, a lowercase summary with no trailing period, a header of at most
80 characters, and a body on everything but docs. The scope is the scheme the
change lives in — hash, lincheck, pcs, sumcheck, zerocheck — or one of
verifier, ghash, prover, sha256_challenger, blake3_challenger, fs,
release for the modules directly under the package. A change spanning several of them takes no scope.
The same linter runs in CI over every commit in a pull request and over the PR
title.
No submodules and nothing to clone by hand — both pinned deps are fetched by the build:
| dep | how |
|---|---|
| flock — the reference prover + byte-compare oracle | a cargo git rev dep (flock-core / flock-prover in Cargo.toml); cargo build fetches it at the pinned rev, and examples/dump_*.rs drive it to dump the golden fixtures |
zorch — the scheme-agnostic spine (the device Fiat-Shamir transcript, the ProverRound/VerifierRound protocols and their drivers, the ProverStage/VerifierStage claim-reduction roles, pcs.ligerito) | a bazel git_override in MODULE.bazel; bazel fetches it |
hash-frx — the symmetric hash layer (hash_frx.sha256 behind the Merkle leaves, hash_frx.blake3 behind witness generation) | a pinned wheel in requirements.in, from the Fractalyze package index |
Prerequisites — an NVIDIA GPU (CUDA; RTX 5090 / sm_120 reference), a Rust
toolchain (flock-core is edition 2024), Python 3.11. For the GPU fast path, a CUDA 13.3
ptxas first on PATH (/usr/local/cuda is not necessarily one): with it
the compiler emits the hardware clmad GF(2¹²⁸) multiply; without it, the
software binary_field_ghash multiply — same output, just slower. PATH — or
CUDA_DIR — is what selects it, not CUDA_ROOT, which frx overwrites at
import. See docs/measurement.md, which also covers why
a stale compilation cache can hide the fix. The *_oracle_test.py gates and
prove_phase_bench.py refuse to run on a toolchain that cannot assemble
clmad, so a mis-set environment fails loudly rather than as a phantom
regression.
git clone https://github.com/fractalyze/flock-zorch.git && cd flock-zorch
Reproduction has three tiers with independent deps: a Rust toolchain regenerates the golden fixtures by driving the pinned flock (no GPU, no Python); the CPU byte-match checks the frx port against them under Bazel (deps from the pip lock, zorch from the git_override — no venv); the GPU byte-match runs the port on-device from a venv. Build the venv once (the other two tiers need nothing installed):
python3.11 -m venv .venv
.venv/bin/pip install -r requirements.in --extra-index-url https://fractalyze.github.io/pypi/simple/
Bumping the pins
- flock — bump the
revon theflock-core/flock-provergit deps inCargo.toml; cargo re-fetches on the next build. - zorch — bump the
git_overridecommit inMODULE.bazel, and moverequirements.in'sfrx/frxlib/frx-cuda12wheels to the SAME version as zorch's ownrequirements.in— the binary-field GPU kernels must match, and CPU-only CI can't catch a desync.
Then re-verify before pushing:
scripts/dump_goldens.sh core && bazel test //python:all
Reproduce
The oracle is the pinned flock itself: examples/dump_*.rs dump fixtures from
flock-core, and the *_oracle_test.py gates byte-compare the FRX port's
serialized proofs against them. The gates are proof-level: every field of a
full serialized proof is compared, which transitively pins every layer under it
(FS framing, NTT, Merkle/octopus, zerocheck, lincheck, ring-switch — one
diverging byte anywhere flips every Fiat-Shamir draw after it). Primitives are
covered by python-native tests (no goldens); the retired per-layer golden gates
live in git history.
Bazel tests (CPU)
Run under bazel — deps from the pip lock, zorch from the MODULE.bazel
git_override, goldens from //artifacts runfiles. One byte-match gate (the
full LigeritoProof — flock's fused prove has no config below m=22, so the e2e
gate can't come down to CPU) plus the native tests:
scripts/dump_goldens.sh core # goldens the gates byte-compare against
bazel test //python:all # (JAX_PLATFORMS=cpu + x64 pinned in .bazelrc)
bazel test //python:ligerito_oracle_test # the CPU byte-match anchor alone
Proof gates (GPU, venv)
The full-prove gates — the identity e2e and the hash-circuit provers
(keccak/sha2/blake3, hundreds-of-MB goldens) — are not bazel targets (the
CUDA wheels aren't hermetic). Run them on the venv, resolving the same
git_override'd zorch via scripts/zorch_pythonpath.sh:
export FRX_PLATFORMS=cuda,cpu # GPU prover + CPU SHA query chains
export FRX_ENABLE_X64=1 # required by packed F128 witnesses
unset JAX_PLATFORMS JAX_ENABLE_X64 # avoid overriding the frx settings
export XLA_PYTHON_CLIENT_PREALLOCATE=false # don't grab ~75% of VRAM up front
export PYTHONPATH="python:$(scripts/zorch_pythonpath.sh)"
CUDA13=/usr/local/cuda # MUST be a 13.3 toolchain
export PATH="$CUDA13/bin:$PATH" # PATH selects clmad — NOT CUDA_ROOT
VENV=.venv/bin/python
scripts/dump_goldens.sh all # + the real hash circuits
$VENV python/flock_zorch/testing/e2e_ligerito_oracle_test.py # fused prove (identity R1CS)
$VENV python/flock_zorch/testing/keccak3_ligerito_oracle_test.py # Keccak full prove (Ligerito)
$VENV python/flock_zorch/testing/blake3_ligerito_oracle_test.py
$VENV python/flock_zorch/testing/bench_ligerito_oracle_test.py # snark.fast profile (BLAKE3 FS+Merkle) vs the challenge fork
The full proof-gate list is the *_oracle_test.py set under
python/flock_zorch/testing/. artifacts/ is gitignored (regenerable, and
blake3_golden.bin alone is ~118 MB); scripts/dump_goldens.sh [core|all]
rebuilds it from the pinned flock.
One benchmark point (SHA-256, m=26)
VENV=.venv/bin/python # the venv from Setup
cargo run --release --example dump_sha2_ligerito -- 2048 artifacts/sha2_ligerito_golden.bin # real R1CS, m=26
cargo build --release --example bench_sha2_ligerito_cpu # CPU anchor
export FRX_PLATFORMS=cuda,cpu FRX_ENABLE_X64=1
export XLA_PYTHON_CLIENT_PREALLOCATE=false
unset JAX_PLATFORMS JAX_ENABLE_X64
export PYTHONPATH="python:$(scripts/zorch_pythonpath.sh)"
CUDA13=/usr/local/cuda # required for the hardware clmad path
export PATH="$CUDA13/bin:$PATH" # PATH selects clmad — NOT CUDA_ROOT
CPU=$(target/release/examples/bench_sha2_ligerito_cpu 2048 | grep -oE '[0-9.]+ ms' | head -1)
$VENV python/flock_zorch/testing/prove_phase_bench.py sha2 --cpu-ms "${CPU%% ms}" # GPU vs CPU
prove_phase_bench.py also splits the prove into commit / zerocheck / lincheck /
open and reports hashes/second, and refuses to print absolute numbers when
another process is using the GPU — a neighbour saturating the SMs inflates a warm
prove ~28× here, which is enough to invent a result. Swap sha2 for blake3 or
keccak3; --golden points it at an m-variant dump.
Against flock's CUDA prover
succinctlabs/flock ships a hand-written CUDA prover (cuda-ghash/) that runs
the same protocol on the same field, so it can be measured on one box against
this one. rival_compare.py runs both, interleaved and each in its own process,
and prints a like-for-like table:
$VENV python/flock_zorch/testing/rival_compare.py \
--rival-bin /path/to/flock/cuda-ghash/bench_ligerito
It refuses to quote a ratio until it has verified structurally — log_n,
initial_k, the recursion depth, the query ladder — that both sides are running
the same instance, since m32 and fast32 are only labels. It then subtracts
the asymmetries their harness reports (witness generation, bench-only input
fill) and measures the one it can control: their bench performs no fold PoW
and ours performs 21 grinds worth ~1.07M expected hash attempts, so the same
prove is run with and without that schedule and the difference is priced rather
than assumed. See docs/measurement.md for why each
correction is there.
The snark.fast harness window
The flock-challenge benchmark
measures a fresh worker per trial — seed on stdin starts the clock, the proof
file's rename stops it — and verifies every proof with the fork's own verifier
(BLAKE3 Fiat-Shamir and BLAKE3 Merkle on the flock-bench-v0 domain; the
prove_fast profile plumbing selects that arm). Point its WORKER positional
at one of the two entry scripts — scripts/bench_worker.sh for the GPU tier
(FRX_PLATFORMS=cuda,cpu) or scripts/bench_worker_cpu.sh for the CPU tier
(cpu), the one the Yukon x86 leaderboard comparison is scored on. Each sets
its platform list and then sources scripts/bench_worker_common.sh, the shared
env shim: the harness clears the worker's env, so the shim restores it and
exec's
python/flock_zorch/testing/bench_worker.py, whose timed body is byte-gated
against a fork-verified bundle by bench_ligerito_oracle_test.py (regen:
cargo run --release --example dump_bench_ligerito) — the harness's
verified=true is the same acceptance the gate pins. The platform list cannot
be an override from the caller, because that cleared env would drop it; one
entry script per tier is how each tier names its own. The shim also wires a
per-wheel JAX_COMPILATION_CACHE_DIR: a respawned worker must absorb the whole
XLA compile inside the harness's readiness budget, so warm trials have to hit
that cache. docs/measurement.md has the rules for
running that harness and reading what comes out.
python/flock_zorch/testing/worker_startup_bench.py attributes the readiness
wall those workers pay: it drives an entry script the way the harness does and
reports min / median / spread per phase, --programs adding the XLA cache load
per program.
git -C "$SCRATCH" clone https://github.com/Layr-Labs/flock-challenge.git
git -C "$SCRATCH/flock-challenge" checkout d866043
# One-time compile-cache warm per wheel and log2, OUTSIDE the harness's
# readiness budget (a cold cache pays ~10 min of XLA compile):
echo 42 | scripts/bench_worker.sh 8 "$SCRATCH/warm.ready" "$SCRATCH/warm.proof"
cargo run --manifest-path "$SCRATCH/flock-challenge/Cargo.toml" --release \
-p flock-benchmark-harness -- \
"$PWD/scripts/bench_worker.sh" "$SCRATCH/bench" score.json summary.json \
8 32 2 10 # LOG2 THREADS WARMUP_RUNS RUNS; log2 8 → m=22, 18 → m=32
# Swap in scripts/bench_worker_cpu.sh, in both commands, for the CPU tier.
Benchmark
Apple-to-apple: unmodified flock CPU vs flock-zorch GPU on the same idle
machine (RTX 5090, Ryzen 9 9950X), same-instance both sides. Every instance is
a real flock hash-circuit R1CS at flock's shipped size, swept over witness size
to locate the crossover. GPU uses hardware clmad; timing is warm best-of-3
(JIT compile excluded), with the card verified idle. CPU rows use pinned flock
85fc0e7, thin LTO, one codegen unit, and target-cpu=native
(AVX-512/VPCLMULQDQ). GPU rows use zorch cad4fea and FRX
0.10.1.dev20260803035606.
The GPU columns below are stale. cad4fea was an orphaned-branch pin that
#199 dropped when it repinned to zorch main, and the repin alone moved m32
throughput substantially. Every GPU number in the tables predates that, so read
them as a floor, not as current performance.
Keccak3 (Ligerito)
| m | hash slots | flock CPU (ms) | GPU (ms) | Keccak/s | speedup |
|---|---|---|---|---|---|
| 22 | 96 | 6.54 | 9.01 | 10,653 | 0.73× |
| 24 | 384 | 7.38 | 9.76 | 39,335 | 0.76× |
| 26 | 1536 | 15.21 | 11.53 | 133,173 | 1.32× |
| 28 | 6144 | 52.67 | 15.65 | 392,485 | 3.37× |
| 30† | 24576 | 218.17 | 33.21 | 739,960 | 6.57× |
| 31† | 49152 | 456.69 | 56.47 | 870,485 | 8.09× |
† Rows marked † were measured under XLA_PYTHON_CLIENT_ALLOCATOR=cuda_async,
believed at the time to be required against BFC fragmentation (#131). That is
not a general rule for large m: at m32 the default allocator runs clean in
both throughput and phase-split mode, and cuda_async inflates the prove
~14% (71.8 vs 81.6 ms, means of three fresh processes per arm).
Reach for it only if you actually hit an allocator OOM — and expect these rows
to understate the current prover until they are re-measured. See
docs/measurement.md.
BLAKE3 (Ligerito)
Both sides exclude witness construction. CUDA 13.3 ptxas emitted PTX 9.3 for
sm_120 (driver 610.43.02).
| m | n_comp | flock CPU (ms) | GPU (ms) | BLAKE/s | speedup |
|---|---|---|---|---|---|
| 26 | 4096 | 14.58 | 9.45 | 433,299 | 1.54× |
| 28 | 16384 | 44.77 | 13.78 | 1,188,901 | 3.25× |
| 31† | 131072 | 367.18 | 55.46 | 2,363,383 | 6.62× |
Reproduce all three GPU points with the shared goldens:
export FLOCK_ZORCH_ARTIFACTS="$PWD/artifacts" # where dump_goldens.sh writes
export FRX_PLATFORMS=cuda,cpu FRX_ENABLE_X64=1
export XLA_PYTHON_CLIENT_PREALLOCATE=false
# A 13.3 toolchain must come first on PATH — /usr/local/cuda is not necessarily
# one, and 12.9 silently selects the software GF(2¹²⁸) multiply (~5.5× at m28)
# and fails outright in nvlink on the larger opens. XLA resolves ptxas from
# CUDA_DIR then PATH; CUDA_ROOT plays no part (frx overwrites it at import), so
# probe the ptxas PATH actually resolves. Keep these two exports SEPARATE: in a
# single `export A=x PATH="$A/bin:$PATH"`, $A still expands to its OLD value,
# so the toolchain never reaches PATH.
CUDA13=/usr/local/cuda
export PATH="$CUDA13/bin:$PATH"
ptxas --version | grep -q 'release 13.3' ||
echo 'WARNING: the ptxas on PATH is not 13.3 — the gates will refuse'
export PYTHONPATH="python:$(scripts/zorch_pythonpath.sh)"
VENV=.venv/bin/python
# Needed when the host does not already provide CUDA 12 user-space libraries.
# Keep the version identical to requirements.in's frx pin.
.venv/bin/pip install "frx-cuda12-plugin[with-cuda]==$(sed -n 's/^frx==//p' requirements.in)" --extra-index-url https://fractalyze.github.io/pypi/simple/
$VENV python/flock_zorch/testing/prove_phase_bench.py blake3 --throughput --golden blake3_ligerito_golden_m26.bin --cpu-ms 14.58
$VENV python/flock_zorch/testing/prove_phase_bench.py blake3 --throughput --golden blake3_ligerito_golden_m28.bin --cpu-ms 44.77
$VENV python/flock_zorch/testing/prove_phase_bench.py blake3 --throughput --golden blake3_ligerito_golden_m31.bin --cpu-ms 367.18
Omit --throughput for the synchronized commit / zerocheck / lincheck / open
diagnostic breakdown; its phase barriers intentionally report a slower time.
Acknowledgments
The proving scheme and the reference implementation are
flock by Succinct Labs — the
flock paper (eprint 2026/1329). flock-zorch
is an independent GPU implementation of that scheme on the zorch stack; the
unmodified succinctlabs/flock prover is pinned as the flock-core /
flock-prover git rev dep and is the byte-compare oracle every gate checks
against. All credit for the scheme and the R1CS PIOP design is theirs.