Skip to content

Commit 89aefc1

Browse files
committed
feat:zk core systems guest/host
1 parent 278ccbd commit 89aefc1

13 files changed

Lines changed: 1008 additions & 0 deletions

File tree

zkvm/guest/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "shadow-evm-guest"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "RISC Zero guest program for Shadow-EVM ZK Coprocessor"
6+
7+
[dependencies]
8+
# Core Shadow-EVM library with no_std
9+
shadow-evm-core = { path = "../../core", default-features = false }
10+
11+
# RISC Zero guest SDK
12+
risc0-zkvm = { version = "1.2", default-features = false }
13+
14+
[profile.release]
15+
opt-level = 3
16+
lto = true

zkvm/guest/src/evm/executor.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//! ZK Executor for guest execution
2+
//!
3+
//! Wraps the core ShadowExecutor for use inside the ZK-VM guest.
4+
5+
use shadow_evm_core::prelude::*;
6+
7+
/// ZK Executor wrapper
8+
///
9+
/// Provides a streamlined interface for executing EVM transactions
10+
/// inside the ZK-VM and generating commitments.
11+
pub struct ZkExecutor;
12+
13+
impl ZkExecutor {
14+
/// Execute a transaction and return the commitment
15+
///
16+
/// This is the main entry point for ZK execution.
17+
/// It takes an ExecutionInput and returns either:
18+
/// - Ok((output, commitment)) on success
19+
/// - Err(error) on failure
20+
///
21+
/// # ZK Properties
22+
/// - Execution is deterministic
23+
/// - Same input always produces same output
24+
/// - Commitment binds input to output cryptographically
25+
pub fn execute(input: ExecutionInput) -> Result<(ExecutionOutput, ExecutionCommitment)> {
26+
// Use the core executor
27+
ShadowExecutor::execute(input)
28+
}
29+
}

zkvm/guest/src/evm/host_io.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//! Host I/O helpers for RISC Zero guest
2+
//!
3+
//! Provides functions for reading inputs from the host and
4+
//! committing outputs to the journal.
5+
6+
use risc0_zkvm::guest::env;
7+
use shadow_evm_core::prelude::*;
8+
9+
/// Read ExecutionInput from the host
10+
///
11+
/// Deserializes the execution input that was provided by the host.
12+
/// This input contains:
13+
/// - Block environment (number, timestamp, gas limit, etc.)
14+
/// - Transaction input (caller, to, value, data, etc.)
15+
/// - Pre-execution state
16+
pub fn read_input() -> ExecutionInput {
17+
env::read()
18+
}
19+
20+
/// Commit the execution commitment to the journal
21+
///
22+
/// The journal is the public output of the ZK proof.
23+
/// Anyone can read the journal after verification.
24+
///
25+
/// We commit the ExecutionCommitment which contains:
26+
/// - Input hash
27+
/// - Output hash
28+
/// - Pre-state root
29+
/// - Post-state root
30+
/// - Combined commitment
31+
pub fn commit_output(commitment: &ExecutionCommitment) {
32+
env::commit(commitment);
33+
}

zkvm/guest/src/evm/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//! EVM execution module for ZKVM guest
2+
//!
3+
//! This module provides the EVM execution logic that runs inside the RISC-V ZK-VM.
4+
5+
pub mod executor;
6+
pub mod host_io;
7+
pub mod state;
8+
9+
// Re-exports
10+
pub use executor::ZkExecutor;
11+
pub use host_io::{commit_output, read_input};

zkvm/guest/src/evm/state.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//! ZK-compatible state wrapper
2+
//!
3+
//! Provides a wrapper around the core InMemoryDB that ensures
4+
//! all state operations are deterministic and ZK-friendly.
5+
6+
use shadow_evm_core::prelude::*;
7+
8+
/// ZK-compatible state wrapper
9+
///
10+
/// This wrapper ensures all state operations are deterministic
11+
/// and suitable for execution inside a ZK-VM.
12+
pub struct ZkState {
13+
/// The underlying in-memory database
14+
db: InMemoryDB,
15+
}
16+
17+
impl ZkState {
18+
/// Create a new ZK state from an InMemoryDB
19+
pub fn new(db: InMemoryDB) -> Self {
20+
Self { db }
21+
}
22+
23+
/// Get the underlying database
24+
pub fn into_db(self) -> InMemoryDB {
25+
self.db
26+
}
27+
28+
/// Get a reference to the underlying database
29+
pub fn db(&self) -> &InMemoryDB {
30+
&self.db
31+
}
32+
33+
/// Get a mutable reference to the underlying database
34+
pub fn db_mut(&mut self) -> &mut InMemoryDB {
35+
&mut self.db
36+
}
37+
38+
/// Compute the state root
39+
pub fn state_root(&self) -> Hash {
40+
self.db.compute_state_root()
41+
}
42+
43+
/// Verify the state root matches expected value
44+
pub fn verify_root(&self, expected: &Hash) -> bool {
45+
self.state_root() == *expected
46+
}
47+
}
48+
49+
impl From<InMemoryDB> for ZkState {
50+
fn from(db: InMemoryDB) -> Self {
51+
Self::new(db)
52+
}
53+
}
54+
55+
impl From<ZkState> for InMemoryDB {
56+
fn from(state: ZkState) -> Self {
57+
state.into_db()
58+
}
59+
}

