Skip to content

Latest commit

 

History

324 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zkvmBlast

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.

Getting the Code

git clone --recursive https://github.com/zksecurity/zkvmblast.git
cd zkvmblast

If you already cloned without --recursive:

git submodule update --init --recursive

Submodule prerequisites:

  • zkrustsmith/ is a Kotlin/Gradle project, so it needs a JDK and Gradle to build.
  • execution-spec-tests/ is driven with uv (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.git

See risc_experiments/README.md for the full setup, including the CORPUS=... override if you keep the corpus somewhere else.

Quick Start

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 execute

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

Repository Layout

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

zkrustsmith

To generate programs using zkrustsmith (fork of RustSmith), run the following commands:

cd zkrustsmith
./gradlew build
./run/rustsmith -n 3 --zkvm --usize-width 32

This 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 --verbose

To 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
done

Note: Adjust the range {0..2} to match the number of programs generated (e.g., {0..9} for 10 programs).

Rust Corpus

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 50

RISC-V Experiments

Location: 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 harnesses
  • sp1-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

Soundness Fuzzing

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 from corpus/.

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

Precompiles

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.

Supported zkVMs and Precompiles

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

Test without precompiles

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-all

Test with precompiles

cargo 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-precompiles

When using --with-precompiles, the guest directory naming changes from {native}_{zkvm}_guest to {native}_precompile_{zkvm}_guest (e.g., sha256_precompile_sp1_guest).

EVM Data Generation

Install Witness Generator CLI tool

Note: the wit-gen branch 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 in Cargo.toml to upstream eth-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-cli

Check available commands

witness-generator-cli --help
witness-generator-cli tests --help

Generate input data using:

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"

Acknowledgments

This project was partially funded by a grant from the Ethereum Foundation.

About

Fuzz test ZKVMs

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages