From 42e7c6c416c629c3f7ee9ba57138ec83807ba01d Mon Sep 17 00:00:00 2001 From: Han Date: Sat, 2 Aug 2025 19:21:52 +0800 Subject: [PATCH] Add `ere-dockerized` (#75) --- .github/workflows/rust-checks.yml | 6 +- .github/workflows/test-zisk-docker.yml | 3 +- Cargo.lock | 44 +- Cargo.toml | 7 +- README.md | 5 +- crates/build-utils/src/lib.rs | 27 +- crates/ere-cli/src/main.rs | 34 +- crates/ere-dockerized/Cargo.toml | 26 + crates/ere-dockerized/build.rs | 65 +++ crates/ere-dockerized/src/docker.rs | 158 +++++++ crates/ere-dockerized/src/error.rs | 102 ++++ crates/ere-dockerized/src/input.rs | 64 +++ crates/ere-dockerized/src/lib.rs | 525 +++++++++++++++++++++ crates/ere-jolt/src/lib.rs | 21 +- crates/ere-nexus/src/lib.rs | 20 +- crates/ere-openvm/src/lib.rs | 19 +- crates/ere-pico/src/lib.rs | 13 +- crates/ere-risc0/Cargo.toml | 4 +- crates/ere-risc0/src/compile.rs | 92 ++-- crates/ere-risc0/src/error.rs | 46 +- crates/ere-risc0/src/lib.rs | 166 ++----- crates/ere-sp1/Cargo.toml | 2 +- crates/ere-sp1/src/compile.rs | 145 +++--- crates/ere-sp1/src/error.rs | 53 ++- crates/ere-sp1/src/lib.rs | 11 +- crates/ere-zisk/src/compile.rs | 2 +- crates/ere-zisk/src/lib.rs | 11 +- crates/zkvm-interface/src/lib.rs | 19 +- crates/zkvm-interface/src/network.rs | 10 + docker/cli/Dockerfile | 16 + docker/jolt/Dockerfile | 7 +- docker/nexus/Dockerfile | 24 +- docker/openvm/Dockerfile | 16 +- docker/pico/Dockerfile | 21 +- docker/risc0/Cargo.toml | 21 - docker/risc0/Dockerfile | 18 +- docker/risc0/src/main.rs | 289 ------------ docker/sp1/Cargo.toml | 16 - docker/sp1/Dockerfile | 26 +- docker/sp1/src/main.rs | 103 ---- docker/zisk/Dockerfile | 42 +- scripts/sdk_installers/install_zisk_sdk.sh | 7 + 42 files changed, 1365 insertions(+), 941 deletions(-) create mode 100644 crates/ere-dockerized/Cargo.toml create mode 100644 crates/ere-dockerized/build.rs create mode 100644 crates/ere-dockerized/src/docker.rs create mode 100644 crates/ere-dockerized/src/error.rs create mode 100644 crates/ere-dockerized/src/input.rs create mode 100644 crates/ere-dockerized/src/lib.rs create mode 100644 docker/cli/Dockerfile delete mode 100644 docker/risc0/Cargo.toml delete mode 100644 docker/risc0/src/main.rs delete mode 100644 docker/sp1/Cargo.toml delete mode 100644 docker/sp1/src/main.rs diff --git a/.github/workflows/rust-checks.yml b/.github/workflows/rust-checks.yml index fbb9a37..f7fa4be 100644 --- a/.github/workflows/rust-checks.yml +++ b/.github/workflows/rust-checks.yml @@ -33,7 +33,7 @@ jobs: strategy: fail-fast: false matrix: - crate: [ere-sp1, ere-risc0] + zkvm: [sp1, risc0] steps: - name: Checkout code uses: actions/checkout@v4 @@ -53,7 +53,7 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Check clippy - run: cargo clippy --bins --lib --examples --tests --benches -p ${{ matrix.crate }} + run: cargo clippy --bins --lib --examples --tests --benches -p ere-${{ matrix.zkvm }} - name: Run tests - run: cargo test --release -p ${{ matrix.crate }} + run: cargo test --release -p ere-dockerized -- ${{ matrix.zkvm }} diff --git a/.github/workflows/test-zisk-docker.yml b/.github/workflows/test-zisk-docker.yml index 911d93c..78fb21a 100644 --- a/.github/workflows/test-zisk-docker.yml +++ b/.github/workflows/test-zisk-docker.yml @@ -30,4 +30,5 @@ jobs: run: | docker build \ --tag ere-builder-zisk:latest \ - --file docker/zisk/Dockerfile . + --file docker/zisk/Dockerfile \ + --build-arg CI=1 . diff --git a/Cargo.lock b/Cargo.lock index 888db54..54229a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2359,6 +2359,20 @@ dependencies = [ "zkvm-interface", ] +[[package]] +name = "ere-dockerized" +version = "0.1.0" +dependencies = [ + "bincode", + "build-utils", + "bytemuck", + "risc0-zkvm", + "serde", + "tempfile", + "thiserror 2.0.12", + "zkvm-interface", +] + [[package]] name = "ere-jolt" version = "0.1.0" @@ -2425,7 +2439,9 @@ dependencies = [ "borsh", "build-utils", "bytemuck", + "cargo_metadata 0.19.2", "hex", + "risc0-build", "risc0-zkvm", "serde", "serde_json", @@ -2444,6 +2460,7 @@ dependencies = [ "sp1-sdk", "tempfile", "thiserror 2.0.12", + "toml 0.8.22", "tracing", "zkvm-interface", ] @@ -7886,22 +7903,6 @@ 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" @@ -8961,17 +8962,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "sp1-guest-compiler" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "tempfile", - "toml 0.8.22", - "tracing", -] - [[package]] name = "sp1-primitives" version = "5.1.0" diff --git a/Cargo.toml b/Cargo.toml index c191b83..e334163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,9 @@ members = [ "crates/ere-zisk", # zkVM interface "crates/zkvm-interface", - # CLI + # CLI and dockerized zkVM "crates/ere-cli", - # Guest compilers - "docker/sp1", - "docker/risc0", + "crates/ere-dockerized", ] resolver = "2" @@ -39,6 +37,7 @@ hex = "0.4.3" zkvm-interface = { path = "crates/zkvm-interface" } build-utils = { path = "crates/build-utils" } ere-cli = { path = "crates/ere-cli" } +ere-dockerized = { path = "crates/ere-dockerized" } ere-jolt = { path = "crates/ere-jolt" } ere-nexus = { path = "crates/ere-nexus" } ere-openvm = { path = "crates/ere-openvm" } diff --git a/README.md b/README.md index 3c62bcf..4cae310 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,8 @@ ere-sp1 = { path = "crates/ere-sp1" } use zkvm_interface::{Compiler, zkVM, Input, ProverResourceType}; use ere_sp1::{EreSP1, RV32_IM_SUCCINCT_ZKVM_ELF}; -let mount_directory = std::path::Path::new("."); -let guest_relative = std::path::Path::new("guest/hello"); -let elf = RV32_IM_SUCCINCT_ZKVM_ELF::compile(mount_directory, guest_relative)?; // compile +let guest_directory = std::path::Path::new("guest/hello"); +let elf = RV32_IM_SUCCINCT_ZKVM_ELF.compile(guest_directory)?; // compile let mut io = Input::new(); io.write(&42u32)?; let zkvm = EreSP1::new(elf, ProverResourceType::Cpu); // create zkVM instance diff --git a/crates/build-utils/src/lib.rs b/crates/build-utils/src/lib.rs index b2ec435..68abfd1 100644 --- a/crates/build-utils/src/lib.rs +++ b/crates/build-utils/src/lib.rs @@ -1,23 +1,28 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use cargo_metadata::MetadataCommand; use std::{env, fs, path::Path}; pub mod docker; // Detect and generate a Rust source file that contains the name and version of the SDK. pub fn detect_and_generate_name_and_sdk_version(name: &str, sdk_dep_name: &str) { - let meta = cargo_metadata::MetadataCommand::new() + gen_name_and_sdk_version(name, &detect_sdk_version(sdk_dep_name)); +} + +// Detect version of the SDK. +pub fn detect_sdk_version(sdk_dep_name: &str) -> String { + let meta = MetadataCommand::new() .exec() .expect("Failed to get cargo metadata"); - let version = meta - .packages + meta.packages .iter() .find(|pkg| pkg.name.eq_ignore_ascii_case(sdk_dep_name)) .map(|pkg| pkg.version.to_string()) .unwrap_or_else(|| { panic!("Dependency {sdk_dep_name} not found in Cargo.toml"); - }); - - gen_name_and_sdk_version(name, &version); + }) } // Generate a Rust source file that contains the provided name and version of the SDK. @@ -31,3 +36,13 @@ pub fn gen_name_and_sdk_version(name: &str, version: &str) { .unwrap(); println!("cargo:rerun-if-changed=Cargo.lock"); } + +/// Detects version of the crate of the `build.rs` that being ran. +pub fn detect_self_crate_version() -> String { + let meta = MetadataCommand::new() + .exec() + .expect("Failed to get cargo metadata"); + + // `root_package` returns the crate of the `build.rs` that being ran. + meta.root_package().unwrap().version.to_string() +} diff --git a/crates/ere-cli/src/main.rs b/crates/ere-cli/src/main.rs index 7d883f1..22bacfc 100644 --- a/crates/ere-cli/src/main.rs +++ b/crates/ere-cli/src/main.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + use anyhow::{Context, Error}; use clap::{Parser, Subcommand}; use std::{fs, path::PathBuf}; @@ -31,12 +33,9 @@ struct Cli { enum Commands { /// Compile a guest program Compile { - /// Path to the base directory (workspace root) + /// Path to the guest program #[arg(long)] - mount_directory: PathBuf, - /// Relative path from `mount_directory` to the guest program - #[arg(long)] - guest_relative: PathBuf, + guest_path: PathBuf, /// Path where the compiled program will be written #[arg(long)] program_path: PathBuf, @@ -90,10 +89,9 @@ fn main() -> Result<(), Error> { match args.command { Commands::Compile { - mount_directory, - guest_relative, + guest_path, program_path, - } => compile(mount_directory, guest_relative, program_path), + } => compile(guest_path, program_path), Commands::Prove { program_path, input_path, @@ -114,31 +112,27 @@ fn main() -> Result<(), Error> { } } -fn compile( - mount_directory: PathBuf, - guest_relative: PathBuf, - program_path: PathBuf, -) -> Result<(), Error> { +fn compile(guest_path: PathBuf, program_path: PathBuf) -> Result<(), Error> { #[cfg(feature = "jolt")] - let program = ere_jolt::JOLT_TARGET::compile(&mount_directory, &guest_relative); + let program = ere_jolt::JOLT_TARGET.compile(&guest_path); #[cfg(feature = "nexus")] - let program = ere_nexus::NEXUS_TARGET::compile(&mount_directory, &guest_relative); + let program = ere_nexus::NEXUS_TARGET.compile(&guest_path); #[cfg(feature = "openvm")] - let program = ere_openvm::OPENVM_TARGET::compile(&mount_directory, &guest_relative); + let program = ere_openvm::OPENVM_TARGET.compile(&guest_path); #[cfg(feature = "pico")] - let program = ere_pico::PICO_TARGET::compile(&mount_directory, &guest_relative); + let program = ere_pico::PICO_TARGET.compile(&guest_path); #[cfg(feature = "risc0")] - let program = ere_risc0::RV32_IM_RISC0_ZKVM_ELF::compile(&mount_directory, &guest_relative); + let program = ere_risc0::RV32_IM_RISC0_ZKVM_ELF.compile(&guest_path); #[cfg(feature = "sp1")] - let program = ere_sp1::RV32_IM_SUCCINCT_ZKVM_ELF::compile(&mount_directory, &guest_relative); + let program = ere_sp1::RV32_IM_SUCCINCT_ZKVM_ELF.compile(&guest_path); #[cfg(feature = "zisk")] - let program = ere_zisk::RV64_IMA_ZISK_ZKVM_ELF::compile(&mount_directory, &guest_relative); + let program = ere_zisk::RV64_IMA_ZISK_ZKVM_ELF.compile(&guest_path); serde::write( &program_path, diff --git a/crates/ere-dockerized/Cargo.toml b/crates/ere-dockerized/Cargo.toml new file mode 100644 index 0000000..bad6c2a --- /dev/null +++ b/crates/ere-dockerized/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ere-dockerized" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[dependencies] +bincode = "1.3" +serde = { version = "1.0", features = ["derive"] } +tempfile = "3.3" +thiserror = "2" + +zkvm-interface = { workspace = true, features = ["clap"] } + +# Needed for Risc0 and OpenVM input serialization. +bytemuck = "1.13" +risc0-zkvm = { version = "^2.3.0", default-features = false } + +[dev-dependencies] + +[build-dependencies] +build-utils = { workspace = true } + +[lints] +workspace = true diff --git a/crates/ere-dockerized/build.rs b/crates/ere-dockerized/build.rs new file mode 100644 index 0000000..48a48b5 --- /dev/null +++ b/crates/ere-dockerized/build.rs @@ -0,0 +1,65 @@ +use build_utils::{detect_sdk_version, detect_self_crate_version}; +use std::{env, fs, path::Path}; + +fn main() { + generate_crate_version(); + generate_zkvm_sdk_version_impl(); + println!("cargo:rerun-if-changed=Cargo.lock"); +} + +fn generate_crate_version() { + let crate_version = format!( + "const CRATE_VERSION: &str = \"{}\";", + detect_self_crate_version() + ); + + let out_dir = env::var("OUT_DIR").unwrap(); + let dst = Path::new(&out_dir).join("crate_version.rs"); + fs::write(dst, crate_version).unwrap(); +} + +fn generate_zkvm_sdk_version_impl() { + let [ + jolt_version, + nexus_version, + openvm_version, + pico_version, + risc0_version, + sp1_version, + ] = [ + "jolt-sdk", + "nexus-sdk", + "openvm-sdk", + "pico-sdk", + "risc0-zkvm", + "sp1-sdk", + ] + .map(detect_sdk_version); + + // FIXME: ZisK doens't depend on SDK yet, so we hardcode the version here, + // same as the one in `scripts/sdk_installers/install_zisk_sdk.sh`. + // Once ZisK's SDK is ready, we should update this to detect the SDK + // version. + // The issue for tracking https://github.com/eth-act/ere/issues/73. + let zisk_version = "0.9.0"; + + let zkvm_sdk_version_impl = format!( + r#"impl crate::ErezkVM {{ + pub fn sdk_version(&self) -> &'static str {{ + match self {{ + Self::Jolt => "{jolt_version}", + Self::Nexus => "{nexus_version}", + Self::OpenVM => "{openvm_version}", + Self::Pico => "{pico_version}", + Self::Risc0 => "{risc0_version}", + Self::SP1 => "{sp1_version}", + Self::Zisk => "{zisk_version}", + }} + }} +}}"#, + ); + + let out_dir = env::var("OUT_DIR").unwrap(); + let dst = Path::new(&out_dir).join("zkvm_sdk_version_impl.rs"); + fs::write(dst, zkvm_sdk_version_impl).unwrap(); +} diff --git a/crates/ere-dockerized/src/docker.rs b/crates/ere-dockerized/src/docker.rs new file mode 100644 index 0000000..09c8751 --- /dev/null +++ b/crates/ere-dockerized/src/docker.rs @@ -0,0 +1,158 @@ +use crate::error::CommonError; +use std::{ + fmt::{self, Display, Formatter}, + io, + path::Path, + process::Command, +}; + +#[derive(Clone)] +pub struct CmdOption(String, Option); + +impl CmdOption { + pub fn new(key: impl AsRef, value: impl AsRef) -> Self { + Self(to_string(key), Some(to_string(value))) + } + + pub fn flag(key: impl AsRef) -> Self { + Self(to_string(key), None) + } + + pub fn to_args(&self) -> Vec { + let Self(key, value) = self; + match value { + Some(value) => vec![format!("--{key}"), format!("{value}")], + None => vec![format!("--{key}")], + } + } +} + +impl Display for CmdOption { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self(key, value) = self; + match value { + Some(value) => write!(f, "--{key} {value}"), + None => write!(f, "--{key}"), + } + } +} + +#[derive(Default)] +pub struct DockerBuildCmd { + options: Vec, +} + +impl DockerBuildCmd { + pub fn new() -> Self { + Self::default() + } + + pub fn file(mut self, file: impl AsRef) -> Self { + self.options + .push(CmdOption::new("file", file.as_ref().to_string_lossy())); + self + } + + pub fn tag(mut self, tag: impl AsRef) -> Self { + self.options.push(CmdOption::new("tag", tag)); + self + } + + pub fn bulid_arg(mut self, key: impl AsRef, value: impl AsRef) -> Self { + self.options.push(CmdOption::new( + "build-arg", + format!("{}={}", to_string(key), to_string(value)), + )); + self + } + + pub fn exec(self, context: impl AsRef) -> Result<(), io::Error> { + let mut cmd = Command::new("docker"); + cmd.arg("build"); + for flag in self.options { + cmd.args(flag.to_args()); + } + cmd.arg(context.as_ref().to_string_lossy().to_string()); + + let status = cmd.status()?; + + if !status.success() { + return Err(io::Error::other(format!( + "Command {cmd:?} failed with status: {status}", + ))); + } + + Ok(()) + } +} + +pub struct DockerRunCmd { + options: Vec, + image: String, +} + +impl DockerRunCmd { + pub fn new(image: String) -> Self { + Self { + options: Vec::new(), + image, + } + } + + pub fn volume(mut self, host: impl AsRef, container: impl AsRef) -> Self { + self.options.push(CmdOption::new( + "volume", + format!( + "{}:{}", + host.as_ref().display(), + container.as_ref().display(), + ), + )); + self + } + + pub fn gpus(mut self, devices: impl AsRef) -> Self { + self.options.push(CmdOption::new("gpus", devices)); + self + } + + pub fn rm(mut self) -> Self { + self.options.push(CmdOption::flag("rm")); + self + } + + pub fn exec(self, commands: impl IntoIterator>) -> Result<(), io::Error> { + let mut cmd = Command::new("docker"); + cmd.arg("run"); + for flag in self.options { + cmd.args(flag.to_args()); + } + cmd.arg(self.image); + for command in commands { + cmd.arg(command.as_ref()); + } + + let status = cmd.status()?; + + if !status.success() { + return Err(io::Error::other(format!( + "Command {cmd:?} failed with status: {status}", + ))); + } + + Ok(()) + } +} + +pub fn docker_image_exists(image: impl AsRef) -> Result { + let output = Command::new("docker") + .args(["images", "--quiet", image.as_ref()]) + .output() + .map_err(CommonError::DockerImageCmd)?; + // If image exists, image id will be printed hence stdout will be non-empty. + Ok(!output.stdout.is_empty()) +} + +fn to_string(s: impl AsRef) -> String { + s.as_ref().to_string() +} diff --git a/crates/ere-dockerized/src/error.rs b/crates/ere-dockerized/src/error.rs new file mode 100644 index 0000000..a9bd9fb --- /dev/null +++ b/crates/ere-dockerized/src/error.rs @@ -0,0 +1,102 @@ +use std::{io, path::PathBuf}; +use thiserror::Error; +use zkvm_interface::zkVMError; + +impl From for zkVMError { + fn from(value: DockerizedError) -> Self { + zkVMError::Other(Box::new(value)) + } +} + +impl From for zkVMError { + fn from(value: CommonError) -> Self { + zkVMError::Other(Box::new(value)) + } +} + +#[derive(Debug, Error)] +pub enum DockerizedError { + #[error(transparent)] + Compile(#[from] CompileError), + + #[error(transparent)] + Execute(#[from] ExecuteError), + + #[error(transparent)] + Prove(#[from] ProveError), + + #[error(transparent)] + Verify(#[from] VerifyError), +} + +#[derive(Debug, Error)] +pub enum CompileError { + #[error( + "Guest directory must be in mounting directory, mounting_directory: {mounting_directory}, guest_directory: {guest_directory}" + )] + GuestNotInMountingDirecty { + mounting_directory: PathBuf, + guest_directory: PathBuf, + }, + #[error(transparent)] + Common(#[from] CommonError), +} + +#[derive(Debug, Error)] +pub enum ExecuteError { + #[error(transparent)] + Common(#[from] CommonError), +} + +#[derive(Debug, Error)] +pub enum ProveError { + #[error(transparent)] + Common(#[from] CommonError), +} + +#[derive(Debug, Error)] +pub enum VerifyError { + #[error(transparent)] + Common(#[from] CommonError), +} + +#[derive(Debug, Error)] +pub enum CommonError { + #[error("{context}: {source}")] + Io { + #[source] + source: io::Error, + context: String, + }, + #[error("Failed to execute `docker image`: {0}")] + DockerImageCmd(io::Error), + #[error("Failed to execute `docker build`: {0}")] + DockerBuildCmd(io::Error), + #[error("Failed to execute `docker run`: {0}")] + DockerRunCmd(io::Error), + #[error("{context}: {source}")] + Serialization { + #[source] + source: Box, + context: String, + }, +} + +impl CommonError { + pub fn io(source: io::Error, context: impl ToString) -> Self { + Self::Io { + source, + context: context.to_string(), + } + } + + pub fn serilization( + source: impl Into>, + context: impl ToString, + ) -> Self { + Self::Serialization { + source: source.into(), + context: context.to_string(), + } + } +} diff --git a/crates/ere-dockerized/src/input.rs b/crates/ere-dockerized/src/input.rs new file mode 100644 index 0000000..f4459d8 --- /dev/null +++ b/crates/ere-dockerized/src/input.rs @@ -0,0 +1,64 @@ +use crate::{ErezkVM, error::CommonError}; +use serde::Serialize; +use zkvm_interface::{Input, InputItem}; + +impl ErezkVM { + pub fn serialize_object( + &self, + obj: &(impl Serialize + ?Sized), + ) -> Result, CommonError> { + match self { + // Issue for tracking: https://github.com/eth-act/ere/issues/4. + Self::Jolt => unimplemented!(), + // Issue for tracking: https://github.com/eth-act/ere/issues/63. + Self::Nexus => unimplemented!(), + // FIXME: Instead of using `openvm::serde::to_vec`, we use Risc0's + // serializer, because OpenVM uses the same one, to avoid the + // duplicated extern symbol they export. + // It'd be better to have each zkvm provides their + // lightweight serde crate. + Self::OpenVM => risc0_zkvm::serde::to_vec(obj) + .map(|words| words.into_iter().flat_map(|w| w.to_le_bytes()).collect()) + .map_err(|err| { + CommonError::serilization( + err, + "Failed to serialize object with `risc0_zkvm::serde::to_vec`", + ) + }), + Self::Pico => bincode::serialize(obj).map_err(|err| { + CommonError::serilization(err, "Failed to serialize object with `bincode`") + }), + Self::Risc0 => risc0_zkvm::serde::to_vec(obj) + .map(|vec| bytemuck::cast_slice(&vec).to_vec()) + .map_err(|err| { + CommonError::serilization( + err, + "Failed to serialize object with `risc0_zkvm::serde::to_vec`", + ) + }), + Self::SP1 => bincode::serialize(obj).map_err(|err| { + CommonError::serilization(err, "Failed to serialize object with `bincode`") + }), + Self::Zisk => bincode::serialize(obj).map_err(|err| { + CommonError::serilization(err, "Failed to serialize object with `bincode`") + }), + } + } + + pub fn serialize_inputs(&self, inputs: &Input) -> Result, CommonError> { + bincode::serialize( + &inputs + .iter() + .map(|input| { + Ok(match input { + InputItem::Object(obj) => self.serialize_object(&**obj)?, + InputItem::Bytes(bytes) => bytes.clone(), + }) + }) + .collect::>, CommonError>>()?, + ) + .map_err(|err| { + CommonError::serilization(err, "Failed to serialize sequence of bytes with `bincode`") + }) + } +} diff --git a/crates/ere-dockerized/src/lib.rs b/crates/ere-dockerized/src/lib.rs new file mode 100644 index 0000000..c2e40b8 --- /dev/null +++ b/crates/ere-dockerized/src/lib.rs @@ -0,0 +1,525 @@ +//! # Ere Dockerized +//! +//! A Docker-based wrapper for other zkVM crates `ere-{zkvm}`. +//! +//! This crate provides a unified interface to dockerize the `Compiler` and +//! `zkVM` implementation of other zkVM crates `ere-{zkvm}`, it requires only +//! `docker` to be installed, but no zkVM specific SDK. +//! +//! ## Example +//! +//! ```rust,no_run +//! # fn main() -> Result<(), Box> { +//! use ere_dockerized::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM}; +//! use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM}; +//! use std::path::Path; +//! +//! // Compile a guest program +//! let compiler = EreDockerizedCompiler::new(ErezkVM::SP1, "mounting/directory"); +//! let guest_path = Path::new("relative/path/to/guest/program"); +//! let program = compiler.compile(&guest_path)?; +//! +//! // Create zkVM instance +//! let zkvm = EreDockerizedzkVM::new( +//! ErezkVM::SP1, +//! program, +//! ProverResourceType::Cpu +//! )?; +//! +//! // Prepare inputs +//! let mut inputs = Input::new(); +//! inputs.write(42u32); +//! inputs.write(100u16); +//! +//! // Execute program +//! let execution_report = zkvm.execute(&inputs)?; +//! println!("Execution cycles: {}", execution_report.total_num_cycles); +//! +//! // Generate proof +//! let (proof, proving_report) = zkvm.prove(&inputs)?; +//! println!("Proof generated in: {:?}", proving_report.proving_time); +//! +//! // Verify proof +//! zkvm.verify(&proof)?; +//! println!("Proof verified successfully!"); +//! # Ok(()) +//! # } +//! ``` + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use crate::{ + docker::{DockerBuildCmd, DockerRunCmd, docker_image_exists}, + error::{CommonError, CompileError, DockerizedError, ExecuteError, ProveError, VerifyError}, +}; +use serde::{Deserialize, Serialize}; +use std::{ + env, + fmt::{self, Display, Formatter}, + fs, iter, + path::{Path, PathBuf}, + str::FromStr, +}; +use tempfile::TempDir; +use zkvm_interface::{ + Compiler, Input, ProgramExecutionReport, ProgramProvingReport, ProverResourceType, zkVM, + zkVMError, +}; + +include!(concat!(env!("OUT_DIR"), "/crate_version.rs")); +include!(concat!(env!("OUT_DIR"), "/zkvm_sdk_version_impl.rs")); + +pub mod docker; +pub mod error; +pub mod input; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ErezkVM { + Jolt, + Nexus, + OpenVM, + Pico, + Risc0, + SP1, + Zisk, +} + +impl ErezkVM { + pub fn as_str(&self) -> &'static str { + match self { + Self::Jolt => "jolt", + Self::Nexus => "nexus", + Self::OpenVM => "openvm", + Self::Pico => "pico", + Self::Risc0 => "risc0", + Self::SP1 => "sp1", + Self::Zisk => "zisk", + } + } + + pub fn base_tag(&self, version: &str) -> String { + format!("ere-base:{version}") + } + + pub fn base_zkvm_tag(&self, version: &str) -> String { + format!("ere-base-{self}:{version}") + } + + pub fn cli_zkvm_tag(&self, version: &str) -> String { + format!("ere-cli-{self}:{version}") + } + + /// This method builds 3 Docker images in sequence: + /// 1. `ere-base:latest`: Base image with common dependencies + /// 2. `ere-base-{zkvm}:latest`: zkVM-specific base image with the zkVM SDK + /// 3. `ere-cli-{zkvm}:latest`: CLI image with the `ere-cli` binary built with feature `{zkvm}` + /// + /// Images are cached and only rebuilt if they don't exist or if the + /// `ERE_FORCE_REBUILD_DOCKER_IMAGE=true` environment variable is set. + pub fn build_docker_image(&self) -> Result<(), CommonError> { + let workspace_dir = workspace_dir(); + + let force_rebuild = env::var("ERE_FORCE_REBUILD_DOCKER_IMAGE") + .ok() + .and_then(|s| s.parse::().ok()) + .unwrap_or_default(); + + if force_rebuild || !docker_image_exists(self.base_tag(CRATE_VERSION))? { + DockerBuildCmd::new() + .file( + workspace_dir + .join("docker") + .join("base") + .join("Dockerfile.base"), + ) + .tag(self.base_tag(CRATE_VERSION)) + .tag(self.base_tag("latest")) + .exec(&workspace_dir) + .map_err(CommonError::DockerBuildCmd)?; + } + + if force_rebuild || !docker_image_exists(self.base_zkvm_tag(CRATE_VERSION))? { + DockerBuildCmd::new() + .file( + workspace_dir + .join("docker") + .join(self.as_str()) + .join("Dockerfile"), + ) + .tag(self.base_zkvm_tag(CRATE_VERSION)) + .tag(self.base_zkvm_tag("latest")) + .bulid_arg("BASE_IMAGE_TAG", self.base_tag(CRATE_VERSION)) + .exec(&workspace_dir) + .map_err(CommonError::DockerBuildCmd)?; + } + + if force_rebuild || !docker_image_exists(self.cli_zkvm_tag(CRATE_VERSION))? { + DockerBuildCmd::new() + .file(workspace_dir.join("docker").join("cli").join("Dockerfile")) + .tag(self.cli_zkvm_tag(CRATE_VERSION)) + .tag(self.cli_zkvm_tag("latest")) + .bulid_arg("BASE_ZKVM_IMAGE_TAG", self.base_zkvm_tag(CRATE_VERSION)) + .bulid_arg("ZKVM", self.as_str()) + .exec(&workspace_dir) + .map_err(CommonError::DockerBuildCmd)?; + } + + Ok(()) + } +} + +impl FromStr for ErezkVM { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(match s { + "jolt" => Self::Jolt, + "nexus" => Self::Nexus, + "openvm" => Self::OpenVM, + "pico" => Self::Pico, + "risc0" => Self::Risc0, + "sp1" => Self::SP1, + "zisk" => Self::Zisk, + _ => return Err(format!("Unsupported zkvm {s}")), + }) + } +} + +impl Display for ErezkVM { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +pub struct EreDockerizedCompiler { + zkvm: ErezkVM, + mount_directory: PathBuf, +} + +impl EreDockerizedCompiler { + pub fn new(zkvm: ErezkVM, mount_directory: impl AsRef) -> Self { + Self { + zkvm, + mount_directory: mount_directory.as_ref().to_path_buf(), + } + } +} + +/// Wrapper for serialized program. +#[derive(Clone, Serialize, Deserialize)] +pub struct SerializedProgram(Vec); + +impl Compiler for EreDockerizedCompiler { + type Error = CompileError; + type Program = SerializedProgram; + + fn compile(&self, guest_directory: &Path) -> Result { + self.zkvm.build_docker_image()?; + + let guest_relative_path = guest_directory + .strip_prefix(&self.mount_directory) + .map_err(|_| CompileError::GuestNotInMountingDirecty { + mounting_directory: self.mount_directory.to_path_buf(), + guest_directory: guest_directory.to_path_buf(), + })?; + let guest_path_in_docker = PathBuf::from("/guest").join(guest_relative_path); + + let tempdir = TempDir::new() + .map_err(|err| CommonError::io(err, "Failed to create temporary directory"))?; + + DockerRunCmd::new(self.zkvm.cli_zkvm_tag(CRATE_VERSION)) + .rm() + .volume(&self.mount_directory, "/guest") + .volume(tempdir.path(), "/guest-output") + .exec([ + "compile", + "--guest-path", + guest_path_in_docker.to_string_lossy().as_ref(), + "--program-path", + "/guest-output/program", + ]) + .map_err(CommonError::DockerRunCmd)?; + + let program_path = tempdir.path().join("program"); + let program = fs::read(&program_path).map_err(|err| { + CommonError::io( + err, + format!( + "Failed to read compiled program at {}", + program_path.display() + ), + ) + })?; + Ok(SerializedProgram(program)) + } +} + +pub struct EreDockerizedzkVM { + zkvm: ErezkVM, + program: SerializedProgram, + resource: ProverResourceType, +} + +impl EreDockerizedzkVM { + pub fn new( + zkvm: ErezkVM, + program: SerializedProgram, + resource: ProverResourceType, + ) -> Result { + zkvm.build_docker_image()?; + Ok(Self { + zkvm, + program, + resource, + }) + } +} + +impl zkVM for EreDockerizedzkVM { + fn execute(&self, inputs: &Input) -> Result { + let tempdir = TempDir::new() + .map_err(|err| CommonError::io(err, "Failed to create temporary directory")) + .map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?; + + fs::write(tempdir.path().join("program"), &self.program.0) + .map_err(|err| CommonError::io(err, "Failed to write program")) + .map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?; + + fs::write( + tempdir.path().join("input"), + self.zkvm + .serialize_inputs(inputs) + .map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?, + ) + .map_err(|err| CommonError::io(err, "Failed to write input")) + .map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?; + + let mut cmd = DockerRunCmd::new(self.zkvm.cli_zkvm_tag(CRATE_VERSION)) + .rm() + .volume(tempdir.path(), "/workspace"); + + if matches!(self.resource, ProverResourceType::Gpu) { + cmd = cmd.gpus("all") + } + + cmd.exec( + iter::empty() + .chain([ + "execute", + "--program-path", + "/workspace/program", + "--input-path", + "/workspace/input", + "--report-path", + "/workspace/report", + ]) + .chain(self.resource.to_args()), + ) + .map_err(CommonError::DockerRunCmd) + .map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?; + + let report_bytes = fs::read(tempdir.path().join("report")) + .map_err(|err| CommonError::io(err, "Failed to read report")) + .map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?; + + let report = bincode::deserialize(&report_bytes) + .map_err(|err| CommonError::serilization(err, "Failed to deserialize report")) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?; + Ok(report) + } + + fn prove(&self, inputs: &Input) -> Result<(Vec, ProgramProvingReport), zkVMError> { + let tempdir = TempDir::new() + .map_err(|err| CommonError::io(err, "Failed to create temporary directory")) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?; + + fs::write(tempdir.path().join("program"), &self.program.0) + .map_err(|err| CommonError::io(err, "Failed to write program")) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?; + fs::write( + tempdir.path().join("input"), + self.zkvm + .serialize_inputs(inputs) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?, + ) + .map_err(|err| CommonError::io(err, "Failed to write input")) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?; + + let mut cmd = DockerRunCmd::new(self.zkvm.cli_zkvm_tag(CRATE_VERSION)) + .rm() + .volume(tempdir.path(), "/workspace"); + + if matches!(self.resource, ProverResourceType::Gpu) { + cmd = cmd.gpus("all") + } + + cmd.exec( + iter::empty() + .chain([ + "prove", + "--program-path", + "/workspace/program", + "--input-path", + "/workspace/input", + "--proof-path", + "/workspace/proof", + "--report-path", + "/workspace/report", + ]) + .chain(self.resource.to_args()), + ) + .map_err(CommonError::DockerRunCmd) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?; + + let proof = fs::read(tempdir.path().join("proof")) + .map_err(|err| CommonError::io(err, "Failed to read proof")) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?; + let report_bytes = fs::read(tempdir.path().join("report")) + .map_err(|err| CommonError::io(err, "Failed to read report")) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?; + let report = bincode::deserialize(&report_bytes) + .map_err(|err| CommonError::serilization(err, "Failed to deserialize report")) + .map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?; + Ok((proof, report)) + } + + fn verify(&self, proof: &[u8]) -> Result<(), zkVMError> { + let tempdir = TempDir::new() + .map_err(|err| CommonError::io(err, "Failed to create temporary directory")) + .map_err(|err| DockerizedError::Verify(VerifyError::Common(err)))?; + + fs::write(tempdir.path().join("program"), &self.program.0) + .map_err(|err| CommonError::io(err, "Failed to write program")) + .map_err(|err| DockerizedError::Verify(VerifyError::Common(err)))?; + fs::write(tempdir.path().join("proof"), proof) + .map_err(|err| CommonError::io(err, "Failed to write proof")) + .map_err(|err| DockerizedError::Verify(VerifyError::Common(err)))?; + + DockerRunCmd::new(self.zkvm.cli_zkvm_tag(CRATE_VERSION)) + .rm() + .volume(tempdir.path(), "/workspace") + .exec([ + "verify", + "--program-path", + "/workspace/program", + "--proof-path", + "/workspace/proof", + ]) + .map_err(CommonError::DockerRunCmd) + .map_err(|err| DockerizedError::Verify(VerifyError::Common(err)))?; + + Ok(()) + } + + fn name(&self) -> &'static str { + self.zkvm.as_str() + } + + fn sdk_version(&self) -> &'static str { + self.zkvm.sdk_version() + } +} + +fn workspace_dir() -> PathBuf { + let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + dir.pop(); + dir.pop(); + dir.canonicalize().unwrap() +} + +#[cfg(test)] +mod test { + use crate::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM, workspace_dir}; + use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM}; + + // TODO: Test other ere-{zkvm} when they are end-to-end ready: + // - ere-jolt + // - ere-nexus + // - ere-pico + + #[test] + fn dockerized_openvm() { + let zkvm = ErezkVM::OpenVM; + + let guest_directory = workspace_dir().join(format!("tests/{zkvm}/compile/basic")); + let program = EreDockerizedCompiler::new(zkvm, workspace_dir()) + .compile(&guest_directory) + .unwrap(); + + let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap(); + + let mut inputs = Input::new(); + inputs.write(42u64); + + let _report = zkvm.execute(&inputs).unwrap(); + + let (proof, _report) = zkvm.prove(&inputs).unwrap(); + + zkvm.verify(&proof).unwrap(); + } + + #[test] + fn dockerized_risc0() { + let zkvm = ErezkVM::Risc0; + + let guest_directory = workspace_dir().join(format!("tests/{zkvm}/compile/basic")); + let program = EreDockerizedCompiler::new(zkvm, workspace_dir()) + .compile(&guest_directory) + .unwrap(); + + let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap(); + + let mut inputs = Input::new(); + inputs.write(42u32); + + let _report = zkvm.execute(&inputs).unwrap(); + + let (proof, _report) = zkvm.prove(&inputs).unwrap(); + + zkvm.verify(&proof).unwrap(); + } + + #[test] + fn dockerized_sp1() { + let zkvm = ErezkVM::SP1; + + let guest_directory = workspace_dir().join(format!("tests/{zkvm}/prove/basic")); + let program = EreDockerizedCompiler::new(zkvm, workspace_dir()) + .compile(&guest_directory) + .unwrap(); + + let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap(); + + let mut inputs = Input::new(); + inputs.write(42u32); + inputs.write(42u16); + + let _report = zkvm.execute(&inputs).unwrap(); + + let (proof, _report) = zkvm.prove(&inputs).unwrap(); + + zkvm.verify(&proof).unwrap(); + } + + #[test] + fn dockerized_zisk() { + let zkvm = ErezkVM::Zisk; + + let guest_directory = workspace_dir().join(format!("tests/{zkvm}/prove/basic")); + let program = EreDockerizedCompiler::new(zkvm, workspace_dir()) + .compile(&guest_directory) + .unwrap(); + + let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap(); + + let mut inputs = Input::new(); + inputs.write(42u32); + inputs.write(42u16); + + let _report = zkvm.execute(&inputs).unwrap(); + + let (proof, _report) = zkvm.prove(&inputs).unwrap(); + + zkvm.verify(&proof).unwrap(); + } +} diff --git a/crates/ere-jolt/src/lib.rs b/crates/ere-jolt/src/lib.rs index 41ea007..9986d56 100644 --- a/crates/ere-jolt/src/lib.rs +++ b/crates/ere-jolt/src/lib.rs @@ -27,14 +27,9 @@ impl Compiler for JOLT_TARGET { type Program = Vec; - fn compile( - workspace_directory: &Path, - guest_relative: &Path, - ) -> Result { - let guest_dir = workspace_directory.join(guest_relative); - + fn compile(&self, guest_dir: &Path) -> Result { // Change current directory for `Program::build` to build guest program. - set_current_dir(&guest_dir).map_err(|source| CompileError::SetCurrentDirFailed { + set_current_dir(guest_dir).map_err(|source| CompileError::SetCurrentDirFailed { source, path: guest_dir.to_path_buf(), })?; @@ -153,7 +148,7 @@ pub fn program(elf: &[u8]) -> Result<(TempDir, jolt::host::Program), zkVMError> #[cfg(test)] mod tests { use crate::{EreJolt, JOLT_TARGET}; - use std::path::{Path, PathBuf}; + use std::path::PathBuf; use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM}; // TODO: for now, we just get one test file @@ -172,29 +167,29 @@ mod tests { #[test] fn test_compile_trait() { let test_guest_path = get_compile_test_guest_program_path(); - let elf = JOLT_TARGET::compile(&test_guest_path, Path::new("")).unwrap(); + let elf = JOLT_TARGET.compile(&test_guest_path).unwrap(); assert!(!elf.is_empty(), "elf has not been compiled"); } #[test] fn test_execute() { let test_guest_path = get_compile_test_guest_program_path(); - let program = JOLT_TARGET::compile(&test_guest_path, Path::new("")).unwrap(); + let elf = JOLT_TARGET.compile(&test_guest_path).unwrap(); let mut inputs = Input::new(); inputs.write(1_u32); - let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreJolt::new(elf, ProverResourceType::Cpu).unwrap(); zkvm.execute(&inputs).unwrap(); } #[test] fn test_prove_verify() { let test_guest_path = get_compile_test_guest_program_path(); - let program = JOLT_TARGET::compile(&test_guest_path, Path::new("")).unwrap(); + let elf = JOLT_TARGET.compile(&test_guest_path).unwrap(); let mut inputs = Input::new(); inputs.write(1_u32); - let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreJolt::new(elf, ProverResourceType::Cpu).unwrap(); let (proof, _) = zkvm.prove(&inputs).unwrap(); zkvm.verify(&proof).unwrap(); } diff --git a/crates/ere-nexus/src/lib.rs b/crates/ere-nexus/src/lib.rs index 4c06660..bb9ab7d 100644 --- a/crates/ere-nexus/src/lib.rs +++ b/crates/ere-nexus/src/lib.rs @@ -29,19 +29,14 @@ impl Compiler for NEXUS_TARGET { type Program = PathBuf; - fn compile( - workspace_directory: &Path, - guest_relative: &Path, - ) -> Result { - let guest_path = workspace_directory.join(guest_relative); - + fn compile(&self, guest_path: &Path) -> Result { // 1. Check guest path if !guest_path.exists() { return Err(NexusError::PathNotFound(guest_path.to_path_buf())); } - std::env::set_current_dir(&guest_path).map_err(|e| CompileError::Client(e.into()))?; + std::env::set_current_dir(guest_path).map_err(|e| CompileError::Client(e.into()))?; - let package_name = get_cargo_package_name(&guest_path) + let package_name = get_cargo_package_name(guest_path) .ok_or(CompileError::Client(Box::from(format!( "Failed to get guest package name, where guest path: {:?}", guest_path @@ -182,7 +177,7 @@ mod tests { #[test] fn test_compile() -> anyhow::Result<()> { let test_guest_path = get_test_guest_program_path(); - let elf_path = NEXUS_TARGET::compile(&test_guest_path, Path::new(""))?; + let elf_path = NEXUS_TARGET.compile(&test_guest_path)?; let prover: Stwo = Stwo::new_from_file(&elf_path.to_string_lossy().to_string())?; let elf = prover.elf.clone(); assert!( @@ -195,8 +190,9 @@ mod tests { #[test] fn test_execute() { let test_guest_path = get_test_guest_program_path(); - let elf = - NEXUS_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed"); + let elf = NEXUS_TARGET + .compile(&test_guest_path) + .expect("compilation failed"); let mut input = Input::new(); input.write(10u64); @@ -207,7 +203,7 @@ mod tests { #[test] fn test_prove_verify() -> anyhow::Result<()> { let test_guest_path = get_test_guest_program_path(); - let elf = NEXUS_TARGET::compile(&test_guest_path, Path::new(""))?; + let elf = NEXUS_TARGET.compile(&test_guest_path)?; let mut input = Input::new(); input.write(10u64); diff --git a/crates/ere-openvm/src/lib.rs b/crates/ere-openvm/src/lib.rs index 00f1d50..4d46b1f 100644 --- a/crates/ere-openvm/src/lib.rs +++ b/crates/ere-openvm/src/lib.rs @@ -37,9 +37,8 @@ impl Compiler for OPENVM_TARGET { type Program = OpenVMProgram; // Inlining `openvm_sdk::Sdk::build` in order to get raw elf bytes. - fn compile(workspace_path: &Path, guest_relative: &Path) -> Result { - let guest_directory = workspace_path.join(guest_relative); - let pkg = openvm_build::get_package(&guest_directory); + fn compile(&self, guest_directory: &Path) -> Result { + let pkg = openvm_build::get_package(guest_directory); let guest_opts = GuestOptions::default().with_profile("release".to_string()); let target_dir = match openvm_build::build_guest_package(&pkg, &guest_opts, None, &None) { Ok(target_dir) => target_dir, @@ -47,7 +46,7 @@ impl Compiler for OPENVM_TARGET { Err(None) => return Err(CompileError::BuildSkipped.into()), }; - let elf_path = openvm_build::find_unique_executable(&guest_directory, target_dir, &None) + let elf_path = openvm_build::find_unique_executable(guest_directory, target_dir, &None) .map_err(|e| CompileError::UniqueElfNotFound(e.into()))?; let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed { source, @@ -225,8 +224,7 @@ mod tests { #[test] fn test_compile() { let test_guest_path = get_compile_test_guest_program_path(); - let program = - OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed"); + let program = OPENVM_TARGET.compile(&test_guest_path).unwrap(); assert!(!program.elf.is_empty(), "ELF bytes should not be empty."); } @@ -235,8 +233,7 @@ mod tests { fn test_execute_empty_input_panic() { // Panics because the program expects input arguments, but we supply none let test_guest_path = get_compile_test_guest_program_path(); - let program = - OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed"); + let program = OPENVM_TARGET.compile(&test_guest_path).unwrap(); let empty_input = Input::new(); let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap(); @@ -246,8 +243,7 @@ mod tests { #[test] fn test_execute() { let test_guest_path = get_compile_test_guest_program_path(); - let program = - OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed"); + let program = OPENVM_TARGET.compile(&test_guest_path).unwrap(); let mut input = Input::new(); input.write(10u64); @@ -258,8 +254,7 @@ mod tests { #[test] fn test_prove_verify() { let test_guest_path = get_compile_test_guest_program_path(); - let program = - OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed"); + let program = OPENVM_TARGET.compile(&test_guest_path).unwrap(); let mut input = Input::new(); input.write(10u64); diff --git a/crates/ere-pico/src/lib.rs b/crates/ere-pico/src/lib.rs index 740b617..c0de2cd 100644 --- a/crates/ere-pico/src/lib.rs +++ b/crates/ere-pico/src/lib.rs @@ -17,12 +17,7 @@ impl Compiler for PICO_TARGET { type Program = Vec; - fn compile( - workspace_directory: &Path, - guest_relative: &Path, - ) -> Result { - let guest_path = workspace_directory.join(guest_relative); - + fn compile(&self, guest_path: &Path) -> Result { // 1. Check guest path if !guest_path.exists() { return Err(PicoError::PathNotFound(guest_path.to_path_buf())); @@ -30,7 +25,7 @@ impl Compiler for PICO_TARGET { // 2. Run `cargo pico build` let status = Command::new("cargo") - .current_dir(&guest_path) + .current_dir(guest_path) .env("RUST_LOG", "info") .args(["pico", "build"]) .status()?; // From → Spawn @@ -145,7 +140,7 @@ impl zkVM for ErePico { #[cfg(test)] mod tests { use crate::PICO_TARGET; - use std::{path::Path, path::PathBuf}; + use std::path::PathBuf; use zkvm_interface::Compiler; fn get_compile_test_guest_program_path() -> PathBuf { @@ -172,7 +167,7 @@ mod tests { let test_guest_path = get_compile_test_guest_program_path(); println!("Using test guest path: {}", test_guest_path.display()); - match PICO_TARGET::compile(&test_guest_path, Path::new("")) { + match PICO_TARGET.compile(&test_guest_path) { Ok(elf_bytes) => { assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty."); } diff --git a/crates/ere-risc0/Cargo.toml b/crates/ere-risc0/Cargo.toml index 279573e..8867584 100644 --- a/crates/ere-risc0/Cargo.toml +++ b/crates/ere-risc0/Cargo.toml @@ -7,8 +7,9 @@ license.workspace = true [dependencies] zkvm-interface = { workspace = true } -build-utils = { workspace = true } anyhow = "1.0" +# Use `risc0-build` to build package instead of `cargo-risczero` for `unstable` features +risc0-build = { version = "^2.3.0", features = ["unstable"] } risc0-zkvm = { version = "^2.3.0", features = ["unstable"] } borsh = "1.5.7" hex = "*" @@ -20,6 +21,7 @@ serde = { version = "1.0.219", features = ["derive", "rc"] } tracing = "0.1" bytemuck = "1.13" bincode = "1.3" +cargo_metadata = "0.19" [build-dependencies] build-utils = { workspace = true } diff --git a/crates/ere-risc0/src/compile.rs b/crates/ere-risc0/src/compile.rs index 4927947..5b1e92b 100644 --- a/crates/ere-risc0/src/compile.rs +++ b/crates/ere-risc0/src/compile.rs @@ -1,80 +1,42 @@ use crate::error::CompileError; -use build_utils::docker; +use cargo_metadata::MetadataCommand; +use risc0_build::GuestOptions; use risc0_zkvm::Digest; use serde::{Deserialize, Serialize}; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; -use tempfile::TempDir; +use std::path::Path; 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, pub(crate) image_id: Digest, } -pub fn compile_risc0_program( - workspace_directory: &Path, - guest_program_relative: &Path, -) -> Result { - // 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)))?; +pub fn compile_risc0_program(guest_directory: &Path) -> Result { + info!("Compiling Risc0 program at {}", guest_directory.display()); - // Prepare paths for compilation - let mount_directory_str = workspace_directory - .to_str() - .ok_or_else(|| CompileError::InvalidMountPath(workspace_directory.to_path_buf()))?; + let metadata = MetadataCommand::new().current_dir(guest_directory).exec()?; + let package = metadata + .root_package() + .ok_or_else(|| CompileError::MissingPackageName { + path: guest_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 guest = + risc0_build::build_package(package, &metadata.target_directory, GuestOptions::default()) + .map_err(|source| CompileError::Risc0BuildFailure { + source, + crate_path: guest_directory.to_path_buf(), + })? + .into_iter() + .next() + .ok_or(CompileError::Risc0BuildMissingGuest)?; - 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()))?; + let elf = guest.elf.to_vec(); + let image_id = guest.image_id; - 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)?; + info!("Risc0 program compiled OK - {} bytes", elf.len()); + info!("Image ID - {image_id}"); Ok(Risc0Program { elf, image_id }) } @@ -83,7 +45,7 @@ pub fn compile_risc0_program( mod tests { mod compile { use crate::compile::compile_risc0_program; - use std::path::{Path, PathBuf}; + use std::path::PathBuf; fn get_test_risc0_methods_crate_path() -> PathBuf { let workspace_dir = env!("CARGO_WORKSPACE_DIR"); @@ -100,8 +62,8 @@ mod tests { 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"); + let program = + compile_risc0_program(&test_methods_path).expect("risc0 compilation failed"); assert!( !program.elf.is_empty(), "Risc0 ELF bytes should not be empty." diff --git a/crates/ere-risc0/src/error.rs b/crates/ere-risc0/src/error.rs index 48f9eaf..cd8e2fb 100644 --- a/crates/ere-risc0/src/error.rs +++ b/crates/ere-risc0/src/error.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{io, path::PathBuf}; use thiserror::Error; #[derive(Debug, Error)] @@ -9,24 +9,28 @@ pub enum Risc0Error { #[derive(Debug, Error)] pub enum CompileError { - #[error("Failed to build Docker image: {0}")] - DockerImageBuildFailed(#[source] Box), - #[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), + #[error("{context}: {source}")] + Io { + #[source] + source: io::Error, + context: &'static str, + }, + #[error("`cargo metadata` failed: {0}")] + MetadataCommand(#[from] cargo_metadata::Error), + #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] + MissingPackageName { path: PathBuf }, + #[error("`risc0_build::build_package` for {crate_path} failed: {source}")] + Risc0BuildFailure { + #[source] + source: anyhow::Error, + crate_path: PathBuf, + }, + #[error("`risc0_build::build_package` succeeded but failed to find guest")] + Risc0BuildMissingGuest, +} + +impl CompileError { + pub fn io(e: io::Error, context: &'static str) -> Self { + Self::Io { source: e, context } + } } diff --git a/crates/ere-risc0/src/lib.rs b/crates/ere-risc0/src/lib.rs index 13754cc..f48e832 100644 --- a/crates/ere-risc0/src/lib.rs +++ b/crates/ere-risc0/src/lib.rs @@ -1,11 +1,7 @@ -use build_utils::docker; +use crate::error::Risc0Error; use compile::compile_risc0_program; -use risc0_zkvm::Receipt; -use std::{ - fs, - path::{Path, PathBuf}, -}; -use tempfile::TempDir; +use risc0_zkvm::{ExecutorEnv, ProverOpts, Receipt, default_executor, default_prover}; +use std::{path::Path, time::Instant}; use zkvm_interface::{ Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, ProverResourceType, zkVM, zkVMError, @@ -14,10 +10,9 @@ use zkvm_interface::{ include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); mod compile; -pub use compile::Risc0Program; - mod error; -use error::Risc0Error; + +pub use compile::Risc0Program; #[allow(non_camel_case_types)] pub struct RV32_IM_RISC0_ZKVM_ELF; @@ -27,11 +22,8 @@ impl Compiler for RV32_IM_RISC0_ZKVM_ELF { type Program = Risc0Program; - fn compile( - workspace_directory: &Path, - guest_relative: &Path, - ) -> Result { - compile_risc0_program(workspace_directory, guest_relative).map_err(Risc0Error::from) + fn compile(&self, guest_directory: &Path) -> Result { + compile_risc0_program(guest_directory).map_err(Risc0Error::from) } } @@ -71,94 +63,55 @@ pub struct EreRisc0 { impl zkVM for EreRisc0 { fn execute(&self, inputs: &Input) -> Result { - // 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())); + 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()))?; - // 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) + 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, 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())); + 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()))?; - // 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)))?; + 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(); - Ok((proof, report)) + 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> { @@ -179,25 +132,6 @@ impl zkVM for EreRisc0 { } } -// Serialize input bytes in the same way as the `ExecutorEnvBuilder`. -fn serialize_input(inputs: &Input) -> Result, 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; @@ -218,7 +152,7 @@ mod prove_tests { fn get_compiled_test_r0_elf_for_prove() -> Result { let test_guest_path = get_prove_test_guest_program_path(); - RV32_IM_RISC0_ZKVM_ELF::compile(&test_guest_path, Path::new("")) + RV32_IM_RISC0_ZKVM_ELF.compile(&test_guest_path) } #[test] @@ -266,7 +200,7 @@ mod execute_tests { fn get_compiled_test_r0_elf() -> Result { let test_guest_path = get_execute_test_guest_program_path(); - RV32_IM_RISC0_ZKVM_ELF::compile(&test_guest_path, Path::new("")) + RV32_IM_RISC0_ZKVM_ELF.compile(&test_guest_path) } fn get_execute_test_guest_program_path() -> PathBuf { diff --git a/crates/ere-sp1/Cargo.toml b/crates/ere-sp1/Cargo.toml index c30efe5..f2661c1 100644 --- a/crates/ere-sp1/Cargo.toml +++ b/crates/ere-sp1/Cargo.toml @@ -7,12 +7,12 @@ license.workspace = true [dependencies] zkvm-interface = { workspace = true } -build-utils.workspace = true sp1-sdk = "5.1.0" tempfile = "3.3" bincode = "1.3" thiserror = "2" tracing = "0.1" +toml.workspace = true [build-dependencies] build-utils.workspace = true diff --git a/crates/ere-sp1/src/compile.rs b/crates/ere-sp1/src/compile.rs index cd3fdee..66b009f 100644 --- a/crates/ere-sp1/src/compile.rs +++ b/crates/ere-sp1/src/compile.rs @@ -1,65 +1,96 @@ -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; - -use build_utils::docker; +use crate::error::CompileError; +use std::{fs, path::Path, process::Command}; use tempfile::TempDir; use tracing::info; -use crate::error::CompileError; +pub fn compile(guest_directory: &Path) -> Result, CompileError> { + info!("Compiling SP1 program at {}", guest_directory.display()); -pub fn compile( - workspace_directory: &Path, - guest_program_relative: &Path, -) -> Result, CompileError> { - // Build the SP1 docker image - let tag = "ere-build-sp1:latest"; - docker::build_image(&PathBuf::from("docker/sp1/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() - .with_volume(mount_directory_str, "/guest-workspace") - .with_volume(elf_output_dir_str, "/output") - .with_command(["./guest-compiler", container_guest_program_str, "/output"]); - - let status = docker_cmd - .run() - .map_err(CompileError::DockerCommandFailed)?; - - if !status.success() { - return Err(CompileError::DockerContainerRunFailed(status)); + if !guest_directory.exists() || !guest_directory.is_dir() { + return Err(CompileError::InvalidProgramPath( + guest_directory.to_path_buf(), + )); } - // 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 guest_manifest_path = guest_directory.join("Cargo.toml"); + if !guest_manifest_path.exists() { + return Err(CompileError::CargoTomlMissing { + program_dir: guest_directory.to_path_buf(), + manifest_path: guest_manifest_path.clone(), + }); + } - Ok(elf) + // ── read + parse Cargo.toml ─────────────────────────────────────────── + let manifest_content = + fs::read_to_string(&guest_manifest_path).map_err(|e| CompileError::ReadFile { + path: guest_manifest_path.clone(), + source: e, + })?; + + let manifest_toml: toml::Value = + manifest_content + .parse::() + .map_err(|e| CompileError::ParseCargoToml { + path: guest_manifest_path.clone(), + source: e, + })?; + + let program_name = manifest_toml + .get("package") + .and_then(|p| p.get("name")) + .and_then(|n| n.as_str()) + .ok_or_else(|| CompileError::MissingPackageName { + path: guest_manifest_path.clone(), + })?; + + info!("Parsed program name: {program_name}"); + + // ── build into a temp dir ───────────────────────────────────────────── + let temp_output_dir = TempDir::new_in(guest_directory)?; + let temp_output_dir_path = temp_output_dir.path(); + + info!( + "Running `cargo prove build` → dir: {}", + temp_output_dir_path.display(), + ); + + let status = Command::new("cargo") + .current_dir(guest_directory) + .args([ + "prove", + "build", + "--output-directory", + temp_output_dir_path.to_str().unwrap(), + "--elf-name", + "guest.elf", + ]) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .map_err(|e| CompileError::CargoProveBuild { + cwd: guest_directory.to_path_buf(), + source: e, + })?; + + if !status.success() { + return Err(CompileError::CargoBuildFailed { + status, + path: guest_directory.to_path_buf(), + }); + } + + let elf_path = temp_output_dir_path.join("guest.elf"); + if !elf_path.exists() { + return Err(CompileError::ElfNotFound(elf_path)); + } + + let elf_bytes = fs::read(&elf_path).map_err(|e| CompileError::ReadFile { + path: elf_path, + source: e, + })?; + info!("SP1 program compiled OK - {} bytes", elf_bytes.len()); + + Ok(elf_bytes) } #[cfg(test)] @@ -88,7 +119,7 @@ mod tests { fn test_compile_sp1_program() { let test_guest_path = get_compile_test_guest_program_path(); - match compile(&test_guest_path, Path::new("")) { + match compile(&test_guest_path) { Ok(elf_bytes) => { assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty."); } @@ -101,7 +132,7 @@ mod tests { #[test] fn test_compile_trait() { let test_guest_path = get_compile_test_guest_program_path(); - match RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path, Path::new("")) { + match RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path) { Ok(elf_bytes) => { assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty."); } diff --git a/crates/ere-sp1/src/error.rs b/crates/ere-sp1/src/error.rs index ecd3ea2..b69c9d8 100644 --- a/crates/ere-sp1/src/error.rs +++ b/crates/ere-sp1/src/error.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{path::PathBuf, process::ExitStatus}; use thiserror::Error; use zkvm_interface::zkVMError; @@ -27,22 +27,41 @@ pub enum SP1Error { /// Errors that can be encountered while compiling a SP1 program #[derive(Debug, Error)] pub enum CompileError { - #[error("Failed to build Docker image: {0}")] - DockerImageBuildFailed(#[source] Box), - #[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("Program path does not exist or is not a directory: {0}")] + InvalidProgramPath(PathBuf), + #[error( + "Cargo.toml not found in program directory: {program_dir}. Expected at: {manifest_path}" + )] + CargoTomlMissing { + program_dir: PathBuf, + manifest_path: PathBuf, + }, + #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] + MissingPackageName { path: PathBuf }, + #[error("Compiled ELF not found at expected path: {0}")] + ElfNotFound(PathBuf), + #[error("`cargo prove build` failed with status: {status} for program at {path}")] + CargoBuildFailed { status: ExitStatus, path: PathBuf }, + #[error("Failed to read file at {path}: {source}")] + ReadFile { + path: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("Failed to parse guest Cargo.toml at {path}: {source}")] + ParseCargoToml { + path: PathBuf, + #[source] + source: toml::de::Error, + }, + #[error("Failed to execute `cargo prove build` in {cwd}: {source}")] + CargoProveBuild { + cwd: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("Failed to create temporary output directory: {0}")] + TempDir(#[from] std::io::Error), } #[derive(Debug, Error)] diff --git a/crates/ere-sp1/src/lib.rs b/crates/ere-sp1/src/lib.rs index a5786aa..2e7a871 100644 --- a/crates/ere-sp1/src/lib.rs +++ b/crates/ere-sp1/src/lib.rs @@ -106,11 +106,8 @@ impl Compiler for RV32_IM_SUCCINCT_ZKVM_ELF { type Program = Vec; - fn compile( - workspace_directory: &Path, - guest_relative: &Path, - ) -> Result { - compile::compile(workspace_directory, guest_relative).map_err(SP1Error::from) + fn compile(&self, guest_directory: &Path) -> Result { + compile::compile(guest_directory).map_err(SP1Error::from) } } @@ -235,7 +232,7 @@ mod execute_tests { fn get_compiled_test_sp1_elf() -> Result, SP1Error> { let test_guest_path = get_execute_test_guest_program_path(); - RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path, Path::new("")) + RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path) } fn get_execute_test_guest_program_path() -> PathBuf { @@ -306,7 +303,7 @@ mod prove_tests { fn get_compiled_test_sp1_elf_for_prove() -> Result, SP1Error> { let test_guest_path = get_prove_test_guest_program_path(); - RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path, Path::new("")) + RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path) } #[test] diff --git a/crates/ere-zisk/src/compile.rs b/crates/ere-zisk/src/compile.rs index 2715854..1c04e00 100644 --- a/crates/ere-zisk/src/compile.rs +++ b/crates/ere-zisk/src/compile.rs @@ -154,7 +154,7 @@ mod tests { #[test] fn test_compile_trait() { let test_guest_path = get_compile_test_guest_program_path(); - match RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path, Path::new("")) { + match RV64_IMA_ZISK_ZKVM_ELF.compile(&test_guest_path) { Ok(elf_bytes) => { assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty."); } diff --git a/crates/ere-zisk/src/lib.rs b/crates/ere-zisk/src/lib.rs index 70e55da..e5ace45 100644 --- a/crates/ere-zisk/src/lib.rs +++ b/crates/ere-zisk/src/lib.rs @@ -30,11 +30,8 @@ impl Compiler for RV64_IMA_ZISK_ZKVM_ELF { type Program = Vec; - fn compile( - workspace_directory: &Path, - guest_relative: &Path, - ) -> Result { - compile_zisk_program(&workspace_directory.join(guest_relative)).map_err(ZiskError::Compile) + fn compile(&self, guest_directory: &Path) -> Result { + compile_zisk_program(guest_directory).map_err(ZiskError::Compile) } } @@ -448,7 +445,7 @@ mod execute_tests { fn get_compiled_test_zisk_elf() -> Result, ZiskError> { let test_guest_path = get_execute_test_guest_program_path(); - RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path, Path::new("")) + RV64_IMA_ZISK_ZKVM_ELF.compile(&test_guest_path) } fn get_execute_test_guest_program_path() -> PathBuf { @@ -514,7 +511,7 @@ mod prove_tests { fn get_compiled_test_zisk_elf_for_prove() -> Result, ZiskError> { let test_guest_path = get_prove_test_guest_program_path(); - RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path, Path::new("")) + RV64_IMA_ZISK_ZKVM_ELF.compile(&test_guest_path) } #[test] diff --git a/crates/zkvm-interface/src/lib.rs b/crates/zkvm-interface/src/lib.rs index a4f18cd..0d67aff 100644 --- a/crates/zkvm-interface/src/lib.rs +++ b/crates/zkvm-interface/src/lib.rs @@ -20,10 +20,8 @@ pub trait Compiler { /// Compiles the program and returns the program /// /// # Arguments - /// * `mount_directory` - The base directory (workspace root) - /// * `guest_relative` - The relative path from mount_directory to the guest program - fn compile(mount_directory: &Path, guest_relative: &Path) - -> Result; + /// * `guest_directory` - The path to the guest program directory + fn compile(&self, guest_directory: &Path) -> Result; } /// ResourceType specifies what resource will be used to create the proofs. @@ -37,6 +35,19 @@ pub enum ProverResourceType { Network(NetworkProverConfig), } +#[cfg(feature = "clap")] +impl ProverResourceType { + pub fn to_args(&self) -> Vec<&str> { + match self { + Self::Cpu => vec!["cpu"], + Self::Gpu => vec!["gpu"], + Self::Network(config) => core::iter::once("network") + .chain(config.to_args()) + .collect(), + } + } +} + /// An error that can occur during prove, execute or verification /// of a zkVM. /// diff --git a/crates/zkvm-interface/src/network.rs b/crates/zkvm-interface/src/network.rs index 5cdf505..328a101 100644 --- a/crates/zkvm-interface/src/network.rs +++ b/crates/zkvm-interface/src/network.rs @@ -12,3 +12,13 @@ pub struct NetworkProverConfig { /// Optional API key for authentication pub api_key: Option, } + +#[cfg(feature = "clap")] +impl NetworkProverConfig { + pub fn to_args(&self) -> Vec<&str> { + core::iter::once(["--endpoint", self.endpoint.as_str()]) + .chain(self.api_key.as_deref().map(|val| ["--api-key", val])) + .flatten() + .collect() + } +} diff --git a/docker/cli/Dockerfile b/docker/cli/Dockerfile new file mode 100644 index 0000000..cdf9f69 --- /dev/null +++ b/docker/cli/Dockerfile @@ -0,0 +1,16 @@ +ARG BASE_ZKVM_IMAGE_TAG=ere-base-zkvm:latest + +FROM ${BASE_ZKVM_IMAGE_TAG} + +COPY . /ere + +WORKDIR /ere + +ARG ZKVM + +RUN cargo build --release --package ere-cli --bin ere-cli --features ${ZKVM} && \ + cp /ere/target/release/ere-cli /ere/ere-cli && \ + cargo clean && \ + rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache + +ENTRYPOINT ["/ere/ere-cli"] diff --git a/docker/jolt/Dockerfile b/docker/jolt/Dockerfile index ee4a6a4..eb8be1b 100644 --- a/docker/jolt/Dockerfile +++ b/docker/jolt/Dockerfile @@ -1,5 +1,6 @@ -ARG BASE_IMAGE_TAG=latest -FROM ere-base:${BASE_IMAGE_TAG} +ARG BASE_IMAGE_TAG=ere-base:latest + +FROM ${BASE_IMAGE_TAG} # The ere-base image provides Rust, Cargo (with a default nightly), and common tools. # We operate as root for SDK installation. @@ -17,4 +18,4 @@ RUN /tmp/install_jolt_sdk.sh && rm /tmp/install_jolt_sdk.sh # Clean up the scrip # Verify jolt CLI is accessible. RUN jolt --version -CMD ["/bin/bash"] \ No newline at end of file +CMD ["/bin/bash"] diff --git a/docker/nexus/Dockerfile b/docker/nexus/Dockerfile index d74b2d5..39377b2 100644 --- a/docker/nexus/Dockerfile +++ b/docker/nexus/Dockerfile @@ -1,5 +1,6 @@ -ARG BASE_IMAGE_TAG=latest -FROM ere-base:${BASE_IMAGE_TAG} +ARG BASE_IMAGE_TAG=ere-base:latest + +FROM ${BASE_IMAGE_TAG} # The ere-base image provides Rust, Cargo, and common tools. # We operate as root for SDK installation. @@ -20,21 +21,10 @@ RUN /tmp/install_nexus_sdk.sh && rm /tmp/install_nexus_sdk.sh # Clean up the scr # Define the Nexus toolchain for convenience in subsequent commands if needed, though cargo-nexus should use it. ENV NEXUS_TOOLCHAIN_VERSION="nightly-2025-06-05" +# Set default toolchain +RUN rustup default "$NEXUS_TOOLCHAIN_VERSION" + # Verify Nexus installation RUN echo "Verifying Nexus installation in Dockerfile (post-script)..." && cargo-nexus --version -# Copy the entire ere project context -# The WORKDIR is /app from the base image -WORKDIR /app -COPY . . - -# Build -RUN echo "Build tests for ere-nexus library..." && \ - cargo build --tests --release -p ere-nexus - -# Run tests -RUN echo "Running tests for ere-nexus library..." && \ - cargo test --release -p ere-nexus --lib -- --color always && \ - echo "Running Nexus tests Success..." - -CMD ["/bin/bash"] \ No newline at end of file +CMD ["/bin/bash"] diff --git a/docker/openvm/Dockerfile b/docker/openvm/Dockerfile index c3d24af..c6e0e0c 100644 --- a/docker/openvm/Dockerfile +++ b/docker/openvm/Dockerfile @@ -1,5 +1,6 @@ -ARG BASE_IMAGE_TAG=latest -FROM ere-base:${BASE_IMAGE_TAG} +ARG BASE_IMAGE_TAG=ere-base:latest + +FROM ${BASE_IMAGE_TAG} # The ere-base image provides Rust, Cargo, and common tools. # We operate as root for SDK installation. @@ -20,13 +21,4 @@ ENV OPENVM_TOOLCHAIN_VERSION="nightly-2025-02-14" # Verify cargo-openvm is accessible with the correct toolchain RUN cargo "+${OPENVM_TOOLCHAIN_VERSION}" openvm --version -# Copy the entire ere project context -# The WORKDIR is /app from the base image -WORKDIR /app -COPY . . - -# Run tests -RUN echo "Running tests for ere-openvm library..." && \ - cargo test --release -p ere-openvm --lib -- --color always - -CMD ["/bin/bash"] \ No newline at end of file +CMD ["/bin/bash"] diff --git a/docker/pico/Dockerfile b/docker/pico/Dockerfile index 7e4caa5..134802c 100644 --- a/docker/pico/Dockerfile +++ b/docker/pico/Dockerfile @@ -1,5 +1,6 @@ -ARG BASE_IMAGE_TAG=latest -FROM ere-base:${BASE_IMAGE_TAG} +ARG BASE_IMAGE_TAG=ere-base:latest + +FROM ${BASE_IMAGE_TAG} # The ere-base image provides Rust, Cargo, and common tools. # We operate as root for SDK installation. @@ -19,16 +20,10 @@ RUN /tmp/install_pico_sdk.sh && rm /tmp/install_pico_sdk.sh # Clean up the scrip # Define the Pico toolchain for convenience in subsequent commands if needed, though cargo pico should use it. ENV PICO_TOOLCHAIN_VERSION="nightly-2024-11-27" +# Set default toolchain +RUN rustup default "$PICO_TOOLCHAIN_VERSION" + # Verify Pico installation -RUN echo "Verifying Pico installation in Dockerfile (post-script)..." && cargo "+${PICO_TOOLCHAIN_VERSION}" pico --version +RUN echo "Verifying Pico installation in Dockerfile (post-script)..." && cargo pico --version -# Copy the entire ere project context -# The WORKDIR is /app from the base image -WORKDIR /app -COPY . . - -# Run tests -RUN echo "Running tests for ere-pico library..." && \ - cargo "+${PICO_TOOLCHAIN_VERSION}" test --release -p ere-pico --lib -- --color always - -CMD ["/bin/bash"] \ No newline at end of file +CMD ["/bin/bash"] diff --git a/docker/risc0/Cargo.toml b/docker/risc0/Cargo.toml deleted file mode 100644 index b4271fe..0000000 --- a/docker/risc0/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[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 diff --git a/docker/risc0/Dockerfile b/docker/risc0/Dockerfile index fdd678e..845728a 100644 --- a/docker/risc0/Dockerfile +++ b/docker/risc0/Dockerfile @@ -1,14 +1,6 @@ -ARG BASE_IMAGE_TAG=latest +ARG BASE_IMAGE_TAG=ere-base: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} +FROM ${BASE_IMAGE_TAG} # Copy and run the Risc0 SDK installer script COPY scripts/sdk_installers/install_risc0_sdk.sh /tmp/install_risc0_sdk.sh @@ -22,8 +14,4 @@ RUN echo "Verifying Risc0 installation in Dockerfile (post-script)..." && cargo # Get docker for `cargo risczero build` RUN curl -fsSL https://get.docker.com | sh -# Copy guest compiler binary -COPY --from=builder /risc0-cli/target/release/risc0-cli /risc0-cli/risc0-cli - -# Set entrypoint to `risc0-cli` -ENTRYPOINT ["/risc0-cli/risc0-cli"] +CMD ["/bin/bash"] diff --git a/docker/risc0/src/main.rs b/docker/risc0/src/main.rs deleted file mode 100644 index 8db47ee..0000000 --- a/docker/risc0/src/main.rs +++ /dev/null @@ -1,289 +0,0 @@ -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::().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(()) -} diff --git a/docker/sp1/Cargo.toml b/docker/sp1/Cargo.toml deleted file mode 100644 index 2fd7149..0000000 --- a/docker/sp1/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "sp1-guest-compiler" -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 - -[lints] -workspace = true diff --git a/docker/sp1/Dockerfile b/docker/sp1/Dockerfile index 263e700..48795ca 100644 --- a/docker/sp1/Dockerfile +++ b/docker/sp1/Dockerfile @@ -1,23 +1,9 @@ -ARG BASE_IMAGE_TAG=latest +ARG BASE_IMAGE_TAG=ere-base:latest -# Build guest-compiler binary -FROM rust:1.85 AS builder -RUN apt-get update && apt-get install -y build-essential libclang-dev -WORKDIR /guest-compiler -COPY . . -RUN cargo build --release -p sp1-guest-compiler - -# Build zkVM builder image -FROM ere-base:${BASE_IMAGE_TAG} +FROM ${BASE_IMAGE_TAG} ARG USERNAME=ere_user -# Ensure Cargo/Rustup environment variables are set from the base image for SDK script -# TODO: These should be inherited from ere-base. -ENV RUSTUP_HOME=${RUSTUP_HOME:-/usr/local/rustup} \ - CARGO_HOME=${CARGO_HOME:-/usr/local/cargo} \ - PATH=${PATH:-/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin} - # Copy the SP1 SDK installer script COPY scripts/sdk_installers/install_sp1_sdk.sh /tmp/install_sp1_sdk.sh RUN chmod +x /tmp/install_sp1_sdk.sh @@ -41,11 +27,7 @@ ENV PATH="${SP1UP_HOME}/bin:${SP1_HOME}/bin:$PATH" # Verify SP1 installation (optional here, as script does it, but good for sanity) RUN cargo prove --version -# Copy guest compiler binary -COPY --from=builder /guest-compiler/target/release/sp1-guest-compiler /guest-compiler/guest-compiler -WORKDIR /guest-compiler - -CMD ["/bin/bash"] +CMD ["/bin/bash"] # TODO: Maybe we use root to install it in ere_user and then switch back to ere_user for security -# USER ${USERNAME} # Switch to non-root user again \ No newline at end of file +# USER ${USERNAME} # Switch to non-root user again diff --git a/docker/sp1/src/main.rs b/docker/sp1/src/main.rs deleted file mode 100644 index 3c5b6fb..0000000 --- a/docker/sp1/src/main.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::{fs, path::PathBuf, process::Command}; - -use anyhow::Context; -use clap::Parser; -use toml::Value as TomlValue; -use tracing::info; - -#[derive(Parser)] -#[command(author, version)] -struct Cli { - /// Path to the guest program crate directory. - guest_folder: PathBuf, - - /// Compiled ELF output folder where guest.elf will be placed. - elf_output_folder: PathBuf, -} - -pub fn main() -> anyhow::Result<()> { - let args = Cli::parse(); - - let dir = args.guest_folder; - - info!("Compiling SP1 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::().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 prove build` → dir: {}", - args.elf_output_folder.display() - ); - - let status = Command::new("cargo") - .current_dir(&dir) - .args([ - "prove", - "build", - "--output-directory", - args.elf_output_folder.to_str().unwrap(), - "--elf-name", - "guest.elf", - ]) - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()) - .status() - .with_context(|| format!("Failed to execute `cargo prove build` in {}", dir.display()))?; - - if !status.success() { - anyhow::bail!("Failed to execute `cargo prove build` in {}", dir.display()) - } - - let elf_path = args.elf_output_folder.join("guest.elf"); - 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!("SP1 program compiled OK - {} bytes", elf_bytes.len()); - - Ok(()) -} diff --git a/docker/zisk/Dockerfile b/docker/zisk/Dockerfile index 62245e9..63eddd2 100644 --- a/docker/zisk/Dockerfile +++ b/docker/zisk/Dockerfile @@ -1,18 +1,18 @@ -ARG BASE_IMAGE_TAG=latest -FROM ere-base:${BASE_IMAGE_TAG} +ARG BASE_IMAGE_TAG=ere-base:latest + +FROM ${BASE_IMAGE_TAG} # The ere-base image provides Rust, Cargo, and common tools. -# ZisK requires Ubuntu 22.04 or higher (ere-base uses 22.04 by default). +# ZisK requires Ubuntu 22.04 or higher (ere-base uses 24.04 by default). # We operate as root for SDK and dependency installation. # Install ZisK system dependencies (for Ubuntu) # Taken from https://0xpolygonhermez.github.io/zisk/getting_started/installation.html RUN apt-get update && apt-get install -y --no-install-recommends \ xz-utils \ - jq \ - # build-essential is in ere-base + # jq is in ere-base # curl is in ere-base - # git is in ere-base + # build-essential is in ere-base qemu-system \ libomp-dev \ libgmp-dev \ @@ -28,7 +28,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ openmpi-bin \ openmpi-common \ libclang-dev \ - clang + clang && \ + apt-get clean && rm -rf /var/lib/apt/lists/* RUN wget https://developer.download.nvidia.com/compute/cuda/repos/$(. /etc/os-release && echo "${ID}${VERSION_ID}" | tr -d '.')/$(uname -i)/cuda-keyring_1.1-1_all.deb && \ dpkg -i cuda-keyring_1.1-1_all.deb && \ @@ -37,14 +38,21 @@ RUN wget https://developer.download.nvidia.com/compute/cuda/repos/$(. /etc/os-re apt install -y cuda-toolkit && \ apt-get clean && rm -rf /var/lib/apt/lists/* +# If current environment is in CI or not. +ARG CI + # Copy the ZisK SDK installer script from the workspace context COPY scripts/sdk_installers/install_zisk_sdk.sh /tmp/install_zisk_sdk.sh RUN chmod +x /tmp/install_zisk_sdk.sh # Run the ZisK SDK installation script using ziskup. -# This script installs the 'zisk' Rust toolchain and cargo-zisk. -# TODO: Download the proving key if the CI runner has enough disk space. -RUN SETUP_KEY=verify /tmp/install_zisk_sdk.sh && rm /tmp/install_zisk_sdk.sh # Clean up the script +# This script installs the 'zisk' Rust toolchain and `cargo-zisk` +# +# If argument `CI` is set, we only install verifying key, this is used by github +# CI runner which only has small disk space (proving key requires ~35 GB). +RUN if [ -n "$CI" ]; then export SETUP_KEY=verify; fi && \ + /tmp/install_zisk_sdk.sh && \ + rm /tmp/install_zisk_sdk.sh # Clean up the script # The 'zisk' Rust toolchain is now installed. # cargo-zisk is installed in /root/.zisk/bin. @@ -56,16 +64,4 @@ ENV PATH="${PATH}:${ZISK_BIN_DIR}" # Verify cargo-zisk is accessible RUN echo "Verifying Zisk installation in Dockerfile ..." && cargo-zisk --version -# Copy the entire ere project context -# The WORKDIR is /app from the base image -WORKDIR /app -COPY . . - -# Run only compile and execution test, because proving requires ~31 GiB disk -# space for the provingKey. -# TODO: Run all tests if the CI runner has enough disk space to install the proving key. -RUN echo "Running tests for ere-zisk library..." && \ - rm -rf ~/.zisk/provingKey && \ - cargo test --release -p ere-zisk --lib -- --color always compile::tests execute_tests - -CMD ["/bin/bash"] \ No newline at end of file +CMD ["/bin/bash"] diff --git a/scripts/sdk_installers/install_zisk_sdk.sh b/scripts/sdk_installers/install_zisk_sdk.sh index 3a4cc8c..3b1a674 100755 --- a/scripts/sdk_installers/install_zisk_sdk.sh +++ b/scripts/sdk_installers/install_zisk_sdk.sh @@ -77,6 +77,13 @@ else fi # Step 4: Make sure `lib-c`'s build script is ran. +# +# `ziskos` provides guest program runtime, and `lib-c` is a dependency of `ziskos`, +# when we need to compile guest, the `build.rs` of `lib-c` will need to be ran once, +# but if there are multiple `build.rs` running at the same time, it will panic. +# So here we make sure it's already ran, and the built thing will be stored in +# `$CARGO_HOME/git/checkouts/zisk-{hash}/{rev}/lib-c/c/build`, so could be +# re-used as long as the `ziskos` has the same version. TEMP_DIR=$(mktemp -d) cd "$TEMP_DIR" cargo init . --name build-lib-c