zkvm/guest/src/main.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//! Shadow-EVM ZKVM Guest Entry Point
2+
//!
3+
//! This is the main program that runs inside the RISC-V ZK-VM.
4+
//! It reads an ExecutionInput from the host, executes the EVM transaction,
5+
//! and commits the ExecutionCommitment to the journal.
6+
//!
7+
//! # Flow
8+
//! 1. Host serializes ExecutionInput and passes it to the guest
9+
//! 2. Guest deserializes and executes using ShadowExecutor
10+
//! 3. Guest computes ExecutionCommitment
11+
//! 4. Guest commits the commitment to the journal
12+
//! 5. Prover generates a proof of correct execution
13+
//!
14+
//! # Public Output (Journal)
15+
//! The journal contains the ExecutionCommitment which includes:
16+
//! - input_hash: Hash of the execution input
17+
//! - output_hash: Hash of the execution output
18+
//! - pre_state_root: Merkle root of pre-execution state
19+
//! - post_state_root: Merkle root of post-execution state
20+
//! - commitment: Combined cryptographic commitment
21+
22+
#![no_main]
23+
#![no_std]
24+
25+
extern crate alloc;
26+
27+
mod evm;
28+
mod types;
29+
30+
use evm::{commit_output, read_input, ZkExecutor};
31+
32+
risc0_zkvm::guest::entry!(main);
33+
34+
/// Guest main entry point
35+
///
36+
/// This function is called when the ZK-VM starts executing.
37+
/// Any panic will cause the proof generation to fail.
38+
fn main() {
39+
// Step 1: Read the execution input from the host
40+
let input = read_input();
41+
42+
// Step 2: Execute the EVM transaction
43+
// This uses the core ShadowExecutor which wraps revm
44+
let result = ZkExecutor::execute(input);
45+
46+
// Step 3: Handle the result and commit to journal
47+
match result {
48+
Ok((_output, commitment)) => {
49+
// Commit the execution commitment to the journal
50+
// This becomes the public output of the ZK proof
51+
commit_output(&commitment);
52+
}
53+
Err(_err) => {
54+
// Execution failed - panic to abort proof generation
55+
// The host should validate inputs before proving
56+
panic!("EVM execution failed");
57+
}
58+
}
59+
}

zkvm/guest/src/types.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//! Type re-exports for ZKVM guest
2+
//!
3+
//! Re-exports all types from shadow-evm-core for convenient use in the guest.
4+
5+
#![allow(unused_imports)]
6+
7+
// Re-export all core types
8+
pub use shadow_evm_core::prelude::*;
9+
10+
// Re-export additional types that might be needed
11+
pub use shadow_evm_core::{compute_commitment, hash_struct, keccak256, VERSION};

zkvm/host/Cargo.toml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "shadow-evm-host"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "RISC Zero host program for Shadow-EVM ZK Coprocessor"
6+
7+
[dependencies]
8+
# Core Shadow-EVM library
9+
shadow-evm-core = { path = "../../core" }
10+
11+
# RISC Zero host SDK
12+
risc0-zkvm = { version = "1.2" }
13+
14+
# Serialization
15+
serde = { version = "1.0", features = ["derive"] }
16+
serde_json = "1.0"
17+
bincode = "1.3"
18+
19+
# CLI
20+
clap = { version = "4.0", features = ["derive"] }
21+
22+
# Async runtime
23+
tokio = { version = "1", features = ["full"] }
24+
25+
# Error handling
26+
anyhow = "1.0"
27+
28+
# Hex encoding for display
29+
hex = "0.4"
30+
31+
[build-dependencies]
32+
risc0-build = { version = "1.2" }

