Quickstart
# Prove a Fibonacci sequence with ZisK's stark, then check the proof.
#
# zisk-zorch is ZisK's pil2-stark prover rebuilt on zorch blocks. `InnerProver`
# runs the inner proof over one Fiat-Shamir transcript -- commit the trace, build
# the quotient, then discharge it with a DEEP opening and a FRI low-degree test --
# and `InnerVerifier` is its stage-for-stage dual: Merkle paths against the
# committed roots, the AIR identity at an out-of-domain point, the DEEP
# composition, and the FRI fold chain.
#
# Where the proof lives: in the quotient's DEGREE. The constraints are folded by
# powers of a challenge into one composite C(x), and C vanishes on the trace
# domain H exactly when every constraint holds on every row. Dividing by the
# zerofier x^N - 1 -- which vanishes precisely on H -- leaves a polynomial ONLY
# in that case. Lie about the output and the division leaves a rational function
# whose interpolant runs to full degree, and FRI rejects.
#
# The verifier re-evaluates the AIR at ONE opened row, so constraints are
# row-wise: a row carries (F_i, F_{i+1}, F_{i+2}) and the recurrence is a + b - c.
# `is_first` / `is_last` are committed selector columns, which is what lets one
# AIR carry the boundary rows and the recurrence at once. Change A0, B0 or LOG_N
# and re-run.
import frx.numpy as fnp
import numpy as np
from zk_dtypes import goldilocks as F
from zisk_zorch.prover import InnerProver
from zisk_zorch.transcript.transcript import Transcript
from zisk_zorch.types import InnerClaim, InnerWitness
from zisk_zorch.verifier import InnerVerifier
A0, B0 = 0, 1 # the seed row (F_0, F_1)
LOG_N = 12 # 2^12 = 4096 rows, so the claim is F_4096
POW_BITS = 8 # grinding difficulty on the query-derivation seed
N_QUERIES = 16 # opened positions -- soundness scales with this; zisk opens 64
N = 1 << LOG_N
GOLDILOCKS_P = (1 << 64) - (1 << 32) + 1
N_COLS, N_CONSTRAINTS = 5, 4
def const(value: int):
"""A canonical int as a 0-D base-field scalar. Goes through uint64 because a
Goldilocks element outgrows int64 -- F_4096 already does. The extension
embeds the base field, so this also lands in the verifier's cubic row."""
return fnp.array(np.array(value % GOLDILOCKS_P, dtype=np.uint64), dtype=F)
def fibonacci_trace(a0: int, b0: int, n: int):
"""Row i is (F_i, F_{i+1}, F_{i+2}) plus the two boundary selectors."""
seq = [a0, b0]
for _ in range(n):
seq.append((seq[-2] + seq[-1]) % GOLDILOCKS_P)
columns = (seq[:n], seq[1 : n + 1], seq[2 : n + 2],
[1] + [0] * (n - 1), [0] * (n - 1) + [1])
trace = np.stack([np.array(c, dtype=np.uint64) for c in columns], axis=1)
return fnp.array(trace, dtype=F), seq[n]
def fibonacci_air(claimed_f_n: int):
"""Four constraints, each vanishing on every row of H."""
def eval_fn(trace):
a, b, c, is_first, is_last = (trace[:, i] for i in range(N_COLS))
return fnp.stack(
[
a + b - c, # the recurrence, every row
is_first * (a - const(A0)), # starts at the given seed,
is_first * (b - const(B0)), # both columns
is_last * (b - const(claimed_f_n)), # and ends at the claimed F_n
],
axis=-1,
)
return eval_fn
trace, f_n = fibonacci_trace(A0, B0, N)
claim = InnerClaim(n_bits=LOG_N, n_cols=N_COLS, n_constraints=N_CONSTRAINTS)
def accepts(claimed_f_n: int) -> bool:
"""Prove the honest trace against a claimed output, then verify. Both roles
are built from the same AIR; only the claimed F_n differs from run to run, so
a wrong claim is an AIR the trace does not satisfy."""
air = fibonacci_air(claimed_f_n)
shape = dict(n_bits=LOG_N, pow_bits=POW_BITS)
proved = InnerProver(air, n_queries=N_QUERIES, **shape).prove(
claim, InnerWitness(trace), Transcript()
)
return bool(InnerVerifier(air, **shape).verify(
claim, proved.reduction_proof, Transcript()
).ok)
print(f"proved the ({A0}, {B0}) Fibonacci sequence over {N} rows")
print(f"F_{N} = {f_n}")
print("verifier accepts the honest claim:", accepts(f_n))
print("... and rejects a wrong F_n: ", not accepts(f_n + 1))
zisk-zorch
A lean ZisK prover built on zorch's
scheme-agnostic SNARK building blocks. zorch provides the reusable pieces
(hashing, Merkle commitment, Reed-Solomon LDE, transcript, …); zisk-zorch
adds only the ZisK-specific glue on top — the pil2-stark Poseidon2-Goldilocks
parameters, the pil2 transcript and linear-hash conventions, and the
byte-match against the pil2-proofman
reference prover that ZisK uses.
frx ──▶ zorch (scheme-/zkVM-agnostic blocks) ──▶ zisk-zorch (ZisK / pil2-stark glue)
ZisK proves with Polygon's eSTARK (pil2-stark) — a FRI-based STARK over
Goldilocks. None of that scheme-specific knowledge belongs in zorch (its hard
rule), so it lives here.
Status
InnerProver runs the inner proof end to end over one Fiat-Shamir transcript —
trace commit → quotient → DEEP → FRI. The primitives it is built from are
byte-matched against golden vectors generated from pil2-proofman v1.0.0-alpha's
fields crate (tools/fixture-gen/);
DEEP is the one phase with no golden. No phase is yet byte-matched against a real
pil2 dump, so the per-stage timings in
docs/development.md
are engineering signal, not a sealed baseline. See
docs/architecture.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.)
CPU
pip install zisk-zorch
GPU (CUDA 12)
pip install zisk-zorch 'frx[cuda12]' \
--extra-index-url https://fractalyze.github.io/pypi/simple/
The extra index carries the CUDA plugin wheels: frx-cuda12-pjrt is over PyPI's
per-file limit, and frx-cuda12-plugin is not published there. It is not needed
for the CPU tier.
Verify
python -c \
"import frx, zisk_zorch.prover; print(frx.devices()); print(zisk_zorch.__version__)"
[CpuDevice(id=0)] is the CPU tier. If you followed the GPU command and still
see it, the CUDA plugins did not take effect and everything will run on the CPU
without saying so.
Importing zisk_zorch.prover rather than the package is deliberate: the package
__init__ is a docstring and a version string, so a bare import zisk_zorch
touches neither frx nor zorch and stays green on an install that resolved
neither.
Development
From a git checkout, not a pip install — nothing below ships in the distribution.
zisk-zorch is pure Python on frx (Field, Ring Accelerated), run against the
Fractalyze xla fork's PJRT plugin (the
frx-cuda12 wheels), built with Bazel (bzlmod). It consumes zorch as a
dev-release wheel from the Fractalyze index, pinned in
requirements.in,
so frx and zk_dtypes resolve once here. Those pins are the development set,
not the packaged dependency set — a release resolves from PyPI via
pyproject.toml.
python3.11 -m venv .venv && . .venv/bin/activate
pip install -r requirements.in \
--extra-index-url https://fractalyze.github.io/pypi/simple/
Dev against a local zorch checkout instead of the pinned wheel — create
.bazelrc.user (gitignored):
common --override_module=zorch=/abs/path/to/your/zorch/checkout
Run the tests (CPU is the default for determinism):
bazel test //...
Documentation
See docs/ —
the architecture
(the inner proof as composite Stage roles over one transcript, plus the pil2
vocabulary they
mirror), the development guide
(environment, testing, fixtures, CI, and the per-stage pil2 baseline), and the
conventions.
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 package the
change lives in — commit, constraints, deep, evals, fri, logup,
poseidon2, quotient, transcript — or prover, golden, bench,
release for the modules directly under zisk_zorch/. A change spanning
several takes no scope.
The same linter runs in CI over every commit in a pull request and over the PR
title.
License
Licensed under the Apache License, Version 2.0 (see LICENSE).