Part III: Practical Guides
This section provides hands-on guidance for different audiences interested in working with zkEVMs. Whether you’re a blockchain developer, a client implementer, or a researcher, you’ll find practical information to help you get started with zkEVM technology.
This section will guide you through the practical aspects of working with zkEVMs, from setting up your environment to deploying and testing applications.
Getting Started with ERE
ERE is a powerful unified zkVM interface and toolkit that allows developers to work with multiple zkVMs through a consistent API. ERE simplifies the process of compiling, executing, proving, and verifying programs across different zkVM implementations.
What is ERE?
ERE provides a unified Rust API that works across multiple zkVM backends. This abstraction layer allows developers to:
- Switch between different zkVM implementations with minimal code changes
- Use a consistent API for compiling, executing, proving, and verifying
- Leverage the strengths of various zkVMs for different use cases
Developer Workflow with ERE
When working with ERE, developers typically follow this workflow:
- Write a guest program: Create the program you want to execute and generate proofs for using your preferred zkVM.
- Use the ERE library: Generate prover and verification keys, execute the guest program, generate proofs, and verify those proofs all through ERE’s unified interface.
Example: Building Your First zkVM Program with ERE
This example demonstrates how to compile, prove, and verify a simple program using ERE. You can choose between two setup options depending on your preference for performance or simplicity.
Option 1: With Local SDK Installation
This approach provides better performance and debugging capabilities by installing the necessary zkVM SDKs directly on your machine.
-
Install SDKs First, install the SDK for the zkVM you want to use. For example, to install the SP1 SDK, run:
bash scripts/sdk_installers/install_sp1_sdk.sh
-
Add Dependencies Next, add the required ERE crates to your project’s
Cargo.toml
file.# Cargo.toml [dependencies] zkvm-interface = { git = "https://github.com/eth-act/ere.git", tag = "v0.0.11" } ere-sp1 = { git = "https://github.com/eth-act/ere.git", tag = "v0.0.11" }
-
Compile & Prove Example Now you can write your host program to compile the guest code and generate a proof.
// main.rs use ere_sp1::{EreSP1, RV32_IM_SUCCINCT_ZKVM_ELF}; use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM}; fn main() -> Result<(), Box<dyn std::error::Error>> { let guest_directory = std::path::Path::new("workspace/guest"); // Compile guest let compiler = RV32_IM_SUCCINCT_ZKVM_ELF; let program = compiler.compile(guest_directory)?; // Create zkVM instance let zkvm = EreSP1::new(program, ProverResourceType::Cpu); // Prepare inputs let mut io = Input::new(); io.write(42u32); // Execute let _report = zkvm.execute(&io)?; // Prove let (proof, _report) = zkvm.prove(&io)?; // Verify zkvm.verify(&proof)?; Ok(()) }
Option 2: Docker-Only Setup
This method is simpler as it only requires Docker to be installed, running the zkVM operations within a container.
-
Add Dependencies Add the
ere-dockerized
crate to yourCargo.toml
instead of a specific backend crate.# Cargo.toml [dependencies] zkvm-interface = { git = "https://github.com/eth-act/ere.git", tag = "v0.0.11" } ere-dockerized = { git = "https://github.com/eth-act/ere.git", tag = "v0.0.11" }
-
Compile & Prove Example The host program is slightly different, as it uses the Dockerized compiler and zkVM wrappers.
// main.rs use ere_dockerized::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM}; use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM}; fn main() -> Result<(), Box<dyn std::error::Error>> { let guest_directory = std::path::Path::new("workspace/guest"); // Compile guest using the Dockerized compiler for SP1 let compiler = EreDockerizedCompiler::new(ErezkVM::SP1, std::path::Path::new("workspace")); let program = compiler.compile(guest_directory)?; // Create a Dockerized zkVM instance for SP1 let zkvm = EreDockerizedzkVM::new(ErezkVM::SP1, program, ProverResourceType::Cpu)?; // Prepare inputs let mut io = Input::new(); io.write(42u32); // Execute let _report = zkvm.execute(&io)?; // Prove let (proof, _report) = zkvm.prove(&io)?; // Verify zkvm.verify(&proof)?; Ok(()) }
ERE for client development
For Ethereum client developers, ERE serves as a crucial tool for a specific and vital task: proving the execution of an Ethereum block statelessly. This process is fundamental to evaluating how different zkVMs perform with various Execution Layer (EL) clients, which is essential for potential L1 integration.
The core idea is to treat an EL client as the guest program. This program is executed inside a zkVM to perform stateless block validation. Instead of connecting to a live network and accessing a database, the client receives a block and a “witness” containing all the necessary state data (like account balances, contract code, and storage) as inputs. The client then executes the block and, if successful, proves that the resulting post-state root and other block details are correct according to Ethereum’s rules.
While you can use the ERE library directly to build custom solutions, the Ethereum Foundation’s zkEVM team has developed a higher-level tool called zkevm-benchmark-workload
. This tool is specifically designed for this purpose and uses ERE under the hood. It allows developers to:
- Run benchmark fixtures through various EL client and zkVM combinations.
- Use test cases from the official
execution-spec-tests
repository. - Test performance against real mainnet blocks or custom-designed “worst-case” scenarios intended to stress-test the prover.
By using these tools, client developers can systematically benchmark how their client performs as a guest program inside different zkVMs. This work is critical for identifying performance bottlenecks, analyzing security implications, and providing concrete data to guide optimizations in both the EL clients and the zkVMs themselves.
Here is what an example guest program for stateless block verification for the SP1 zkVM looks like:
//! SP1 guest program #![no_main] extern crate alloc; use alloc::sync::Arc; use guest_libs::mpt::SparseState; use reth_chainspec::ChainSpec; use reth_evm_ethereum::EthEvmConfig; use reth_stateless::{Genesis, StatelessInput, stateless_validation_with_trie}; use tracing_subscriber::fmt; sp1_zkvm::entrypoint!(main); /// Entry point. pub fn main() { let input = sp1_zkvm::io::read::<StatelessInput>(); let genesis = Genesis { config: input.chain_config.clone(), ..Default::default() }; let chain_spec: Arc<ChainSpec> = Arc::new(genesis.into()); let evm_config = EthEvmConfig::new(chain_spec.clone()); stateless_validation_with_trie::<SparseState, _, _>( input.block, input.witness, chain_spec, evm_config, ) .unwrap(); }
note: this code would change with time as it is under active development. Refer to this link for updated implementations.
The tools and techniques outlined here are not just for experimentation; they are the essential building blocks for the future of Ethereum scaling and verifiable computation. We encourage you to build upon these examples, explore the different zkVM backends, and contribute to the ongoing effort to create a more scalable and secure decentralized world.
Resources
- https://github.com/eth-act/ere
- https://github.com/eth-act/zkevm-benchmark-workload
- https://github.com/eth-act/zkevm-benchmark-workload/blob/master/ere-guests/stateless-validator/sp1/src/main.rs