zkvmBlast is a zkVM-agnostic differential fuzzer for RISC-V zkVMs. It runs the same program
across several zkVM implementations (SP1, RISC0, OpenVM, Pico, Zisk, Airbender) and against the
Spike ISA simulator as the reference oracle, then compares the results to find divergences.
There are two harness paths: a Rust-level one that compiles Rust guest programs for each
zkVM (the harness, fuzzer, input-gen and convert-fixtures crates), and a RISC-V-level
one that works directly on RV32IM binaries (risc_experiments/lean_generator).
The RISC-V-level path includes SPE (Skeletal Program Enumeration), which takes a seed program
and enumerates semantics-preserving variants of it (register reallocations, operand swaps and
value substitutions), so that a single seed yields many differently-shaped programs that must all
produce the same final register state. See
risc_experiments/lean_generator/README.md.
git clone --recursive https://github.com/zksecurity/zkvmblast.git
cd zkvmblastIf you already cloned without --recursive:
git submodule update --init --recursiveSubmodule prerequisites:
zkrustsmith/is a Kotlin/Gradle project, so it needs a JDK and Gradle to build.execution-spec-tests/is driven withuv(uv run fill).
The RISC-V-level harnesses additionally need the seed corpus repository,
riscv-seeds-lab, checked out as a sibling
directory of this one. They resolve the corpus as <parent>/riscv-seeds-lab/corpus:
cd ..
git clone https://github.com/zksecurity/riscv-seeds-lab.gitSee risc_experiments/README.md for the full setup, including the
CORPUS=... override if you keep the corpus somewhere else.
Run a fuzzing campaign on SP1 in execute mode, using the Fibonacci program, with 5 generated inputs capped at a maximum value of 50.
cargo run --release -p zkvmblast-fuzzer -- \
--config ./native/fib/config.json \
--num-inputs 5 \
--max-value 50 \
--zkvms sp1 \
--guests ./guest \
--results ./results \
--mode executeAll example commands in this README are meant to be run from the repository root, and all paths
are relative to it. --zkvms accepts a comma-separated list; the Rust-level harness supports
sp1, risc0, openvm, pico, zisk and jolt.
| Directory | Purpose |
|---|---|
harness/ |
Rust guest differential harness: builds a guest per zkVM, runs it, compares outputs |
fuzzer/ |
Fuzzer driver: generates inputs and drives the harness over one config or a whole corpus |
input-gen/ |
Input generation for guest program signatures |
convert-fixtures/ |
Converts Ethereum execution-spec-tests witnesses into the JSON the Reth guest consumes |
native/ |
Native reference implementations and their guest configs (config.json) |
input/ |
Input JSON data for the native programs |
rust_corpus/ |
Rust-level corpus: seeds harvested from real code plus AST-level mutants |
risc_experiments/ |
RISC-V-level fuzzing: SPE engine, executors, and findings |
trace-mutation/ |
Soundness testing by trace-level mutation: extract a prover's witness data, mutate it, and re-prove |
arguzz-plus/ |
Soundness fuzzer that injects single-fault mutations into a patched zkVM and flags accepted corrupt proofs |
zkvms/ |
zkVM source forks used by the soundness crates (risc0-v2, risc0-v3 as submodules) |
corpus/ |
Guest ELF corpus consumed by arguzz-plus |
scripts/ |
Pipeline scripts (fixture download, EVM input generation, result parsing, cleanup) |
zkrustsmith/ |
Submodule: fork of RustSmith that emits random zkVM-compatible Rust programs |
execution-spec-tests/ |
Submodule: fork of the Ethereum execution-spec-tests with randomization hooks |
To generate programs using zkrustsmith (fork of RustSmith), run the following commands:
cd zkrustsmith
./gradlew build
./run/rustsmith -n 3 --zkvm --usize-width 32This will generate three Rust programs in outRust/native and one input for each in outRust/input (e.g., outRust/input/file0.json).
To test a single generated program, use the following command (from the project root directory):
cargo run --release -p zkvmblast-harness -- \
--config ./zkrustsmith/outRust/native/file0/config.json \
--input ./zkrustsmith/outRust/input/file0.json \
--zkvms sp1 \
--guests ./guest \
--mode prove \
--results ./results --verboseTo run all generated programs, use a loop:
for i in {0..2}; do
cargo run --release -p zkvmblast-harness -- \
--config ./zkrustsmith/outRust/native/file${i}/config.json \
--input ./zkrustsmith/outRust/input/file${i}.json \
--zkvms sp1 \
--guests ./guest \
--mode prove \
--results ./results --verbose
doneNote: Adjust the range {0..2} to match the number of programs generated (e.g., {0..9} for 10 programs).
Location: rust_corpus/
Automated pipeline for generating a mutation-based fuzzing corpus: seeds are harvested from
TheAlgorithms/Rust, mutated at the AST level (AOR, ROR,
BVM, CR operators), and packaged as native crates under rust_corpus/corpus/native/ (172 in the
current snapshot). Counts vary with the configuration and the upstream revision.
rust_corpus/README.md is the source of truth for the current numbers
and for how to regenerate them.
Batch test multiple native seeds (example, limit to 3 seeds):
cargo run --release -p zkvmblast-fuzzer -- \
--configs-dir ./rust_corpus/corpus/native/ \
--max-seeds 3 \
--num-inputs 5 \
--zkvms sp1 \
--guests ./guest \
--results ./results \
--mode execute \
--max-value 50Location: risc_experiments/
Differential fuzzing at the RISC-V level, comparing zkVM execution against Spike:
- Multi-target ELF generation so one program runs on Spike, SP1, RISC0, OpenVM, Pico and Airbender
- Spike ISA simulator as the reference oracle
- SPE (Skeletal Program Enumeration): semantics-preserving variants of each seed program
- Register and termination oracles for comparing runs
Tools:
lean_generator- RV32IM program adaptation, the SPE engine, and the diff-test harnessessp1-executor,pico-executor,airbender-executor- per-zkVM test harnesses
This path needs the riscv-seeds-lab corpus as a sibling checkout (see Getting the Code).
See: risc_experiments/README.md and
risc_experiments/lean_generator/README.md
The differential and SPE paths above look for completeness bugs (a valid program a zkVM cannot execute or prove). Two workspace crates target the other class, soundness: a prover producing a proof a verifier accepts for an incorrect execution.
trace-mutation/extracts a prover's internal witness data (execution traces, commitments, public values), mutates it, and re-proves to check whether the altered proof still verifies. Each zkVM backend is behind a Cargo feature (sp1,risc0-v2,openvm); the tests prove real programs and are slow, so they are marked#[ignore]and run with--features <backend> -- --ignored.arguzz-plus/(inspired by ARGUZZ) drives a corpus of guest ELFs through a patched zkVM, injects single-fault mutations, and reports any proof the verifier accepts for a corrupted execution. It reads its corpus fromcorpus/.
Both link zkVM prover SDKs (SP1, RISC0, OpenVM, and for trace-mutation also Pico
and Jolt) and need the zkvms/ submodules initialized:
git submodule update --init zkvms/risc0-v2 zkvms/risc0-v3. Because the zkVM
platforms define conflicting global symbols, build and test one backend feature at
a time, never with --all-features.
See: trace-mutation/README.md and
arguzz-plus/README.md
zkVMs use precompiles to accelerate cryptographic operations. When --with-precompiles is enabled, the guest program's Cargo.toml will include [patch.crates-io] sections that replace common crypto crates with zkVM-optimized versions.
SP1 (docs):
sha2,sha3,tiny-keccak,k256,p256,curve25519-dalek,curve25519-dalek-ng,rsa,bls12_381,substrate-bn,crypto-bigint,secp256k1,ecdsa
RISC0 (docs):
sha2,tiny-keccak,k256,p256,curve25519-dalek,rsa,bls12_381,substrate-bn,crypto-bigint,c-kzg,blst
cargo run --release -p zkvmblast-harness -- \
--config ./native/sha256/config.json \
--input ./input/sha256.json \
--zkvms sp1,risc0 \
--guests ./guest \
--results ./results \
--mode execute \
--verbose \
--save-allcargo run --release -p zkvmblast-harness -- \
--config ./native/sha256/config.json \
--input ./input/sha256.json \
--zkvms sp1,risc0 \
--guests ./guest \
--results ./results \
--mode execute \
--save-all \
--verbose \
--with-precompilesWhen using --with-precompiles, the guest directory naming changes from {native}_{zkvm}_guest to {native}_precompile_{zkvm}_guest (e.g., sha256_precompile_sp1_guest).
Note: the
wit-genbranch below lives in a personal fork; it will be moved to an org repo before release. The guest-side crates (reth-guest,guest-libs,witness-generator) are pinned inCargo.tomlto upstreameth-act/zkevm-benchmark-workload; only this CLI needs the fork.
git clone https://github.com/varunthakore/zkevm-benchmark-workload.git
cd zkevm-benchmark-workload
git checkout wit-gen
cargo install --locked --path crates/witness-generator-cliCheck available commands
witness-generator-cli --help
witness-generator-cli tests --help
RAYON_NUM_THREADS=8 ./scripts/generate-fixtures-input.sh ./input/reth.json
Example with string argument:
RAYON_NUM_THREADS=8 ./scripts/generate-fixtures-input.sh ./input/reth.json "--include Prague --exclude exception"
This project was partially funded by a grant from the Ethereum Foundation.