zorch_12

zisk-zorch

zkvm2

Lean ZisK prover on zorch blocks: pil2-stark Poseidon2-Goldilocks params, transcript, and stage-1 trace commit byte-matched against pil2-proofman.

Run in Playground ↗GitHub ↗

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).