Skip to main content

This page walks through the six stages every ZisK proof passes through: compiling the guest to a RISC-V ELF, setting up the proving and verification keys, executing the ELF to produce the minimal trace, proving that trace into a single STARK, optionally wrapping it into a more compact format, and verifying it. Each stage produces artifacts the next one consumes.

The proving lifecycle

From source code to verified statement, a ZisK proof passes through six stages. Each stage produces artifacts the next one consumes, and, together they form the lifecycle of every proof the system generates.

Compiling a guest program

The first stage turns a Rust (or C) source tree into a RISC-V ELF binary. ZisK ships a Rust toolchain that is essentially the standard LLVM/Rust toolchain with a small set of customisations, notably the linker script and the ROM/RAM address ranges that the emulator expects. From the developer's point of view, the build looks like any other Cargo project: standard dependencies, standard testing tools, standard debugging workflow. The output is a 64-bit RISC-V ELF. This ELF is the canonical representation of the program. Every subsequent stage operates on this binary, not on the source code.

Setting up the compiled ELF

Setup precomputes the cryptographic material that ties future proofs to this specific compiled ELF. Two artifacts come out: a proving key the prover uses to generate proofs, and a matching verification key verifiers use to check them.

The verification key is derived deterministically from the program. A single-byte change in the ELF produces a different VK, so a known VK pins the proof to the exact guest program. It's worth noting that The ELF is standard RISC-V, but the VM that gets proven runs ZisK's own ISA: a static transpiler bridges them at setup, and the VK commits to this ZisK form.

The proving key bundles the data the prover will need: precomputed lookup tables, constant columns, chip-template metadata, and (for programs using precompiles) the variants required for hint-driven witness generation. Setup is a one-time cost per program; the keys are reused across every proving run regardless of input and only need regenerating when the program changes.

info

For the prover to run efficiently, the proving key must be loaded into memory and kept resident across runs as it is large enough that reading it from disk every run would significantly increase proving latency.

Execute the compiled program

Execution runs the compiled ELF and produces the minimal trace: a row-by-row record of every instruction executed, every memory access made, and every public value committed during the run. Two host streams flow into the run, and they play very different roles — inputs are part of the proven computation, while hints only affect how the executor builds the trace and sit outside what the proof attests to.

StreamWhat it isWhy
InputsValues loaded into the input region of memory.Part of the proven computation: committed by the proof system, so the input the program ran on is locked in and cannot be changed by the prover.
HintsValues the execution can request at runtime.Outside the proof: hints let the executor skip expensive work while generating the minimal trace.

The same trace can be produced by two emulators with identical results but different performance and hardware trade-offs; from the prover's point of view there is no difference between the traces they produce:

EmulatorHow it worksSpeedMemory floor
AssemblyNative assembly tailored to the specific ELF; every instruction runs as straight-line native code on locked physical RAM.Very fast~64 GB
RustPure-Rust interpreter of the ZisK ISA; every instruction goes through a Rust dispatch loop instead of native code.Slower~32 GB

Execution does more than produce the raw trace: it also plans how the trace will be sliced into per-chip segments. That plan is what the prove stage consumes — chips pick up their assigned segments and start proving.

Proving the execution

The most expensive stage. The prover consumes the minimal trace and the segmentation plan produced by execution and turns them into a single STARK proof. Proving runs in five phases:

PhaseWhat happens
Trace generationEach chip's witness generator fills in its trace columns from the execution log, one segment at a time. Bus messages exchanged between chips during the original execution are recorded once and read by every chip that participates. This step runs in parallel across chips.
Challenge derivationOnce every chip has fixed its trace, a single shared random value is derived from all those traces and broadcast to every segment prover. The derivation is order-independent: each segment contributes its share separately, and the shares combine the same way no matter the order.
Per-segment provingEvery filled trace (per chip, per segment) is turned into an independent STARK using the shared random value. Segments do not wait for one another. Each per-segment proof exposes its own public outputs, a partial bus-balancing sum, and its share of that value.
AggregationThe aggregation tree starts folding pairs of leaf proofs as soon as any two are ready. Branches that finish early get combined immediately rather than waiting on slower ones. Each aggregation node verifies its two children and adds together their partial sums and challenge contributions.
Root proofTwo arithmetic checks finalise the proof at the top of the tree: the total bus-balancing sum must equal zero (every chip message was received exactly once, with matching content), and the accumulated challenge shares must match the value broadcast at the start (so the randomness was honestly derived from all the traces).

The output is the root STARK proof and the public values the program committed during execution.

Applying a proof wrapper

The STARK produced by the previous stage is fast to generate but large to transmit. For local or off-chain verification that size is acceptable. For situations where the proof has to travel, and especially for on-chain verification, wrapping it into a more compact format is worth the extra proving time. Two wrapper variants exist:

VariantHow it worksUse when
Minimal STARKThe STARK is re-proven with a smaller, recursion-friendly STARK on top.The proof still has to be verified by a STARK-aware verifier, but a smaller artifact is needed for off-chain transport.
PLONK SNARKThe STARK is re-proven inside a PLONK proof system. The output is a SNARK.On-chain verification on Ethereum and EVM-compatible chains, where smart contracts can verify a PLONK proof cheaply.

Wrap is strictly opt-in. Programs whose proofs are consumed by local verifiers or ZisK-aware infrastructure skip this stage entirely.

Verifying the proof

Verification reads the proof, the verification key, and the public values, and runs a deterministic check. The cost is roughly constant in the size of the original computation: a million-step program verifies in roughly the same time as a thousand-step one, which is what makes ZisK practical for verifiers with limited compute.

A passing check confirms one thing precisely: the proof is cryptographically valid. To trust the result, three checks have to hold together:

CheckWhat it confirms
Proof is cryptographically validThe proof is well-formed and consistent with the verification key it was checked against.
VK matches the program you intendedThe verification key was derived from the exact ELF you audited, not from a different binary a malicious prover might substitute.
Public values match what you expectedA valid proof of an irrelevant computation is still useless, the committed outputs have to be the ones the application needs.

Where this picks up

The next page, Host & guest, zooms into what actually runs inside the zkVM during Execute and Prove — the two-process model, the input streams, the public outputs, and the trust boundary between them.