Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions revm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
13 changes: 13 additions & 0 deletions revm/Contract.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
38 changes: 38 additions & 0 deletions revm/README.md
Original file line number Diff line number Diff line change
@@ -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).
12 changes: 12 additions & 0 deletions revm/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
68 changes: 68 additions & 0 deletions revm/core/build.rs
Original file line number Diff line number Diff line change
@@ -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());
}
46 changes: 46 additions & 0 deletions revm/core/src/abi.rs
Original file line number Diff line number Diff line change
@@ -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<u8> {
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<u8> {
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
}
60 changes: 60 additions & 0 deletions revm/core/src/config.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
pub calldata: Vec<u8>,
}

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<u8>) -> Result<Self, hex::FromHexError> {
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<u8> {
self.bytecode.clone()
}

/// Get the calldata
pub fn get_calldata(&self) -> &[u8] {
&self.calldata
}
}
91 changes: 91 additions & 0 deletions revm/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<u8>, Vec<u8>, 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"
);
}
}
10 changes: 10 additions & 0 deletions revm/methods/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
17 changes: 17 additions & 0 deletions revm/methods/build.rs
Original file line number Diff line number Diff line change
@@ -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();
}
Loading