mirror of
https://github.com/eth-act/ere.git
synced 2026-02-19 11:54:42 -05:00
Risc0 docker compilation (#58)
This commit is contained in:
@@ -1 +1 @@
|
||||
/target
|
||||
**/target
|
||||
6
.github/workflows/rust-checks.yml
vendored
6
.github/workflows/rust-checks.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
crate: [ere-sp1]
|
||||
crate: [ere-sp1, ere-risc0]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -51,9 +51,9 @@ jobs:
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
|
||||
- name: Check clippy
|
||||
run: cargo clippy --bins --lib --examples --tests --benches --all-features -p ${{ matrix.crate }}
|
||||
run: cargo clippy --bins --lib --examples --tests --benches -p ${{ matrix.crate }}
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --release -p ${{ matrix.crate }}
|
||||
|
||||
33
.github/workflows/test-risc0-docker.yml
vendored
33
.github/workflows/test-risc0-docker.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Test Risc0 (Docker)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test-risc0-via-docker-build:
|
||||
name: Build Risc0 Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build ere-base image
|
||||
run: |
|
||||
docker build \
|
||||
--tag ere-base:latest \
|
||||
--file docker/base/Dockerfile.base .
|
||||
|
||||
- name: Build ere-builder-risc0 image
|
||||
run: |
|
||||
docker build \
|
||||
--tag ere-builder-risc0:latest \
|
||||
--file docker/risc0/Dockerfile .
|
||||
31
Cargo.lock
generated
31
Cargo.lock
generated
@@ -2336,18 +2336,21 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ere-risczero"
|
||||
name = "ere-risc0"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"borsh",
|
||||
"build-utils",
|
||||
"bytemuck",
|
||||
"hex",
|
||||
"risc0-zkvm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"zkvm-interface",
|
||||
]
|
||||
|
||||
@@ -4052,9 +4055,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.5"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -7804,6 +7807,22 @@ dependencies = [
|
||||
"sppark",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "risc0-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"borsh",
|
||||
"clap",
|
||||
"hex",
|
||||
"risc0-zkvm",
|
||||
"tempfile",
|
||||
"toml 0.8.22",
|
||||
"tracing",
|
||||
"zkvm-interface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "risc0-core"
|
||||
version = "2.0.0"
|
||||
@@ -8105,7 +8124,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9186,9 +9205,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sppark"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16bf457036c0a778140ce4c3bcf9ff30c5c70a9d9c0bb04fe513af025b647b2c"
|
||||
checksum = "6bdc4f02f557e3037bbe2a379cac8be6e014a67beb7bf0996b536979392f6361"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"which",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
"crates/ere-nexus",
|
||||
"crates/ere-openvm",
|
||||
"crates/ere-pico",
|
||||
"crates/ere-risczero",
|
||||
"crates/ere-risc0",
|
||||
"crates/ere-sp1",
|
||||
"crates/ere-zisk",
|
||||
# zkVM interface
|
||||
@@ -14,6 +14,7 @@ members = [
|
||||
|
||||
# Guest compilers
|
||||
"docker/sp1",
|
||||
"docker/risc0",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -31,6 +32,7 @@ tempfile = "3.3"
|
||||
toml = "0.8"
|
||||
clap = { version = "4.5.41", features = ["derive"] }
|
||||
anyhow = "1.0"
|
||||
hex = "0.4.3"
|
||||
|
||||
# local dependencies
|
||||
zkvm-interface = { path = "crates/zkvm-interface" }
|
||||
|
||||
29
Makefile
29
Makefile
@@ -1,29 +0,0 @@
|
||||
# Heavily inspired by Reth: https://github.com/paradigmxyz/reth/blob/4c39b98b621c53524c6533a9c7b52fc42c25abd6/Makefile
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
##@ Help
|
||||
.PHONY: help
|
||||
help: # Display this help.
|
||||
@awk 'BEGIN {FS = ":.*#"; printf "Usage:\n make \033[34m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?#/ { printf " \033[34m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
|
||||
|
||||
##@ Build
|
||||
.PHONY: build
|
||||
build: # Build the Ream binary into `target` directory.
|
||||
@cargo build --verbose --release
|
||||
|
||||
|
||||
##@ Lint
|
||||
.PHONY: clean
|
||||
clean: # Run `cargo clean`.
|
||||
@cargo clean
|
||||
|
||||
.PHONY: lint pr
|
||||
lint: # Run `clippy` and `rustfmt`.
|
||||
cargo +nightly fmt --all
|
||||
cargo clippy --all --all-targets --no-deps -- --deny warnings
|
||||
|
||||
# clippy for bls with supranational feature
|
||||
cargo clippy --all-targets --no-deps -- --deny warnings
|
||||
|
||||
# cargo sort
|
||||
cargo sort --grouped
|
||||
@@ -6,6 +6,18 @@ use std::{
|
||||
use thiserror::Error;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid Dockerfile path: {0}")]
|
||||
InvalidDockerfilePath(PathBuf),
|
||||
#[error("Docker image build failed: {0}")]
|
||||
DockerBuildFailed(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("Docker image build failed")]
|
||||
ImageBuildFailed,
|
||||
#[error("Docker is not available. Please ensure Docker is installed and running.")]
|
||||
DockerIsNotAvailable,
|
||||
}
|
||||
|
||||
pub fn build_image(compiler_dockerfile: &Path, tag: &str) -> Result<(), Error> {
|
||||
// Check that Docker is installed and available
|
||||
if Command::new("docker")
|
||||
@@ -72,14 +84,62 @@ pub fn build_image(compiler_dockerfile: &Path, tag: &str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid Dockerfile path: {0}")]
|
||||
InvalidDockerfilePath(PathBuf),
|
||||
#[error("Docker image build failed: {0}")]
|
||||
DockerBuildFailed(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("Docker image build failed")]
|
||||
ImageBuildFailed,
|
||||
#[error("Docker is not available. Please ensure Docker is installed and running.")]
|
||||
DockerIsNotAvailable,
|
||||
#[derive(Debug)]
|
||||
pub struct DockerRunCommand {
|
||||
image: String,
|
||||
volumes: Vec<(String, String)>, // (host_path, container_path)
|
||||
command: Vec<String>,
|
||||
// remove image after running
|
||||
remove_after: bool,
|
||||
}
|
||||
|
||||
impl DockerRunCommand {
|
||||
pub fn new(image: impl Into<String>) -> Self {
|
||||
Self {
|
||||
image: image.into(),
|
||||
volumes: Vec::new(),
|
||||
command: Vec::new(),
|
||||
remove_after: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_volume(
|
||||
mut self,
|
||||
host_path: impl Into<String>,
|
||||
container_path: impl Into<String>,
|
||||
) -> Self {
|
||||
self.volumes.push((host_path.into(), container_path.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_command(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
||||
self.command.extend(args.into_iter().map(|s| s.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remove_after_run(mut self) -> Self {
|
||||
self.remove_after = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_args(&self) -> Vec<String> {
|
||||
let mut args = vec!["run".to_string()];
|
||||
|
||||
if self.remove_after {
|
||||
args.push("--rm".to_string());
|
||||
}
|
||||
|
||||
for (host_path, container_path) in &self.volumes {
|
||||
args.extend(["-v".to_string(), format!("{host_path}:{container_path}")]);
|
||||
}
|
||||
|
||||
args.push(self.image.clone());
|
||||
args.extend(self.command.iter().cloned());
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<std::process::ExitStatus, std::io::Error> {
|
||||
Command::new("docker").args(self.to_args()).status()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "ere-risczero"
|
||||
name = "ere-risc0"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -7,9 +7,9 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
zkvm-interface = { workspace = true }
|
||||
anyhow = "1.0" #TODO: remove only needed in tests
|
||||
#toml = "0.8"
|
||||
risc0-zkvm = { version = "2.3.0", features = ["unstable"] }
|
||||
build-utils = { workspace = true }
|
||||
anyhow = "1.0"
|
||||
risc0-zkvm = { version = "^2.3.0", features = ["unstable"] }
|
||||
borsh = "1.5.7"
|
||||
hex = "*"
|
||||
|
||||
@@ -17,6 +17,9 @@ tempfile = "3.3"
|
||||
serde_json = "1.0"
|
||||
thiserror = "2"
|
||||
serde = { version = "1.0.219", features = ["derive", "rc"] }
|
||||
tracing = "0.1"
|
||||
bytemuck = "1.13"
|
||||
bincode = "1.3"
|
||||
|
||||
[build-dependencies]
|
||||
build-utils = { workspace = true }
|
||||
111
crates/ere-risc0/src/compile.rs
Normal file
111
crates/ere-risc0/src/compile.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use crate::error::CompileError;
|
||||
use build_utils::docker;
|
||||
use risc0_zkvm::Digest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Risc0Program {
|
||||
// TODO: Seems like the risc0 compilation is also compiling
|
||||
// TODO: the analogous prover and verifying key
|
||||
pub(crate) elf: Vec<u8>,
|
||||
pub(crate) image_id: Digest,
|
||||
}
|
||||
|
||||
pub fn compile_risc0_program(
|
||||
workspace_directory: &Path,
|
||||
guest_program_relative: &Path,
|
||||
) -> Result<Risc0Program, CompileError> {
|
||||
// Build the SP1 docker image
|
||||
let tag = "ere-risc0-cli:latest";
|
||||
docker::build_image(&PathBuf::from("docker/risc0/Dockerfile"), tag)
|
||||
.map_err(|e| CompileError::DockerImageBuildFailed(Box::new(e)))?;
|
||||
|
||||
// Prepare paths for compilation
|
||||
let mount_directory_str = workspace_directory
|
||||
.to_str()
|
||||
.ok_or_else(|| CompileError::InvalidMountPath(workspace_directory.to_path_buf()))?;
|
||||
|
||||
let elf_output_dir = TempDir::new().map_err(CompileError::CreatingTempOutputDirectoryFailed)?;
|
||||
let elf_output_dir_str = elf_output_dir
|
||||
.path()
|
||||
.to_str()
|
||||
.ok_or_else(|| CompileError::InvalidTempOutputPath(elf_output_dir.path().to_path_buf()))?;
|
||||
|
||||
let container_mount_directory = PathBuf::from_str("/guest-workspace").unwrap();
|
||||
let container_guest_program_path = container_mount_directory.join(guest_program_relative);
|
||||
let container_guest_program_str = container_guest_program_path
|
||||
.to_str()
|
||||
.ok_or_else(|| CompileError::InvalidGuestPath(guest_program_relative.to_path_buf()))?;
|
||||
|
||||
info!(
|
||||
"Compiling program: mount_directory={} guest_program={}",
|
||||
mount_directory_str, container_guest_program_str
|
||||
);
|
||||
|
||||
// Build and run Docker command
|
||||
let docker_cmd = docker::DockerRunCommand::new(tag)
|
||||
.remove_after_run()
|
||||
// Needed by `cargo risczero build` which uses docker in docker.
|
||||
.with_volume("/var/run/docker.sock", "/var/run/docker.sock")
|
||||
.with_volume(mount_directory_str, "/guest-workspace")
|
||||
.with_volume(elf_output_dir_str, "/output")
|
||||
.with_command(["compile", container_guest_program_str, "/output"]);
|
||||
|
||||
let status = docker_cmd
|
||||
.run()
|
||||
.map_err(CompileError::DockerCommandFailed)?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(CompileError::DockerContainerRunFailed(status));
|
||||
}
|
||||
|
||||
// Read the compiled ELF program from the output directory
|
||||
let elf = std::fs::read(elf_output_dir.path().join("guest.elf"))
|
||||
.map_err(CompileError::ReadCompiledELFProgram)?;
|
||||
let image_id = std::fs::read(elf_output_dir.path().join("image_id"))
|
||||
.and_then(|image_id| {
|
||||
Digest::try_from(image_id)
|
||||
.map_err(|image_id| format!("Invalid image id: {image_id:?}"))
|
||||
.map_err(std::io::Error::other)
|
||||
})
|
||||
.map_err(CompileError::ReadImageId)?;
|
||||
|
||||
Ok(Risc0Program { elf, image_id })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod compile {
|
||||
use crate::compile::compile_risc0_program;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn get_test_risc0_methods_crate_path() -> PathBuf {
|
||||
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
|
||||
PathBuf::from(workspace_dir)
|
||||
.join("tests")
|
||||
.join("risc0")
|
||||
.join("compile")
|
||||
.join("basic")
|
||||
.canonicalize()
|
||||
.expect("Failed to find or canonicalize test Risc0 methods crate")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_risc0_method() {
|
||||
let test_methods_path = get_test_risc0_methods_crate_path();
|
||||
|
||||
let program = compile_risc0_program(&test_methods_path, Path::new(""))
|
||||
.expect("risc0 compilation failed");
|
||||
assert!(
|
||||
!program.elf.is_empty(),
|
||||
"Risc0 ELF bytes should not be empty."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
crates/ere-risc0/src/error.rs
Normal file
32
crates/ere-risc0/src/error.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Risc0Error {
|
||||
#[error(transparent)]
|
||||
Compile(#[from] CompileError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CompileError {
|
||||
#[error("Failed to build Docker image: {0}")]
|
||||
DockerImageBuildFailed(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("Docker command failed to execute: {0}")]
|
||||
DockerCommandFailed(#[source] std::io::Error),
|
||||
#[error("Docker container run failed with status: {0}")]
|
||||
DockerContainerRunFailed(std::process::ExitStatus),
|
||||
#[error("Invalid mount path: {0}")]
|
||||
InvalidMountPath(PathBuf),
|
||||
#[error("Invalid guest program path: {0}")]
|
||||
InvalidGuestPath(PathBuf),
|
||||
#[error("Failed to create temporary directory: {0}")]
|
||||
CreatingTempOutputDirectoryFailed(#[source] std::io::Error),
|
||||
#[error("Failed to create temporary output path: {0}")]
|
||||
InvalidTempOutputPath(PathBuf),
|
||||
#[error("Failed to read compiled ELF program: {0}")]
|
||||
ReadCompiledELFProgram(#[source] std::io::Error),
|
||||
#[error("Failed to read image id: {0}")]
|
||||
ReadImageId(#[source] std::io::Error),
|
||||
#[error("Failed to compute image id: {0}")]
|
||||
ComputeImaegIdFailed(#[source] anyhow::Error),
|
||||
}
|
||||
313
crates/ere-risc0/src/lib.rs
Normal file
313
crates/ere-risc0/src/lib.rs
Normal file
@@ -0,0 +1,313 @@
|
||||
use build_utils::docker;
|
||||
use compile::compile_risc0_program;
|
||||
use risc0_zkvm::Receipt;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use zkvm_interface::{
|
||||
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, ProverResourceType,
|
||||
zkVM, zkVMError,
|
||||
};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
|
||||
|
||||
mod compile;
|
||||
pub use compile::Risc0Program;
|
||||
|
||||
mod error;
|
||||
use error::Risc0Error;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct RV32_IM_RISC0_ZKVM_ELF;
|
||||
|
||||
impl Compiler for RV32_IM_RISC0_ZKVM_ELF {
|
||||
type Error = Risc0Error;
|
||||
|
||||
type Program = Risc0Program;
|
||||
|
||||
fn compile(
|
||||
workspace_directory: &Path,
|
||||
guest_relative: &Path,
|
||||
) -> Result<Self::Program, Self::Error> {
|
||||
compile_risc0_program(workspace_directory, guest_relative).map_err(Risc0Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl EreRisc0 {
|
||||
pub fn new(
|
||||
program: <RV32_IM_RISC0_ZKVM_ELF as Compiler>::Program,
|
||||
resource_type: ProverResourceType,
|
||||
) -> Self {
|
||||
match resource_type {
|
||||
ProverResourceType::Cpu => {
|
||||
#[cfg(any(feature = "cuda", feature = "metal"))]
|
||||
panic!("CPU mode requires both 'cuda' and 'metal' features to be disabled");
|
||||
}
|
||||
ProverResourceType::Gpu => {
|
||||
#[cfg(not(any(feature = "cuda", feature = "metal")))]
|
||||
panic!("GPU selected but neither 'cuda' nor 'metal' feature is enabled");
|
||||
}
|
||||
ProverResourceType::Network(_) => {
|
||||
panic!(
|
||||
"Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
program,
|
||||
resource_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EreRisc0 {
|
||||
program: <RV32_IM_RISC0_ZKVM_ELF as Compiler>::Program,
|
||||
#[allow(dead_code)]
|
||||
resource_type: ProverResourceType,
|
||||
}
|
||||
|
||||
impl zkVM for EreRisc0 {
|
||||
fn execute(&self, inputs: &Input) -> Result<ProgramExecutionReport, zkVMError> {
|
||||
// Build the Docker image
|
||||
let tag = "ere-risc0-cli:latest";
|
||||
docker::build_image(&PathBuf::from("docker/risc0/Dockerfile"), tag)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
// Create temporary directory for file exchange
|
||||
let temp_dir = TempDir::new().map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
let elf_path = temp_dir.path().join("guest.elf");
|
||||
let input_path = temp_dir.path().join("input");
|
||||
let report_path = temp_dir.path().join("report");
|
||||
|
||||
// Write ELF file to temp directory
|
||||
fs::write(&elf_path, &self.program.elf).map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
// Write input bytes to temp directory
|
||||
fs::write(&input_path, &serialize_input(inputs)?)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
// Run Docker command for execution
|
||||
let status = docker::DockerRunCommand::new(tag)
|
||||
.remove_after_run()
|
||||
.with_volume(temp_dir.path().to_string_lossy().to_string(), "/workspace")
|
||||
.with_command([
|
||||
"execute",
|
||||
"/workspace/guest.elf",
|
||||
"/workspace/input",
|
||||
"/workspace/report",
|
||||
])
|
||||
.run()
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(zkVMError::Other("Docker execution command failed".into()));
|
||||
}
|
||||
|
||||
// Read the execution report from the output file
|
||||
let report: ProgramExecutionReport = bincode::deserialize(
|
||||
&fs::read(report_path).map_err(|e| zkVMError::Other(Box::new(e)))?,
|
||||
)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
fn prove(&self, inputs: &Input) -> Result<(Vec<u8>, ProgramProvingReport), zkVMError> {
|
||||
// Build the Docker image
|
||||
let tag = "ere-risc0-cli:latest";
|
||||
docker::build_image(&PathBuf::from("docker/risc0/Dockerfile"), tag)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
// Create temporary directory for file exchange
|
||||
let temp_dir = TempDir::new().map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
let elf_path = temp_dir.path().join("guest.elf");
|
||||
let input_path = temp_dir.path().join("input");
|
||||
let proof_path = temp_dir.path().join("proof");
|
||||
let report_path = temp_dir.path().join("report");
|
||||
|
||||
// Write ELF file to temp directory
|
||||
fs::write(&elf_path, &self.program.elf).map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
// Write input bytes to temp directory
|
||||
fs::write(&input_path, &serialize_input(inputs)?)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
// Run Docker command for proving
|
||||
let status = docker::DockerRunCommand::new(tag)
|
||||
.remove_after_run()
|
||||
.with_volume(temp_dir.path().to_string_lossy().to_string(), "/workspace")
|
||||
.with_command([
|
||||
"prove",
|
||||
"/workspace/guest.elf",
|
||||
"/workspace/input",
|
||||
"/workspace/proof",
|
||||
"/workspace/report",
|
||||
])
|
||||
.run()
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(zkVMError::Other("Docker proving command failed".into()));
|
||||
}
|
||||
|
||||
// Read the proof from the output file
|
||||
let proof = fs::read(proof_path).map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
let report = bincode::deserialize(
|
||||
&fs::read(report_path).map_err(|e| zkVMError::Other(Box::new(e)))?,
|
||||
)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
Ok((proof, report))
|
||||
}
|
||||
|
||||
fn verify(&self, proof: &[u8]) -> Result<(), zkVMError> {
|
||||
let decoded: Receipt =
|
||||
borsh::from_slice(proof).map_err(|err| zkVMError::Other(Box::new(err)))?;
|
||||
|
||||
decoded
|
||||
.verify(self.program.image_id)
|
||||
.map_err(|err| zkVMError::Other(Box::new(err)))
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
NAME
|
||||
}
|
||||
|
||||
fn sdk_version(&self) -> &'static str {
|
||||
SDK_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize input bytes in the same way as the `ExecutorEnvBuilder`.
|
||||
fn serialize_input(inputs: &Input) -> Result<Vec<u8>, zkVMError> {
|
||||
let mut input_bytes = Vec::new();
|
||||
for input in inputs.iter() {
|
||||
match input {
|
||||
InputItem::Object(serialize) => {
|
||||
let vec = risc0_zkvm::serde::to_vec(serialize)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
input_bytes.extend_from_slice(bytemuck::cast_slice(&vec));
|
||||
}
|
||||
InputItem::Bytes(items) => {
|
||||
input_bytes.extend_from_slice(&(items.len() as u32).to_le_bytes());
|
||||
input_bytes.extend_from_slice(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(input_bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod prove_tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
use zkvm_interface::Input;
|
||||
|
||||
fn get_prove_test_guest_program_path() -> PathBuf {
|
||||
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
|
||||
PathBuf::from(workspace_dir)
|
||||
.join("tests")
|
||||
.join("risc0")
|
||||
.join("compile")
|
||||
.join("basic")
|
||||
.canonicalize()
|
||||
.expect("Failed to find or canonicalize test Risc0 methods crate")
|
||||
}
|
||||
|
||||
fn get_compiled_test_r0_elf_for_prove() -> Result<Risc0Program, Risc0Error> {
|
||||
let test_guest_path = get_prove_test_guest_program_path();
|
||||
RV32_IM_RISC0_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prove_r0_dummy_input() {
|
||||
let program = get_compiled_test_r0_elf_for_prove().unwrap();
|
||||
|
||||
let mut input_builder = Input::new();
|
||||
let n: u32 = 42;
|
||||
let a: u16 = 42;
|
||||
input_builder.write(n);
|
||||
input_builder.write(a);
|
||||
|
||||
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu);
|
||||
|
||||
let (proof_bytes, _) = zkvm
|
||||
.prove(&input_builder)
|
||||
.unwrap_or_else(|err| panic!("Proving error in test: {err:?}"));
|
||||
|
||||
assert!(!proof_bytes.is_empty(), "Proof bytes should not be empty.");
|
||||
|
||||
let verify_results = zkvm.verify(&proof_bytes).is_ok();
|
||||
assert!(verify_results);
|
||||
|
||||
// TODO: Check public inputs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prove_r0_fails_on_bad_input_causing_execution_failure() {
|
||||
let elf_bytes = get_compiled_test_r0_elf_for_prove().unwrap();
|
||||
|
||||
let empty_input = Input::new();
|
||||
|
||||
let zkvm = EreRisc0::new(elf_bytes, ProverResourceType::Cpu);
|
||||
let prove_result = zkvm.prove(&empty_input);
|
||||
assert!(prove_result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod execute_tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
use zkvm_interface::Input;
|
||||
|
||||
fn get_compiled_test_r0_elf() -> Result<Risc0Program, Risc0Error> {
|
||||
let test_guest_path = get_execute_test_guest_program_path();
|
||||
RV32_IM_RISC0_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
|
||||
}
|
||||
|
||||
fn get_execute_test_guest_program_path() -> PathBuf {
|
||||
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
|
||||
PathBuf::from(workspace_dir)
|
||||
.join("tests")
|
||||
.join("risc0")
|
||||
.join("compile")
|
||||
.join("basic")
|
||||
.canonicalize()
|
||||
.expect("Failed to find or canonicalize test Risc0 methods crate")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_r0_dummy_input() {
|
||||
let program = get_compiled_test_r0_elf().unwrap();
|
||||
|
||||
let mut input_builder = Input::new();
|
||||
let n: u32 = 42;
|
||||
let a: u16 = 42;
|
||||
input_builder.write(n);
|
||||
input_builder.write(a);
|
||||
|
||||
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu);
|
||||
|
||||
zkvm.execute(&input_builder)
|
||||
.unwrap_or_else(|err| panic!("Execution error: {err:?}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_r0_no_input_for_guest_expecting_input() {
|
||||
let program = get_compiled_test_r0_elf().unwrap();
|
||||
|
||||
let empty_input = Input::new();
|
||||
|
||||
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu);
|
||||
let result = zkvm.execute(&empty_input);
|
||||
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"execute should fail if guest expects input but none is provided."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// This is ere-risczero/build_script_template.rs
|
||||
// This script will be temporarily copied as build.rs into the target methods crate.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GuestMethodInfo {
|
||||
name: String,
|
||||
elf_path: String, // Path to the ELF in OUT_DIR, as determined by risc0_build
|
||||
image_id_hex: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let guest_entries = risc0_build::embed_methods();
|
||||
|
||||
if guest_entries.is_empty() {
|
||||
eprintln!("ere Risc0 Template Build: risc0_build::embed_methods() found no guest methods.");
|
||||
return;
|
||||
}
|
||||
|
||||
let entry = &guest_entries[0]; // For simplicity, take the first guest
|
||||
let info = GuestMethodInfo {
|
||||
name: entry.name.to_string(),
|
||||
elf_path: entry.path.to_string(), // This path is to the ELF in OUT_DIR
|
||||
image_id_hex: entry
|
||||
.image_id
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.map(|b| format!("{:02x}", b))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// Output the info to a known file directly in the methods crate directory.
|
||||
let manifest_dir =
|
||||
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set for template build.rs");
|
||||
let info_file_path = Path::new(&manifest_dir).join("ere_guest_info.json");
|
||||
|
||||
let json_output = format!(
|
||||
r#"{{
|
||||
"name": "{}",
|
||||
"elf_path": "{}",
|
||||
"image_id_hex": "{}"
|
||||
}}"#,
|
||||
info.name.replace('\\', "\\\\").replace('"', "\\\""),
|
||||
info.elf_path.replace('\\', "\\\\").replace('"', "\\\""),
|
||||
info.image_id_hex
|
||||
);
|
||||
|
||||
let mut file = File::create(&info_file_path)
|
||||
.expect("Template build.rs: Failed to create ere_guest_info.json in manifest dir");
|
||||
file.write_all(json_output.as_bytes())
|
||||
.expect("Template build.rs: Failed to write to ere_guest_info.json in manifest dir");
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
eprintln!(
|
||||
"ere Risc0 Template Build: Guest info written to {:?}",
|
||||
info_file_path
|
||||
);
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
mod file_utils;
|
||||
use file_utils::FileRestorer;
|
||||
use risc0_zkvm::Digest;
|
||||
|
||||
use crate::error::CompileError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Risc0Program {
|
||||
// TODO: Seems like the risc0 compilation is also compiling
|
||||
// TODO: the analogous prover and verifying key
|
||||
pub(crate) elf: Vec<u8>,
|
||||
pub(crate) image_id: Digest,
|
||||
}
|
||||
|
||||
/// BUILD_SCRIPT_TEMPLATE that we will use to fetch the elf-path
|
||||
/// TODO: We might be able to deterministically get the elf path
|
||||
/// TODO: But note we also probably want the image id too, so not sure
|
||||
/// TODO: we can remove this hack sometime soon.
|
||||
const BUILD_SCRIPT_TEMPLATE: &str = include_str!("../build_script_template.rs");
|
||||
|
||||
pub(crate) fn compile_risczero_program(path: &Path) -> Result<Risc0Program, CompileError> {
|
||||
if !path.exists() || !path.is_dir() {
|
||||
return Err(CompileError::InvalidMethodsPath(path.to_path_buf()));
|
||||
}
|
||||
|
||||
// Inject `build.rs`
|
||||
let build_rs_path = path.join("build.rs");
|
||||
let _restorer = FileRestorer::new(&build_rs_path)?;
|
||||
fs::write(&build_rs_path, BUILD_SCRIPT_TEMPLATE)
|
||||
.map_err(|e| CompileError::io(e, "writing template build.rs"))?;
|
||||
|
||||
// Run `cargo build`
|
||||
let output = Command::new("cargo")
|
||||
.current_dir(path)
|
||||
.arg("build")
|
||||
.arg("--release")
|
||||
.output()
|
||||
.map_err(|e| CompileError::io(e, "spawning cargo build"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(CompileError::CargoBuildFailure {
|
||||
crate_path: path.to_path_buf(),
|
||||
status: output.status,
|
||||
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
||||
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
// Read guest info JSON
|
||||
let info_file = path.join("ere_guest_info.json");
|
||||
let info_text = fs::read_to_string(&info_file)
|
||||
.map_err(|e| CompileError::io(e, "reading ere_guest_info.json"))?;
|
||||
let info_json: JsonValue = serde_json::from_str(&info_text)
|
||||
.map_err(|e| CompileError::serde(e, "parsing ere_guest_info.json"))?;
|
||||
|
||||
let elf_path = info_json["elf_path"]
|
||||
.as_str()
|
||||
.map(PathBuf::from)
|
||||
.ok_or_else(|| CompileError::MissingJsonField {
|
||||
field: "elf_path",
|
||||
file: info_file.clone(),
|
||||
})?;
|
||||
let image_id_hex_str = info_json["image_id_hex"].as_str().unwrap();
|
||||
let image_id = hex::decode(image_id_hex_str).unwrap();
|
||||
let image_id = image_id.try_into().unwrap();
|
||||
|
||||
// Return Program
|
||||
fs::read(&elf_path)
|
||||
.map_err(|e| CompileError::io(e, "reading ELF file"))
|
||||
.map(|elf| Risc0Program { elf, image_id })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod compile {
|
||||
|
||||
use crate::compile::compile_risczero_program;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn get_test_risczero_methods_crate_path() -> PathBuf {
|
||||
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
|
||||
PathBuf::from(workspace_dir)
|
||||
.join("tests")
|
||||
.join("risczero")
|
||||
.join("compile")
|
||||
.join("project_structure_build")
|
||||
.canonicalize()
|
||||
.expect("Failed to find or canonicalize test Risc0 methods crate")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_risczero_method_with_custom_build_rs() {
|
||||
let test_methods_path = get_test_risczero_methods_crate_path();
|
||||
|
||||
let program =
|
||||
compile_risczero_program(&test_methods_path).expect("risc0 compilation failed");
|
||||
assert!(
|
||||
!program.elf.is_empty(),
|
||||
"Risc0 ELF bytes should not be empty."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
use crate::error::CompileError;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
// NOTE: We can remove this if we can deterministically always knows where the risc0 artifacts
|
||||
// will be.
|
||||
|
||||
/// RAII guard for backing up a file and ensuring its original state is restored
|
||||
/// when the guard goes out of scope, or that a temporarily created file is deleted.
|
||||
#[derive(Debug)]
|
||||
pub struct FileRestorer {
|
||||
path: PathBuf,
|
||||
original_content: Option<Vec<u8>>,
|
||||
was_originally_present: bool,
|
||||
}
|
||||
|
||||
impl FileRestorer {
|
||||
/// Creates a new FileRestorer for the given path.
|
||||
/// It reads and stores the original content if the file exists.
|
||||
pub fn new(path_to_manage: &Path) -> Result<Self, CompileError> {
|
||||
let was_originally_present = path_to_manage.exists();
|
||||
let original_content =
|
||||
if was_originally_present {
|
||||
if path_to_manage.is_dir() {
|
||||
return Err(CompileError::InvalidMethodsPath(path_to_manage.into()));
|
||||
}
|
||||
Some(fs::read(path_to_manage).map_err(|e| {
|
||||
CompileError::io(e, "FileRestorer: could not read original file")
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
path: path_to_manage.to_path_buf(),
|
||||
original_content,
|
||||
was_originally_present,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileRestorer {
|
||||
fn drop(&mut self) {
|
||||
if let Some(content) = &self.original_content {
|
||||
// Original file existed, restore its content.
|
||||
if let Err(e) = fs::write(&self.path, content) {
|
||||
eprintln!(
|
||||
"ERROR (FileRestorer): Failed to restore original content to file {}: {}. Manual restoration may be needed.",
|
||||
self.path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
} else if self.was_originally_present {
|
||||
// This case (original file existed, but no content backed up) should ideally not be reached
|
||||
// if `new()` successfully read it or errored out. This implies an issue in `new()` logic or state.
|
||||
eprintln!(
|
||||
"ERROR (FileRestorer): Original file {} was present but no backup content was stored. Cannot restore properly.",
|
||||
self.path.display()
|
||||
);
|
||||
} else {
|
||||
// File was not originally present, so the file at `self.path` was created by the user of FileRestorer.
|
||||
// We should delete it.
|
||||
if self.path.exists() && !self.path.is_dir() {
|
||||
// Extra check for is_dir before remove_file
|
||||
if let Err(e) = fs::remove_file(&self.path) {
|
||||
eprintln!(
|
||||
"ERROR (FileRestorer): Failed to remove temporary file {}: {}. Manual removal may be needed.",
|
||||
self.path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
} else if self.path.exists() && self.path.is_dir() {
|
||||
eprintln!(
|
||||
"ERROR (FileRestorer): Path {} was expected to be a file created by the operation, but it's a directory. Will not remove.",
|
||||
self.path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use std::{fs::File, io::Read};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn test_file_restorer_restores_existing_file() -> Result<()> {
|
||||
let temp_file = NamedTempFile::new()?;
|
||||
let initial_content = b"initial content";
|
||||
fs::write(temp_file.path(), initial_content)?;
|
||||
|
||||
let file_path = temp_file.path().to_path_buf();
|
||||
{
|
||||
let _restorer = FileRestorer::new(&file_path)?;
|
||||
// Modify the file while restorer is in scope
|
||||
fs::write(&file_path, b"modified content")?;
|
||||
let mut current_content = Vec::new();
|
||||
File::open(&file_path)?.read_to_end(&mut current_content)?;
|
||||
assert_eq!(current_content, b"modified content");
|
||||
} // _restorer goes out of scope here, Drop is called
|
||||
|
||||
let mut final_content = Vec::new();
|
||||
File::open(&file_path)?.read_to_end(&mut final_content)?;
|
||||
assert_eq!(
|
||||
final_content, initial_content,
|
||||
"File content was not restored."
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_restorer_removes_created_file() -> Result<()> {
|
||||
let temp_file = NamedTempFile::new()?; // Creates a file
|
||||
let file_path = temp_file.path().to_path_buf();
|
||||
// Ensure it's deleted before the test so FileRestorer sees it as new
|
||||
drop(temp_file); // This deletes the file created by NamedTempFile
|
||||
assert!(
|
||||
!file_path.exists(),
|
||||
"Temp file should be deleted before FileRestorer test for creation."
|
||||
);
|
||||
|
||||
{
|
||||
let _restorer = FileRestorer::new(&file_path)?;
|
||||
assert!(
|
||||
!file_path.exists(),
|
||||
"File should not exist yet if it was not originally present."
|
||||
);
|
||||
// Create the file while restorer is in scope
|
||||
fs::write(&file_path, b"newly created content")?;
|
||||
assert!(file_path.exists(), "File should exist after being written.");
|
||||
} // _restorer goes out of scope here, Drop is called
|
||||
|
||||
assert!(
|
||||
!file_path.exists(),
|
||||
"Newly created file was not removed by FileRestorer."
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_restorer_handles_path_is_directory() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let result = FileRestorer::new(temp_dir.path());
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(
|
||||
result.unwrap_err(),
|
||||
CompileError::InvalidMethodsPath(_)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use std::{io, path::PathBuf, process::ExitStatus};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RiscZeroError {
|
||||
#[error(transparent)]
|
||||
Compile(#[from] CompileError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CompileError {
|
||||
#[error("{context}: {source}")]
|
||||
Io {
|
||||
#[source]
|
||||
source: io::Error,
|
||||
context: &'static str,
|
||||
},
|
||||
#[error("{context}: {source}")]
|
||||
SerdeJson {
|
||||
#[source]
|
||||
source: serde_json::Error,
|
||||
context: &'static str,
|
||||
},
|
||||
#[error("Methods crate path does not exist or is not a directory: {0}")]
|
||||
InvalidMethodsPath(PathBuf),
|
||||
#[error(
|
||||
"`cargo build` for {crate_path} failed with status {status}\nstdout:\n{stdout}\nstderr:\n{stderr}"
|
||||
)]
|
||||
CargoBuildFailure {
|
||||
crate_path: PathBuf,
|
||||
status: ExitStatus,
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
},
|
||||
#[error("Could not find field `{field}` in JSON file `{file}`")]
|
||||
MissingJsonField { field: &'static str, file: PathBuf },
|
||||
}
|
||||
|
||||
impl CompileError {
|
||||
pub fn io(e: io::Error, context: &'static str) -> Self {
|
||||
Self::Io { source: e, context }
|
||||
}
|
||||
pub fn serde(e: serde_json::Error, context: &'static str) -> Self {
|
||||
Self::SerdeJson { source: e, context }
|
||||
}
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
use std::{path::Path, time::Instant};
|
||||
|
||||
use compile::compile_risczero_program;
|
||||
use risc0_zkvm::{ExecutorEnv, ProverOpts, Receipt, default_executor, default_prover};
|
||||
use zkvm_interface::{
|
||||
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, ProverResourceType,
|
||||
zkVM, zkVMError,
|
||||
};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
|
||||
|
||||
mod compile;
|
||||
pub use compile::Risc0Program;
|
||||
|
||||
mod error;
|
||||
use error::RiscZeroError;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct RV32_IM_RISCZERO_ZKVM_ELF;
|
||||
|
||||
impl Compiler for RV32_IM_RISCZERO_ZKVM_ELF {
|
||||
type Error = RiscZeroError;
|
||||
|
||||
type Program = Risc0Program;
|
||||
|
||||
fn compile(
|
||||
workspace_directory: &Path,
|
||||
guest_relative: &Path,
|
||||
) -> Result<Self::Program, Self::Error> {
|
||||
compile_risczero_program(&workspace_directory.join(guest_relative))
|
||||
.map_err(RiscZeroError::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl EreRisc0 {
|
||||
pub fn new(
|
||||
program: <RV32_IM_RISCZERO_ZKVM_ELF as Compiler>::Program,
|
||||
resource_type: ProverResourceType,
|
||||
) -> Self {
|
||||
match resource_type {
|
||||
ProverResourceType::Cpu => {
|
||||
#[cfg(any(feature = "cuda", feature = "metal"))]
|
||||
panic!("CPU mode requires both 'cuda' and 'metal' features to be disabled");
|
||||
}
|
||||
ProverResourceType::Gpu => {
|
||||
#[cfg(not(any(feature = "cuda", feature = "metal")))]
|
||||
panic!("GPU selected but neither 'cuda' nor 'metal' feature is enabled");
|
||||
}
|
||||
ProverResourceType::Network(_) => {
|
||||
panic!(
|
||||
"Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
program,
|
||||
resource_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EreRisc0 {
|
||||
program: <RV32_IM_RISCZERO_ZKVM_ELF as Compiler>::Program,
|
||||
#[allow(dead_code)]
|
||||
resource_type: ProverResourceType,
|
||||
}
|
||||
|
||||
impl zkVM for EreRisc0 {
|
||||
fn execute(&self, inputs: &Input) -> Result<ProgramExecutionReport, zkVMError> {
|
||||
let executor = default_executor();
|
||||
let mut env = ExecutorEnv::builder();
|
||||
for input in inputs.iter() {
|
||||
match input {
|
||||
InputItem::Object(serialize) => {
|
||||
env.write(serialize).unwrap();
|
||||
}
|
||||
InputItem::Bytes(items) => {
|
||||
env.write_frame(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
let env = env.build().map_err(|err| zkVMError::Other(err.into()))?;
|
||||
|
||||
let start = Instant::now();
|
||||
let session_info = executor
|
||||
.execute(env, &self.program.elf)
|
||||
.map_err(|err| zkVMError::Other(err.into()))?;
|
||||
Ok(ProgramExecutionReport {
|
||||
total_num_cycles: session_info.cycles() as u64,
|
||||
execution_duration: start.elapsed(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn prove(&self, inputs: &Input) -> Result<(Vec<u8>, ProgramProvingReport), zkVMError> {
|
||||
let prover = default_prover();
|
||||
let mut env = ExecutorEnv::builder();
|
||||
for input in inputs.iter() {
|
||||
match input {
|
||||
InputItem::Object(serialize) => {
|
||||
env.write(serialize).unwrap();
|
||||
}
|
||||
InputItem::Bytes(items) => {
|
||||
env.write_frame(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
let env = env.build().map_err(|err| zkVMError::Other(err.into()))?;
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
let prove_info = prover
|
||||
.prove_with_opts(env, &self.program.elf, &ProverOpts::succinct())
|
||||
.map_err(|err| zkVMError::Other(err.into()))?;
|
||||
let proving_time = now.elapsed();
|
||||
|
||||
let encoded =
|
||||
borsh::to_vec(&prove_info.receipt).map_err(|err| zkVMError::Other(Box::new(err)))?;
|
||||
Ok((encoded, ProgramProvingReport::new(proving_time)))
|
||||
}
|
||||
|
||||
fn verify(&self, proof: &[u8]) -> Result<(), zkVMError> {
|
||||
let decoded: Receipt =
|
||||
borsh::from_slice(proof).map_err(|err| zkVMError::Other(Box::new(err)))?;
|
||||
|
||||
decoded
|
||||
.verify(self.program.image_id)
|
||||
.map_err(|err| zkVMError::Other(Box::new(err)))
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
NAME
|
||||
}
|
||||
|
||||
fn sdk_version(&self) -> &'static str {
|
||||
SDK_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod prove_tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
use zkvm_interface::Input;
|
||||
|
||||
fn get_prove_test_guest_program_path() -> PathBuf {
|
||||
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
|
||||
PathBuf::from(workspace_dir)
|
||||
.join("tests")
|
||||
.join("risczero")
|
||||
.join("compile")
|
||||
.join("project_structure_build")
|
||||
.canonicalize()
|
||||
.expect("Failed to find or canonicalize test Risc0 methods crate")
|
||||
}
|
||||
|
||||
fn get_compiled_test_r0_elf_for_prove() -> Result<Risc0Program, RiscZeroError> {
|
||||
let test_guest_path = get_prove_test_guest_program_path();
|
||||
RV32_IM_RISCZERO_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prove_r0_dummy_input() {
|
||||
let program = get_compiled_test_r0_elf_for_prove().unwrap();
|
||||
|
||||
let mut input_builder = Input::new();
|
||||
let n: u32 = 42;
|
||||
let a: u16 = 42;
|
||||
input_builder.write(n);
|
||||
input_builder.write(a);
|
||||
|
||||
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu);
|
||||
|
||||
let proof_bytes = match zkvm.prove(&input_builder) {
|
||||
Ok((prove_result, _)) => prove_result,
|
||||
Err(err) => {
|
||||
panic!("Proving error in test: {err}",);
|
||||
}
|
||||
};
|
||||
|
||||
assert!(!proof_bytes.is_empty(), "Proof bytes should not be empty.");
|
||||
|
||||
let verify_results = zkvm.verify(&proof_bytes).is_ok();
|
||||
assert!(verify_results);
|
||||
|
||||
// TODO: Check public inputs
|
||||
}
|
||||
|
||||
#[test]
|
||||
// TODO: Note: SP1 will panic here
|
||||
// #[should_panic]
|
||||
fn test_prove_r0_fails_on_bad_input_causing_execution_failure() {
|
||||
let elf_bytes = get_compiled_test_r0_elf_for_prove().unwrap();
|
||||
|
||||
let empty_input = Input::new();
|
||||
|
||||
let zkvm = EreRisc0::new(elf_bytes, ProverResourceType::Cpu);
|
||||
let prove_result = zkvm.prove(&empty_input);
|
||||
assert!(prove_result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod execute_tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
use zkvm_interface::Input;
|
||||
|
||||
fn get_compiled_test_r0_elf() -> Result<Risc0Program, RiscZeroError> {
|
||||
let test_guest_path = get_execute_test_guest_program_path();
|
||||
RV32_IM_RISCZERO_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
|
||||
}
|
||||
|
||||
fn get_execute_test_guest_program_path() -> PathBuf {
|
||||
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
|
||||
PathBuf::from(workspace_dir)
|
||||
.join("tests")
|
||||
.join("risczero")
|
||||
.join("compile")
|
||||
.join("project_structure_build")
|
||||
.canonicalize()
|
||||
.expect("Failed to find or canonicalize test Risc0 methods crate")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_r0_dummy_input() {
|
||||
let program = get_compiled_test_r0_elf().unwrap();
|
||||
|
||||
let mut input_builder = Input::new();
|
||||
let n: u32 = 42;
|
||||
let a: u16 = 42;
|
||||
input_builder.write(n);
|
||||
input_builder.write(a);
|
||||
|
||||
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu);
|
||||
|
||||
let result = zkvm.execute(&input_builder);
|
||||
|
||||
if let Err(err) = &result {
|
||||
panic!("Execution error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_r0_no_input_for_guest_expecting_input() {
|
||||
let program = get_compiled_test_r0_elf().unwrap();
|
||||
|
||||
let empty_input = Input::new();
|
||||
|
||||
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu);
|
||||
let result = zkvm.execute(&empty_input);
|
||||
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"execute should fail if guest expects input but none is provided."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,3 @@ tracing = "0.1"
|
||||
|
||||
[build-dependencies]
|
||||
build-utils.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "ere_succinct"
|
||||
path = "src/lib.rs"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
@@ -42,7 +41,7 @@ pub fn compile(
|
||||
);
|
||||
|
||||
// Build and run Docker command
|
||||
let docker_cmd = DockerRunCommand::new(tag)
|
||||
let docker_cmd = docker::DockerRunCommand::new(tag)
|
||||
.remove_after_run()
|
||||
.with_volume(mount_directory_str, "/guest-workspace")
|
||||
.with_volume(elf_output_dir_str, "/output")
|
||||
@@ -112,63 +111,3 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DockerRunCommand {
|
||||
image: String,
|
||||
volumes: Vec<(String, String)>, // (host_path, container_path)
|
||||
command: Vec<String>,
|
||||
// remove image after running
|
||||
remove_after: bool,
|
||||
}
|
||||
|
||||
impl DockerRunCommand {
|
||||
fn new(image: impl Into<String>) -> Self {
|
||||
Self {
|
||||
image: image.into(),
|
||||
volumes: Vec::new(),
|
||||
command: Vec::new(),
|
||||
remove_after: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_volume(
|
||||
mut self,
|
||||
host_path: impl Into<String>,
|
||||
container_path: impl Into<String>,
|
||||
) -> Self {
|
||||
self.volumes.push((host_path.into(), container_path.into()));
|
||||
self
|
||||
}
|
||||
|
||||
fn with_command(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
||||
self.command.extend(args.into_iter().map(|s| s.into()));
|
||||
self
|
||||
}
|
||||
|
||||
fn remove_after_run(mut self) -> Self {
|
||||
self.remove_after = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn to_args(&self) -> Vec<String> {
|
||||
let mut args = vec!["run".to_string()];
|
||||
|
||||
if self.remove_after {
|
||||
args.push("--rm".to_string());
|
||||
}
|
||||
|
||||
for (host_path, container_path) in &self.volumes {
|
||||
args.extend(["-v".to_string(), format!("{host_path}:{container_path}")]);
|
||||
}
|
||||
|
||||
args.push(self.image.clone());
|
||||
args.extend(self.command.iter().cloned());
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
fn run(&self) -> Result<std::process::ExitStatus, std::io::Error> {
|
||||
Command::new("docker").args(self.to_args()).status()
|
||||
}
|
||||
}
|
||||
|
||||
21
docker/risc0/Cargo.toml
Normal file
21
docker/risc0/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "risc0-cli"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tempfile.workspace = true
|
||||
toml.workspace = true
|
||||
tracing.workspace = true
|
||||
clap.workspace = true
|
||||
anyhow.workspace = true
|
||||
hex.workspace = true
|
||||
zkvm-interface.workspace = true
|
||||
risc0-zkvm = { version = "^2.3.0", features = ["unstable"] }
|
||||
borsh = "1.5"
|
||||
bincode = "1.3"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,33 +1,29 @@
|
||||
ARG BASE_IMAGE_TAG=latest
|
||||
|
||||
FROM rust:1.85 AS builder
|
||||
|
||||
WORKDIR /risc0-cli
|
||||
|
||||
# Build `risc0-cli`
|
||||
COPY . .
|
||||
RUN cargo build --release -p risc0-cli
|
||||
|
||||
FROM ere-base:${BASE_IMAGE_TAG}
|
||||
|
||||
ARG USERNAME=ere_user
|
||||
USER root
|
||||
|
||||
# Ensure Cargo/Rustup environment variables are set from the base image for SDK script
|
||||
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||
CARGO_HOME=/usr/local/cargo \
|
||||
PATH=/usr/local/cargo/bin:$PATH
|
||||
|
||||
# Copy and run the Risc0 SDK installer script
|
||||
COPY scripts/sdk_installers/install_risc0_sdk.sh /tmp/install_risc0_sdk.sh
|
||||
RUN chmod +x /tmp/install_risc0_sdk.sh
|
||||
|
||||
# Run the script without version arguments to install latest
|
||||
# TODO: We need to change this in all scripts so that we can fix the version in CI
|
||||
RUN /tmp/install_risc0_sdk.sh
|
||||
RUN chmod +x /tmp/install_risc0_sdk.sh && /tmp/install_risc0_sdk.sh
|
||||
|
||||
# Verify Risc0 installation (script also does this, but good for Dockerfile sanity)
|
||||
RUN echo "Verifying Risc0 installation in Dockerfile (post-script)..." && cargo risczero --version
|
||||
|
||||
# Copy the entire ere project context
|
||||
# The WORKDIR is /app from the base image
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
# Get docker for `cargo risczero build`
|
||||
RUN curl -fsSL https://get.docker.com | sh
|
||||
|
||||
# Run tests
|
||||
RUN echo "Running tests for ere-risczero library..." && \
|
||||
cargo test --release -p ere-risczero --lib -- --color always
|
||||
# Copy guest compiler binary
|
||||
COPY --from=builder /risc0-cli/target/release/risc0-cli /risc0-cli/risc0-cli
|
||||
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
# Set entrypoint to `risc0-cli`
|
||||
ENTRYPOINT ["/risc0-cli/risc0-cli"]
|
||||
|
||||
289
docker/risc0/src/main.rs
Normal file
289
docker/risc0/src/main.rs
Normal file
@@ -0,0 +1,289 @@
|
||||
use anyhow::Context;
|
||||
use clap::{Parser, Subcommand};
|
||||
use risc0_zkvm::{ExecutorEnv, ProverOpts, default_executor, default_prover};
|
||||
use std::{fs, path::PathBuf, process::Command};
|
||||
use toml::Value as TomlValue;
|
||||
use tracing::info;
|
||||
use zkvm_interface::{ProgramExecutionReport, ProgramProvingReport};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Compile a guest program
|
||||
Compile {
|
||||
/// Path to the guest program crate directory.
|
||||
guest_folder: PathBuf,
|
||||
/// Output folder where compiled `guest.elf` and `image_id` will be placed.
|
||||
output_folder: PathBuf,
|
||||
},
|
||||
/// Execute a compiled program
|
||||
Execute {
|
||||
/// Path to the compiled ELF file
|
||||
elf_path: PathBuf,
|
||||
/// Path to the serialized input bytes file
|
||||
input_path: PathBuf,
|
||||
/// Path where the execution report will be written
|
||||
report_path: PathBuf,
|
||||
},
|
||||
/// Prove execution of a compiled program
|
||||
Prove {
|
||||
/// Path to the compiled ELF file
|
||||
elf_path: PathBuf,
|
||||
/// Path to the serialized input bytes file
|
||||
input_path: PathBuf,
|
||||
/// Path where the proof will be written
|
||||
proof_path: PathBuf,
|
||||
/// Path where the report will be written
|
||||
report_path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
match args.command {
|
||||
Commands::Compile {
|
||||
guest_folder,
|
||||
output_folder,
|
||||
} => compile(guest_folder, output_folder),
|
||||
Commands::Prove {
|
||||
elf_path,
|
||||
input_path,
|
||||
proof_path,
|
||||
report_path,
|
||||
} => prove(elf_path, input_path, proof_path, report_path),
|
||||
Commands::Execute {
|
||||
elf_path,
|
||||
input_path,
|
||||
report_path,
|
||||
} => execute(elf_path, input_path, report_path),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile(guest_folder: PathBuf, output_folder: PathBuf) -> anyhow::Result<()> {
|
||||
let dir = guest_folder;
|
||||
|
||||
info!("Compiling Risc0 program at {}", dir.display());
|
||||
|
||||
if !dir.exists() || !dir.is_dir() {
|
||||
anyhow::bail!(
|
||||
"Program path does not exist or is not a directory: {}",
|
||||
dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
let guest_manifest_path = dir.join("Cargo.toml");
|
||||
if !guest_manifest_path.exists() {
|
||||
anyhow::bail!(
|
||||
"Cargo.toml not found in program directory: {}. Expected at: {}",
|
||||
dir.display(),
|
||||
guest_manifest_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// ── read + parse Cargo.toml ───────────────────────────────────────────
|
||||
let manifest_content = fs::read_to_string(&guest_manifest_path)
|
||||
.with_context(|| format!("Failed to read file at {}", guest_manifest_path.display()))?;
|
||||
|
||||
let manifest_toml: TomlValue = manifest_content.parse::<TomlValue>().with_context(|| {
|
||||
format!(
|
||||
"Failed to parse guest Cargo.toml at {}",
|
||||
guest_manifest_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let program_name = manifest_toml
|
||||
.get("package")
|
||||
.and_then(|p| p.get("name"))
|
||||
.and_then(|n| n.as_str())
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Could not find `[package].name` in guest Cargo.toml at {}",
|
||||
guest_manifest_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
info!("Parsed program name: {program_name}");
|
||||
|
||||
// ── build into a temp dir ─────────────────────────────────────────────
|
||||
info!(
|
||||
"Running `cargo risczero build` → dir: {}",
|
||||
output_folder.display()
|
||||
);
|
||||
|
||||
let output = Command::new("cargo")
|
||||
.current_dir(&dir)
|
||||
.args(["risczero", "build"])
|
||||
.stderr(std::process::Stdio::inherit())
|
||||
.output()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to execute `cargo risczer build` in {}",
|
||||
dir.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"Failed to execute `cargo risczero build` in {}",
|
||||
dir.display()
|
||||
)
|
||||
}
|
||||
|
||||
let (image_id, elf_path) = {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let line = stdout
|
||||
.lines()
|
||||
.find(|line| line.starts_with("ImageID: "))
|
||||
.unwrap();
|
||||
let (image_id, elf_path) = line
|
||||
.trim_start_matches("ImageID: ")
|
||||
.split_once(" - ")
|
||||
.unwrap();
|
||||
(image_id.to_string(), PathBuf::from(elf_path))
|
||||
};
|
||||
|
||||
if !elf_path.exists() {
|
||||
anyhow::bail!(
|
||||
"Compiled ELF not found at expected path: {}",
|
||||
elf_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let elf_bytes = fs::read(&elf_path)
|
||||
.with_context(|| format!("Failed to read file at {}", elf_path.display()))?;
|
||||
info!("Risc0 program compiled OK - {} bytes", elf_bytes.len());
|
||||
info!("Image ID - {image_id}");
|
||||
|
||||
fs::copy(&elf_path, output_folder.join("guest.elf")).with_context(|| {
|
||||
format!(
|
||||
"Failed to copy elf file from {} to {}",
|
||||
elf_path.display(),
|
||||
output_folder.join("guest.elf").display()
|
||||
)
|
||||
})?;
|
||||
fs::write(output_folder.join("image_id"), hex::decode(image_id)?).with_context(|| {
|
||||
format!(
|
||||
"Failed to write image id to {}",
|
||||
output_folder.join("image_id").display()
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute(elf_path: PathBuf, input_path: PathBuf, report_path: PathBuf) -> anyhow::Result<()> {
|
||||
info!("Starting execution for ELF at {}", elf_path.display());
|
||||
|
||||
// Read the ELF file
|
||||
let elf = fs::read(&elf_path)
|
||||
.with_context(|| format!("Failed to read ELF file at {}", elf_path.display()))?;
|
||||
|
||||
// Read the serialized input bytes
|
||||
let input_bytes = fs::read(&input_path)
|
||||
.with_context(|| format!("Failed to read input bytes at {}", input_path.display()))?;
|
||||
|
||||
info!("ELF size: {} bytes", elf.len());
|
||||
info!("Input size: {} bytes", input_bytes.len());
|
||||
|
||||
// Create executor environment using write_slice to write the serialized input bytes directly
|
||||
let executor = default_executor();
|
||||
let env = ExecutorEnv::builder()
|
||||
.write_slice(&input_bytes)
|
||||
.build()
|
||||
.context("Failed to build executor environment")?;
|
||||
|
||||
info!("Starting execution...");
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
// Execute the program
|
||||
let session_info = executor
|
||||
.execute(env, &elf)
|
||||
.context("Failed to execute program")?;
|
||||
|
||||
let execution_duration = start.elapsed();
|
||||
|
||||
info!("Execution completed in {:?}", execution_duration);
|
||||
info!("Total cycles: {}", session_info.cycles());
|
||||
|
||||
// Create execution report
|
||||
let report = ProgramExecutionReport {
|
||||
total_num_cycles: session_info.cycles() as u64,
|
||||
execution_duration,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Serialize and write the report
|
||||
let report_bytes =
|
||||
bincode::serialize(&report).context("Failed to serialize execution report")?;
|
||||
|
||||
fs::write(&report_path, report_bytes)
|
||||
.with_context(|| format!("Failed to write report to {}", report_path.display()))?;
|
||||
|
||||
info!("Execution report written to {}", report_path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prove(
|
||||
elf_path: PathBuf,
|
||||
input_path: PathBuf,
|
||||
proof_path: PathBuf,
|
||||
report_path: PathBuf,
|
||||
) -> anyhow::Result<()> {
|
||||
info!(
|
||||
"Starting proof generation for ELF at {}",
|
||||
elf_path.display()
|
||||
);
|
||||
|
||||
// Read the ELF file
|
||||
let elf = fs::read(&elf_path)
|
||||
.with_context(|| format!("Failed to read ELF file at {}", elf_path.display()))?;
|
||||
|
||||
// Read the serialized input bytes
|
||||
let input_bytes = fs::read(&input_path)
|
||||
.with_context(|| format!("Failed to read input bytes at {}", input_path.display()))?;
|
||||
|
||||
info!("ELF size: {} bytes", elf.len());
|
||||
info!("Input size: {} bytes", input_bytes.len());
|
||||
|
||||
// Create prover environment using write_slice to write the serialized input bytes directly
|
||||
let prover = default_prover();
|
||||
let env = ExecutorEnv::builder()
|
||||
.write_slice(&input_bytes)
|
||||
.build()
|
||||
.context("Failed to build executor environment")?;
|
||||
|
||||
info!("Starting proof generation...");
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
// Generate proof
|
||||
let prove_info = prover
|
||||
.prove_with_opts(env, &elf, &ProverOpts::succinct())
|
||||
.context("Failed to generate proof")?;
|
||||
|
||||
let proving_time = now.elapsed();
|
||||
|
||||
info!("Proof generation completed in {:?}", proving_time);
|
||||
|
||||
// Serialize and write the proof
|
||||
let proof_bytes = borsh::to_vec(&prove_info.receipt).context("Failed to serialize proof")?;
|
||||
fs::write(&proof_path, proof_bytes)
|
||||
.with_context(|| format!("Failed to write proof to {}", proof_path.display()))?;
|
||||
|
||||
let report_bytes = bincode::serialize(&ProgramProvingReport::new(proving_time))
|
||||
.context("Failed to serialize report")?;
|
||||
fs::write(&report_path, report_bytes)
|
||||
.with_context(|| format!("Failed to write report to {}", report_path.display()))?;
|
||||
|
||||
info!("Proof written to {}", proof_path.display());
|
||||
info!("Report written to {}", report_path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
[package]
|
||||
name = "risc0guest"
|
||||
name = "ere-test-risc0-guest"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = { version = "^2.3.0", default-features = false, features = [
|
||||
'std',
|
||||
] }
|
||||
risc0-zkvm = { version = "^2.3.0", default-features = false, features = ['std'] }
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "methods"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[build-dependencies]
|
||||
risc0-build = { version = "^2.3.0" }
|
||||
|
||||
[package.metadata.risc0]
|
||||
methods = ["guest"]
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
risc0_build::embed_methods();
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "risc0guest",
|
||||
"elf_path": "/Users/kev/work/ere/tests/risczero/project_structure_build/target/riscv-guest/methods/risc0guest/riscv32im-risc0-zkvm-elf/release/risc0guest.bin",
|
||||
"image_id_hex": "6a0d2e9f10ded46c571e644cdf8776e8f96be1844df36386fdee6c0a0e90084c"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
components = ["rustfmt", "rust-src"]
|
||||
profile = "minimal"
|
||||
@@ -1 +0,0 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
||||
Reference in New Issue
Block a user