Zero-Knowledge Virtual Machines (zkVMs)
A zero-knowledge virtual machine (zkVM) proves that an ordinary program ran correctly. You compile normal code to a general instruction set, run it off-chain, and emit a small cryptographic proof that a smart contract can check in milliseconds — without re-running the program or trusting whoever ran it. The design goal: developers write plain code instead of hand-building a bespoke circuit, and one universal verifier serves every program.
TL;DR
- A zkVM proves correct execution of a program compiled to a general instruction set (usually RISC-V), not a single hand-written circuit.
- The prover runs the "guest" program off-chain and produces a succinct proof; an on-chain verifier checks it against a hash of the program (a verification key) without re-execution.
- Because the verifier is universal to the VM, there is no per-application Trusted Setup — the one setup belongs to the VM's proof-wrapping circuit, run once and reused by every program.
- SP1 (by Succinct) is the running example here: Rust → RISC-V guest, a STARK prover, a STARK→SNARK wrap for cheap on-chain verification, and an on-chain
SP1VerifierGateway. - The trade-off vs a bespoke Groth16 circuit: less specialist effort to write and audit, but proving is much more expensive and slower.
- Trust does not vanish — it moves to the off-chain guest source (the on-chain contract pins only its hash), the prover's liveness, and the verifier contract's correctness.
- Live examples on DNZN: Tacit's ConfidentialPool and Bridge Mixer both gate on SP1.
What Is a zkVM?
A zkVM is a machine that, alongside a program's output, produces a proof that the output is the genuine result of running that program on that input. Anyone can verify the proof cheaply; nobody has to trust the party that did the computing.
Contrast two ways to build a zk-SNARK application:
- Bespoke circuit. You express your exact computation (e.g. "this Merkle path is valid and the amounts balance") as an arithmetic circuit by hand, then run a Trusted Setup ceremony for that specific circuit. This is how Tornado-style mixers work (Groth16). Tight and cheap to verify, but every app is a new circuit and a new ceremony, and circuits are hard to write and audit.
- zkVM. You write ordinary code (Rust, in SP1's case), compile it to RISC-V, and let the VM prove that the compiled program executed correctly. One verifier works for every program; the program's identity is captured by a hash. You trade proving efficiency for reduced circuit-authoring effort and a universal verifier.
%%{init: {'theme': 'base'}}%%
flowchart LR
subgraph off["Off-chain (untrusted prover)"]
SRC["Guest program<br/>(Rust → RISC-V)"]
RUN["Execute on inputs"]
PRV["Prover:<br/>STARK proof of execution"]
WRAP["Wrap: STARK → SNARK<br/>(small on-chain proof)"]
end
subgraph on["On-chain (Ethereum)"]
VER["Verifier contract<br/>verify(vkey, publicValues, proof)"]
APP["Your contract<br/>acts on publicValues"]
end
SRC --> RUN --> PRV --> WRAP --> VER
SRC -. "compile-time hash" .-> VKEY["PROGRAM_VKEY<br/>(pins the program)"]
VKEY --> VER
VER -->|proof valid| APP
Figure 1: A zkVM pipeline. The guest program is hashed to a verification key at compile time; the prover runs it off-chain and produces a proof bound to that key; the on-chain verifier accepts the committed public values only if the proof checks out.
How It Works
SP1 makes the pipeline concrete:
- Guest Program. Ordinary Rust compiled to RISC-V. It reads inputs, does the work (a Merkle Tree-membership check, a range proof), and
commits a set of public outputs. - Verification key (
PROGRAM_VKEY). A hash that commits to the exact compiled guest. Change one line of the guest and the key changes. The on-chain contract stores this key as an immutable and will only accept proofs for that program. - Prover. Runs the guest, records the full execution trace, and produces a STARK proof that every RISC-V step was executed faithfully. This is the expensive part — proving an entire VM costs far more than proving a tight circuit.
- STARK → SNARK wrap. A raw STARK proof is large and costly to verify on Ethereum. SP1 recursively compresses and wraps it into a small SNARK (Groth16 or PLONK over bn254) that verifies through the EVM's pairing precompiles for a near-constant, low gas cost.
- On-chain verifier. Succinct's
SP1VerifierGatewayroutes to a versioned verifier contract that runsverify(vkey, publicValues, proofBytes). If it returns cleanly, your contract trustspublicValuesas the honest output of the pinned program.
The application contract does no cryptography of its own beyond that one verifier call. A single proof-checking entrypoint reads the committed public values and applies effects; all the heavy checking (membership proofs, value conservation, range bounds) already happened inside the guest, so the contract only enforces the public boundary and any one-shot spend gates.
For the Merkle Proof membership check above, see Merkle Trees.
Trusted setup, amortized
A common misconception is that zkVMs remove trusted setup. They don't — they amortize it. The STARK proving core needs no setup, but the SNARK used for the final on-chain wrap does (a Groth16 wrap has a circuit-specific setup; a PLONK wrap uses a universal Structured Reference String (SRS)). That setup is for the VM's fixed wrap circuit, performed once by Succinct and reused by every guest program. There is no per-application ceremony. A bespoke Groth16 app, by contrast, needs a fresh ceremony for every circuit — and inherits fresh Toxic Waste risk each time.
Key Concepts
| TERM | ROLE |
|---|---|
| Guest program | The application logic, written as normal code and compiled to RISC-V. |
Verification key (PROGRAM_VKEY) |
Hash commitment to the compiled guest; pins which program a proof must be for. |
| Prover | Off-chain party that executes the guest and generates the proof. Untrusted — the proof is what's trusted. |
| Public values | The outputs the guest commits to; the only data the on-chain contract acts on. |
| STARK | Hash-based, transparent (no-setup) proof system used for the VM execution proof. |
| STARK → SNARK wrap | Compression of the STARK into a small Groth16/PLONK proof cheap to verify on-chain. |
| Verifier / gateway | On-chain contract (SP1VerifierGateway) that checks the wrapped proof against a vkey. |
zkVM vs bespoke circuit
| DIMENSION | ZKVM (E.G. SP1) | BESPOKE CIRCUIT (E.G. GROTH16) |
|---|---|---|
| Developer effort | Write normal code (Rust) | Hand-build an arithmetic circuit |
| Auditability | Read the guest source | Read circuit constraints (specialist skill) |
| Trusted setup | One, for the VM wrap circuit, reused by all apps | One per circuit, per app |
| Verifier | Universal to the VM | Specific to the circuit |
| Proof size / verify gas | Small / low (after wrap) | Smallest / lowest |
| Prover cost & latency | High (proving a whole VM) | Lower (tight circuit) |
| Upgrade a bug | New guest → new vkey → contract must point at it | New circuit → new ceremony |
Other zkVMs
SP1 is the zkVM DNZN has encountered on-chain, but it is not the only one. Others in the same category include RISC Zero (also RISC-V), Jolt, and Nexus. They differ in proof system and performance, but share the model: prove a general program, verify with a universal verifier.
Risks / What Can Go Wrong
A zkVM proof is only as trustworthy as the things around it. What a valid proof does not tell you:
- ☒ The guest source is honest. The contract pins a hash (
PROGRAM_VKEY), not human-readable code. If the off-chain guest contains a value-conservation bug, the contract will faithfully prove and apply the wrong thing. The guest source lives off-chain and has to be obtained and audited independently — a valid proof says nothing about whether the program it pins is correct. - ☒ The verifier and gateway are correct and honestly governed. A
verifythat wrongly returns success breaks everything downstream. Gateways route to versioned verifier contracts; who can add or freeze routes is a governance question worth checking. - ☒ Public values must be bound correctly. A proof attests that the guest produced some outputs; the application must bind those outputs to the right chain, contract, and nonce. Without that binding — typically a chain-id/address commitment plus a one-shot nullifier — the same proof can be replayed against another deployment or reused twice.
- △ Setup assumptions remain. The one-time wrap-circuit setup still carries Toxic Waste risk. It's amortized across all programs rather than removed — a Groth16 wrap rests on at least one honest ceremony participant.
- △ Prover liveness and centralization. Proving is expensive and slow; apps often lean on a prover network or a single prover. That is a censorship and liveness surface, not a soundness one — but a stalled prover means a stuck protocol.
- △ The zkVM is young software. A soundness bug in the VM, a precompile, or the recursion layer would forge proofs. These systems are new and lightly battle-tested relative to the value they can gate.
Where the trust actually sits
A verified zkVM proof converts "trust the operator" into "trust the guest source, plus the prover and verifier around it." That is often a real reduction in trust surface — but it is a relocation, not an elimination. When a protocol advertises "no trusted roles," check where the proving lives and read the guest source.
References
- SP1 Documentation — Succinct's zkVM: guest programs, proving, and the on-chain verifier.
- SP1 (GitHub) — source for the zkVM and verifier contracts.
- RISC Zero Documentation — another RISC-V zkVM, useful for comparison.
- The Path to Secure and Efficient zkVMs (a16z crypto) — Justin Thaler on the zkVM landscape and its security/performance trade-offs.
- STARKs, Part I (Vitalik Buterin) — background on the transparent, no-setup proof systems zkVMs build on.
Changelog
| DATE | AUTHOR | NOTES |
|---|---|---|
| 2026-07-04 | Artificial. | Generated by robots. Gas: 40 tok |
| 2026-07-04 | Denizen. | Reviewed, edited, and curated by humans. |