From f0eab2969e1653b52e33a32872b04bc61e9d87fa Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 8 Sep 2025 12:46:04 +0000 Subject: [PATCH] Add revm example This example shows how to execute EVM bytecode inside the RISC Zero zkVM using the revm library. This demonstrates how a Zero Knowledge Bug Bounty Program concept can be achieved, where a whitehat can prove they know how to construct an attack without revelating the transaction information. Ported from risc0/risc0#3412. --- revm/Cargo.toml | 17 ++++++ revm/Contract.sol | 13 +++++ revm/README.md | 38 +++++++++++++ revm/core/Cargo.toml | 12 +++++ revm/core/build.rs | 68 ++++++++++++++++++++++++ revm/core/src/abi.rs | 46 ++++++++++++++++ revm/core/src/config.rs | 60 +++++++++++++++++++++ revm/core/src/lib.rs | 91 +++++++++++++++++++++++++++++++ revm/methods/Cargo.toml | 10 ++++ revm/methods/build.rs | 17 ++++++ revm/methods/guest/Cargo.toml | 12 +++++ revm/methods/guest/src/main.rs | 97 ++++++++++++++++++++++++++++++++++ revm/methods/src/lib.rs | 15 ++++++ revm/src/main.rs | 52 ++++++++++++++++++ 14 files changed, 548 insertions(+) create mode 100644 revm/Cargo.toml create mode 100644 revm/Contract.sol create mode 100644 revm/README.md create mode 100644 revm/core/Cargo.toml create mode 100644 revm/core/build.rs create mode 100644 revm/core/src/abi.rs create mode 100644 revm/core/src/config.rs create mode 100644 revm/core/src/lib.rs create mode 100644 revm/methods/Cargo.toml create mode 100644 revm/methods/build.rs create mode 100644 revm/methods/guest/Cargo.toml create mode 100644 revm/methods/guest/src/main.rs create mode 100644 revm/methods/src/lib.rs create mode 100644 revm/src/main.rs diff --git a/revm/Cargo.toml b/revm/Cargo.toml new file mode 100644 index 0000000..b667426 --- /dev/null +++ b/revm/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "revm" +version = "0.1.0" +edition = "2021" + +[dependencies] +revm-core = { path = "core" } +revm-methods = { path = "methods" } +risc0-zkvm = "3.0" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +hex = "0.4" +sha3 = "0.10" + +[features] +cuda = ["risc0-zkvm/cuda"] +default = [] +prove = ["risc0-zkvm/prove"] \ No newline at end of file diff --git a/revm/Contract.sol b/revm/Contract.sol new file mode 100644 index 0000000..d378445 --- /dev/null +++ b/revm/Contract.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +contract Contract { + function isSolved(int256 x) external pure returns (bool) { + // p(x) = x^4 - 84x^3 + 1765x^2 - 84x + 1764 + return ((x**4) + - (84 * (x**3)) + + (1765 * (x**2)) + - (84 * x) + + 1764) == 0; + } +} diff --git a/revm/README.md b/revm/README.md new file mode 100644 index 0000000..dadf3ed --- /dev/null +++ b/revm/README.md @@ -0,0 +1,38 @@ +# REVM Example + +This example demonstrates how to execute EVM bytecode inside the RISC Zero zkVM using the [revm](https://github.com/bluealloy/revm) library. + +## Overview + +This example shows a possible integration between RISC Zero and revm: + +1. **Host**: Prepares simple EVM bytecode and sends it to the zkVM +2. **Guest**: Uses revm to process the bytecode inside the zkVM +3. **Verification**: The host verifies the proof that the bytecode was executed + +The original motivation for this example is a Zero Knowledge Bug Bounty Program, in which a whitehat can prove they know how to construct an attack to extract funds from a contract without revealing the transaction information, minimizing trust issues with protocols and revealing the information only after a payout. + +## Quick Start + +First, make sure [rustup](https://rustup.rs) is installed and you have the +required RISC Zero toolchain installed. + +To build all methods and execute the method within the zkVM, run the following +command: + +```bash +cargo run +``` + +## Use Cases + +This demonstrates the foundation for more complex EVM-based applications in zkVM: + +- **Private smart contract execution**: Execute smart contracts privately while proving correctness +- **EVM compatibility layers**: Build EVM-compatible rollups or sidechains +- **Cross-chain verification**: Prove execution of Ethereum transactions on other chains +- **Auditing tools**: Verify contract behavior without revealing sensitive inputs + +## Acknowledgments + +This project was built as part of the [EF Core Program Brazil 2025](https://github.com/erc55/core-program-2025). \ No newline at end of file diff --git a/revm/core/Cargo.toml b/revm/core/Cargo.toml new file mode 100644 index 0000000..1485971 --- /dev/null +++ b/revm/core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "revm-core" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +revm-methods = { path = "../methods" } +risc0-zkvm = "3.0" +serde = { version = "1.0", default-features = false } +hex = "0.4" +sha3 = "0.10" \ No newline at end of file diff --git a/revm/core/build.rs b/revm/core/build.rs new file mode 100644 index 0000000..ed0506c --- /dev/null +++ b/revm/core/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs; +use std::path::Path; +use std::process::Command; + +fn main() { + println!("cargo:rerun-if-changed=../Contract.sol"); + println!("cargo:rerun-if-changed=build.rs"); + + let out_dir = env::var("OUT_DIR").unwrap(); + let contract_path = Path::new("../Contract.sol"); + + // Check if solc is available + let solc_check = Command::new("solc") + .arg("--version") + .output(); + + if solc_check.is_err() { + panic!("solc not found! Please install solc (Solidity compiler)"); + } + + // Compile the contract to get the bytecode + let output = Command::new("solc") + .arg("--bin") + .arg("--optimize") + .arg(contract_path) + .output() + .expect("Failed to execute solc"); + + if !output.status.success() { + panic!("solc compilation failed: {}", String::from_utf8_lossy(&output.stderr)); + } + + let output_str = String::from_utf8_lossy(&output.stdout); + + // Parse the output to extract the bytecode + let lines: Vec<&str> = output_str.lines().collect(); + let mut bytecode = String::new(); + let mut found_binary = false; + + for line in lines { + if line.contains("Binary:") { + found_binary = true; + continue; + } + if found_binary && !line.is_empty() && !line.starts_with('=') { + bytecode = line.to_string(); + break; + } + } + + if bytecode.is_empty() { + panic!("Failed to extract bytecode from solc output"); + } + + // Generate Rust code with the bytecode + let generated_code = format!( + r#"// Auto-generated bytecode from Contract.sol +pub const CONTRACT_BYTECODE: &str = "{}"; +"#, + bytecode + ); + + let dest_path = Path::new(&out_dir).join("contract_bytecode.rs"); + fs::write(&dest_path, generated_code).expect("Failed to write generated bytecode file"); + + println!("cargo:rustc-env=CONTRACT_BYTECODE_PATH={}", dest_path.display()); +} \ No newline at end of file diff --git a/revm/core/src/abi.rs b/revm/core/src/abi.rs new file mode 100644 index 0000000..a9fa95f --- /dev/null +++ b/revm/core/src/abi.rs @@ -0,0 +1,46 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sha3::{Digest, Keccak256}; + +fn keccak256(data: &[u8]) -> [u8; 32] { + let mut hasher = Keccak256::new(); + hasher.update(data); + hasher.finalize().into() +} + +fn encode_int256(value: i64) -> Vec { + let mut result = vec![0u8; 32]; + if value < 0 { + for byte in result.iter_mut() { + *byte = 0xFF; + } + } + let bytes = value.to_be_bytes(); + for (i, &byte) in bytes.iter().enumerate() { + result[24 + i] = byte; + } + result +} + +pub fn abi_encode(function_signature: &str, value: i64) -> Vec { + let signature_hash = keccak256(function_signature.as_bytes()); + let selector = &signature_hash[..4]; + + let argument = encode_int256(value); + let mut calldata = Vec::from(selector); + calldata.extend(argument); + + calldata +} \ No newline at end of file diff --git a/revm/core/src/config.rs b/revm/core/src/config.rs new file mode 100644 index 0000000..d965ce7 --- /dev/null +++ b/revm/core/src/config.rs @@ -0,0 +1,60 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// EVM bytecode configuration + +use serde::{Deserialize, Serialize}; +use crate::abi::abi_encode; + +// Include the auto-generated bytecode +include!(concat!(env!("OUT_DIR"), "/contract_bytecode.rs")); + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct EvmConfig { + pub bytecode: Vec, + pub calldata: Vec, +} + +impl Default for EvmConfig { + fn default() -> Self { + Self { + // Bytecode compiled from Contract.sol at build time + bytecode: hex::decode(CONTRACT_BYTECODE).unwrap(), + calldata: abi_encode("isSolved(int256)", 42), + } + } +} + +impl EvmConfig { + /// Create a new EVM config with custom bytecode + pub fn new(bytecode_hex: &str, calldata: Vec) -> Result { + let bytecode = hex::decode(bytecode_hex)?; + Ok(Self { bytecode, calldata }) + } + + /// Get the bytecode as a reference + pub fn get_bytecode(&self) -> &[u8] { + &self.bytecode + } + + /// Get the bytecode as owned Vec + pub fn get_bytecode_owned(&self) -> Vec { + self.bytecode.clone() + } + + /// Get the calldata + pub fn get_calldata(&self) -> &[u8] { + &self.calldata + } +} diff --git a/revm/core/src/lib.rs b/revm/core/src/lib.rs new file mode 100644 index 0000000..948a8c5 --- /dev/null +++ b/revm/core/src/lib.rs @@ -0,0 +1,91 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![doc = include_str!("../../README.md")] + +use revm_methods::REVM_GUEST_ELF; +use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; +use config::EvmConfig; +use sha3::{Digest, Keccak256}; + +pub mod abi; +pub mod config; + +// This is a simple EVM demo for the RISC Zero zkVM. +// By running the demo, you can prove that you executed specific EVM bytecode +// inside the zkVM, providing cryptographic proof of the execution. + +// Execute EVM bytecode inside the zkVM +pub fn execute_evm_bytecode(config: EvmConfig) -> (Receipt, bool) { + let env = ExecutorEnv::builder() + // Send config to the guest + .write(&config) + .unwrap() + .build() + .unwrap(); + + // Obtain the default prover. + let prover = default_prover(); + + // Produce a receipt by proving the specified ELF binary. + let receipt = prover.prove(env, REVM_GUEST_ELF).unwrap().receipt; + + // Extract journal of receipt (now contains hashes instead of raw data) + let public_state: (Vec, Vec, bool) = receipt.journal.decode().expect( + "Journal output should deserialize into the same types (& order) that it was written", + ); + + // Compute expected hashes + let expected_bytecode_hash = Keccak256::digest(&config.bytecode); + let expected_calldata_hash = Keccak256::digest(&config.calldata); + + // Compare hashes instead of raw data + assert_eq!(public_state.0, expected_bytecode_hash.to_vec(), "Bytecode hash mismatch"); + assert_eq!(public_state.1, expected_calldata_hash.to_vec(), "Calldata hash mismatch"); + + // Report the result + println!( + "Successfully executed EVM bytecode inside zkVM! Return value: {:02x?}", + public_state.2 + ); + println!( + "Bytecode hash: 0x{}", + hex::encode(&public_state.0) + ); + println!( + "Calldata hash: 0x{}", + hex::encode(&public_state.1) + ); + + (receipt, public_state.2) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple_bytecode() { + // Use configuration from the core crate + let config = config::EvmConfig::default(); + + let (_, result) = execute_evm_bytecode(config); + // The expected return value should be TRUE + let expected_return = true; + assert_eq!( + result, expected_return, + "We expect the zkVM output to be the actual return value from EVM execution" + ); + } +} diff --git a/revm/methods/Cargo.toml b/revm/methods/Cargo.toml new file mode 100644 index 0000000..c11134c --- /dev/null +++ b/revm/methods/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "revm-methods" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = "3.0" + +[package.metadata.risc0] +methods = ["guest"] \ No newline at end of file diff --git a/revm/methods/build.rs b/revm/methods/build.rs new file mode 100644 index 0000000..001422f --- /dev/null +++ b/revm/methods/build.rs @@ -0,0 +1,17 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +fn main() { + risc0_build::embed_methods(); +} \ No newline at end of file diff --git a/revm/methods/guest/Cargo.toml b/revm/methods/guest/Cargo.toml new file mode 100644 index 0000000..13e83ee --- /dev/null +++ b/revm/methods/guest/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "revm-guest" +version = "0.12.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "3.0", default-features = false, features = ["std"] } +revm = { version = "14", default-features = false, features = ["std", "optimism"] } +sha3 = "0.10" +serde = { version = "1.0", default-features = false, features = ["derive"] } \ No newline at end of file diff --git a/revm/methods/guest/src/main.rs b/revm/methods/guest/src/main.rs new file mode 100644 index 0000000..c12fec8 --- /dev/null +++ b/revm/methods/guest/src/main.rs @@ -0,0 +1,97 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] +#![no_std] + +use revm::{ + primitives::{Bytes, TxKind, Address, U256, ExecutionResult, Output}, + InMemoryDB, Evm, +}; +use risc0_zkvm::guest::env; +use sha3::{Digest, Keccak256}; + +extern crate alloc; +use alloc::vec::Vec; + +// Define the EvmConfig struct locally with serde support +#[derive(Clone, Debug, serde::Deserialize)] +struct EvmConfig { + bytecode: Vec, + calldata: Vec, +} + +risc0_zkvm::guest::entry!(main); + +fn main() { + // Read the bytecode from the host + let config: EvmConfig = env::read(); + + // Create a new EVM instance with in-memory database + let mut evm = Evm::builder() + .with_db(InMemoryDB::default()) + .build(); + + // Compute keccak256 hashes of bytecode and calldata + let bytecode_hash = Keccak256::digest(&config.bytecode); + let calldata_hash = Keccak256::digest(&config.calldata); + + // Deploy the contract + evm.tx_mut().transact_to = TxKind::Create; + evm.tx_mut().data = Bytes::from(config.bytecode.clone()); + let deploy_result = evm.transact_commit().unwrap(); + + // Extract the deployed contract address + let contract_address = match deploy_result { + ExecutionResult::Success { + output: Output::Create(_, Some(addr)), + .. + } => addr, + _ => { + // If deployment failed, commit with error (using hashes) + env::commit(&(bytecode_hash.to_vec(), calldata_hash.to_vec(), false)); + return; + } + }; + + // Use the calldata directly from config + let calldata = config.calldata.clone(); + + // Call the function + evm.tx_mut().transact_to = TxKind::Call(contract_address); + evm.tx_mut().data = Bytes::from(calldata); + evm.tx_mut().nonce = Some(1); + let call_result = evm.transact_commit().unwrap(); + + // Extract the return value from the transaction result + let is_solved = match call_result { + ExecutionResult::Success { output, .. } => { + match output { + Output::Call(bytes) => { + // Check if any byte is non-zero (true) + bytes.iter().any(|&byte| byte != 0) + } + _ => false, + } + } + _ => false, + }; + + // Commit the hashes instead of raw data + env::commit(&( + bytecode_hash.to_vec(), + calldata_hash.to_vec(), + is_solved + )); +} \ No newline at end of file diff --git a/revm/methods/src/lib.rs b/revm/methods/src/lib.rs new file mode 100644 index 0000000..21784d6 --- /dev/null +++ b/revm/methods/src/lib.rs @@ -0,0 +1,15 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +include!(concat!(env!("OUT_DIR"), "/methods.rs")); \ No newline at end of file diff --git a/revm/src/main.rs b/revm/src/main.rs new file mode 100644 index 0000000..e710bb4 --- /dev/null +++ b/revm/src/main.rs @@ -0,0 +1,52 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use revm_core::config::EvmConfig; +use revm_core::execute_evm_bytecode; +use revm_methods::REVM_GUEST_ID; + +fn main() { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + // Load EVM configuration + let config = EvmConfig::default(); + + // Compute and display hashes instead of raw data for privacy + use sha3::{Digest, Keccak256}; + let bytecode_hash = Keccak256::digest(config.get_bytecode()); + let calldata_hash = Keccak256::digest(config.get_calldata()); + + println!( + "Executing EVM bytecode hash: 0x{} with calldata hash: 0x{}", + hex::encode(bytecode_hash), + hex::encode(calldata_hash) + ); + + // Execute the bytecode inside the zkVM + let (receipt, return_value) = execute_evm_bytecode(config); + + // Here is where one would send 'receipt' over the network... + + // Verify receipt, panic if it's wrong + receipt.verify(REVM_GUEST_ID).expect( + "Code you have proven should successfully verify; did you specify the correct image ID?", + ); + + println!( + "✓ Receipt verified! EVM bytecode returned: {:02x?}", + return_value + ); +}