zkvm/host/build.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! Build script for Shadow-EVM host
2+
//!
3+
//! Compiles the guest program and generates the methods.rs file
4+
//! containing the ELF binary and image ID.
5+
6+
fn main() {
7+
// Tell Cargo to rerun if guest code changes
8+
println!("cargo:rerun-if-changed=../guest/src");
9+
println!("cargo:rerun-if-changed=../guest/Cargo.toml");
10+
11+
// Build the guest program
12+
risc0_build::embed_methods();
13+
}

zkvm/host/src/io.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//! I/O utilities for the host
2+
//!
3+
//! Handles serialization/deserialization of inputs and outputs
4+
//! for communication with the guest and external systems.
5+
6+
use anyhow::{Context, Result};
7+
use shadow_evm_core::prelude::*;
8+
use std::fs;
9+
use std::path::Path;
10+
11+
/// Serialize ExecutionInput to bytes
12+
pub fn serialize_input(input: &ExecutionInput) -> Result<Vec<u8>> {
13+
bincode::serialize(input).context("Failed to serialize ExecutionInput")
14+
}
15+
16+
/// Deserialize ExecutionInput from bytes
17+
pub fn deserialize_input(data: &[u8]) -> Result<ExecutionInput> {
18+
bincode::deserialize(data).context("Failed to deserialize ExecutionInput")
19+
}
20+
21+
/// Serialize ExecutionCommitment to bytes
22+
pub fn serialize_commitment(commitment: &ExecutionCommitment) -> Result<Vec<u8>> {
23+
bincode::serialize(commitment).context("Failed to serialize ExecutionCommitment")
24+
}
25+
26+
/// Deserialize ExecutionCommitment from bytes
27+
pub fn deserialize_commitment(data: &[u8]) -> Result<ExecutionCommitment> {
28+
bincode::deserialize(data).context("Failed to deserialize ExecutionCommitment")
29+
}
30+
31+
/// Save ExecutionInput to a JSON file
32+
pub fn save_input_json<P: AsRef<Path>>(input: &ExecutionInput, path: P) -> Result<()> {
33+
let json = serde_json::to_string_pretty(input)?;
34+
fs::write(path, json)?;
35+
Ok(())
36+
}
37+
38+
/// Load ExecutionInput from a JSON file
39+
pub fn load_input_json<P: AsRef<Path>>(path: P) -> Result<ExecutionInput> {
40+
let json = fs::read_to_string(path)?;
41+
let input: ExecutionInput = serde_json::from_str(&json)?;
42+
Ok(input)
43+
}
44+
45+
/// Save binary data to a file
46+
pub fn save_bytes<P: AsRef<Path>>(data: &[u8], path: P) -> Result<()> {
47+
fs::write(path, data)?;
48+
Ok(())
49+
}
50+
51+
/// Load binary data from a file
52+
pub fn load_bytes<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
53+
let data = fs::read(path)?;
54+
Ok(data)
55+
}
56+
57+
/// Format a hash as a hex string
58+
pub fn format_hash(hash: &Hash) -> String {
59+
format!("0x{}", hex::encode(hash.as_slice()))
60+
}
61+
62+
/// Format commitment for display
63+
pub fn format_commitment(commitment: &ExecutionCommitment) -> String {
64+
format!(
65+
"ExecutionCommitment {{\n \
66+
input_hash: {},\n \
67+
output_hash: {},\n \
68+
pre_state_root: {},\n \
69+
post_state_root: {},\n \
70+
commitment: {}\n\
71+
}}",
72+
format_hash(&commitment.input_hash),
73+
format_hash(&commitment.output_hash),
74+
format_hash(&commitment.pre_state_root),
75+
format_hash(&commitment.post_state_root),
76+
format_hash(&commitment.commitment),
77+
)
78+
}
79+
80+
#[cfg(test)]
81+
mod tests {
82+
use super::*;
83+
84+
#[test]
85+
fn test_serialize_roundtrip() {
86+
let input = ExecutionInput::new(BlockEnv::default(), TxInput::default(), InMemoryDB::new());
87+
88+
let bytes = serialize_input(&input).unwrap();
89+
let decoded = deserialize_input(&bytes).unwrap();
90+
91+
assert_eq!(input.hash(), decoded.hash());
92+
}
93+
}

0 commit comments

Comments
 (0)