From 0d0bb451ffd1c6b2f8c127048b08643f960b9673 Mon Sep 17 00:00:00 2001 From: Han Date: Tue, 28 Oct 2025 08:24:27 +0800 Subject: [PATCH] Refactor `zkVM` error handling (#179) --- Cargo.lock | 15 +- Cargo.toml | 5 + README.md | 2 +- crates/compile-utils/Cargo.toml | 1 + crates/compile-utils/src/error.rs | 127 +++++++++++++-- crates/compile-utils/src/lib.rs | 4 +- crates/compile-utils/src/rust.rs | 72 ++++++--- crates/dockerized/dockerized/Cargo.toml | 1 + crates/dockerized/dockerized/src/error.rs | 7 - crates/dockerized/dockerized/src/lib.rs | 45 ++---- crates/dockerized/server/src/main.rs | 10 +- crates/test-utils/Cargo.toml | 3 +- crates/zkvm-interface/Cargo.toml | 1 + crates/zkvm-interface/src/error.rs | 148 ++++++++++++++++++ crates/zkvm-interface/src/lib.rs | 50 +----- crates/zkvm/airbender/Cargo.toml | 1 + crates/zkvm/airbender/src/client.rs | 73 ++++----- .../airbender/src/compiler/rust_rv32ima.rs | 26 +-- crates/zkvm/airbender/src/error.rs | 80 ++-------- crates/zkvm/airbender/src/lib.rs | 41 +++-- crates/zkvm/jolt/Cargo.toml | 1 + crates/zkvm/jolt/src/compiler/rust_rv32ima.rs | 10 +- .../src/compiler/rust_rv32ima_customized.rs | 21 +-- crates/zkvm/jolt/src/error.rs | 59 ++----- crates/zkvm/jolt/src/jolt_methods.rs | 4 +- crates/zkvm/jolt/src/lib.rs | 42 +++-- crates/zkvm/miden/Cargo.toml | 2 +- crates/zkvm/miden/src/compiler/miden_asm.rs | 33 ++-- crates/zkvm/miden/src/error.rs | 95 ++++------- crates/zkvm/miden/src/lib.rs | 109 ++++++------- crates/zkvm/nexus/Cargo.toml | 1 + crates/zkvm/nexus/src/compiler/rust_rv32i.rs | 10 +- crates/zkvm/nexus/src/error.rs | 63 +++----- crates/zkvm/nexus/src/lib.rs | 63 ++++---- crates/zkvm/openvm/Cargo.toml | 2 + crates/zkvm/openvm/src/compiler.rs | 12 +- .../zkvm/openvm/src/compiler/rust_rv32ima.rs | 15 +- .../src/compiler/rust_rv32ima_customized.rs | 21 +-- crates/zkvm/openvm/src/error.rs | 116 +++++--------- crates/zkvm/openvm/src/lib.rs | 70 +++++---- crates/zkvm/pico/src/compiler/rust_rv32ima.rs | 12 +- .../src/compiler/rust_rv32ima_customized.rs | 33 ++-- crates/zkvm/pico/src/error.rs | 85 +++------- crates/zkvm/pico/src/lib.rs | 74 +++++---- crates/zkvm/risc0/Cargo.toml | 1 + .../zkvm/risc0/src/compiler/rust_rv32ima.rs | 11 +- .../src/compiler/rust_rv32ima_customized.rs | 15 +- crates/zkvm/risc0/src/error.rs | 53 +++++-- crates/zkvm/risc0/src/lib.rs | 52 +++--- crates/zkvm/sp1/Cargo.toml | 1 + crates/zkvm/sp1/src/compiler/rust_rv32ima.rs | 12 +- .../src/compiler/rust_rv32ima_customized.rs | 54 ++----- crates/zkvm/sp1/src/error.rs | 87 ++-------- crates/zkvm/sp1/src/lib.rs | 55 +++---- crates/zkvm/ziren/Cargo.toml | 5 +- .../src/compiler/rust_mips32r2_customized.rs | 77 +++------ crates/zkvm/ziren/src/error.rs | 87 ++-------- crates/zkvm/ziren/src/lib.rs | 45 +++--- crates/zkvm/zisk/Cargo.toml | 2 +- crates/zkvm/zisk/src/client.rs | 142 +++++++++-------- .../src/compiler/rust_rv64ima_customized.rs | 81 +++------- crates/zkvm/zisk/src/error.rs | 88 ++--------- crates/zkvm/zisk/src/lib.rs | 23 ++- 63 files changed, 1138 insertions(+), 1418 deletions(-) create mode 100644 crates/zkvm-interface/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 780bf59..7a87539 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3731,6 +3731,7 @@ dependencies = [ name = "ere-airbender" version = "0.0.14" dependencies = [ + "anyhow", "bincode 2.0.1", "ere-build-utils", "ere-compile-utils", @@ -3755,6 +3756,7 @@ dependencies = [ name = "ere-compile-utils" version = "0.0.14" dependencies = [ + "anyhow", "cargo_metadata 0.19.2", "tempfile", "thiserror 2.0.12", @@ -3786,6 +3788,7 @@ dependencies = [ name = "ere-dockerized" version = "0.0.14" dependencies = [ + "anyhow", "ere-build-utils", "ere-server", "ere-test-utils", @@ -3809,6 +3812,7 @@ dependencies = [ name = "ere-jolt" version = "0.0.14" dependencies = [ + "anyhow", "ark-serialize 0.5.0", "common", "ere-build-utils", @@ -3824,7 +3828,7 @@ dependencies = [ name = "ere-miden" version = "0.0.14" dependencies = [ - "bincode 2.0.1", + "anyhow", "ere-build-utils", "ere-test-utils", "ere-zkvm-interface", @@ -3842,6 +3846,7 @@ dependencies = [ name = "ere-nexus" version = "0.0.14" dependencies = [ + "anyhow", "bincode 2.0.1", "ere-build-utils", "ere-compile-utils", @@ -3860,10 +3865,12 @@ dependencies = [ name = "ere-openvm" version = "0.0.14" dependencies = [ + "anyhow", "ere-build-utils", "ere-compile-utils", "ere-test-utils", "ere-zkvm-interface", + "eyre", "openvm-build", "openvm-circuit", "openvm-continuations", @@ -3906,6 +3913,7 @@ dependencies = [ "ere-zkvm-interface", "risc0-binfmt", "risc0-build", + "risc0-zkp", "risc0-zkvm", "serde", "thiserror 2.0.12", @@ -3946,6 +3954,7 @@ dependencies = [ name = "ere-sp1" version = "0.0.14" dependencies = [ + "anyhow", "bincode 2.0.1", "ere-build-utils", "ere-compile-utils", @@ -3972,6 +3981,7 @@ dependencies = [ name = "ere-ziren" version = "0.0.14" dependencies = [ + "anyhow", "bincode 2.0.1", "ere-build-utils", "ere-compile-utils", @@ -3986,7 +3996,7 @@ dependencies = [ name = "ere-zisk" version = "0.0.14" dependencies = [ - "bincode 2.0.1", + "anyhow", "blake3", "bytemuck", "ere-build-utils", @@ -4003,6 +4013,7 @@ dependencies = [ name = "ere-zkvm-interface" version = "0.0.14" dependencies = [ + "anyhow", "auto_impl", "bincode 2.0.1", "clap", diff --git a/Cargo.toml b/Cargo.toml index 7e11d19..6a22d49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ bytemuck = "1.23.1" cargo_metadata = "0.19.0" clap = "4.5.42" dashmap = "6.1.0" +eyre = "0.6.12" indexmap = "2.10.0" postcard = "1.0.8" prost = "0.13" @@ -98,6 +99,7 @@ pico-vm = { git = "https://github.com/brevis-network/pico.git", tag = "v1.1.7" } # Risc0 dependencies risc0-build = "3.0.3" +risc0-zkp = { version = "3.0.2", default-features = false } risc0-zkvm = { version = "3.0.3", default-features = false } risc0-binfmt = { version = "3.0.2", default-features = false } @@ -130,6 +132,9 @@ ere-build-utils = { path = "crates/build-utils" } ere-compile-utils = { path = "crates/compile-utils" } ere-test-utils = { path = "crates/test-utils" } +[profile.dev.package.openvm-stark-backend] +opt-level = 3 + [patch.crates-io] # These patches are only needed by Jolt ark-ff = { git = "https://github.com/a16z/arkworks-algebra", branch = "v0.5.0-optimize-mul-u64" } diff --git a/README.md b/README.md index f4e1c15..17350cb 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ fn main() -> Result<(), Box> { let program = compiler.compile(guest_directory)?; // Create zkVM instance - let zkvm = EreSP1::new(program, ProverResourceType::Cpu); + let zkvm = EreSP1::new(program, ProverResourceType::Cpu)?; // Serialize input let input = 42u32.to_le_bytes(); diff --git a/crates/compile-utils/Cargo.toml b/crates/compile-utils/Cargo.toml index fe99ad4..3ecec90 100644 --- a/crates/compile-utils/Cargo.toml +++ b/crates/compile-utils/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true cargo_metadata.workspace = true tempfile.workspace = true thiserror.workspace = true diff --git a/crates/compile-utils/src/error.rs b/crates/compile-utils/src/error.rs index 86922b9..8e209a1 100644 --- a/crates/compile-utils/src/error.rs +++ b/crates/compile-utils/src/error.rs @@ -1,23 +1,122 @@ -use std::{io, path::PathBuf, process::ExitStatus}; +use std::{ + io, + path::{Path, PathBuf}, + process::{Command, ExitStatus, Output}, +}; use thiserror::Error; #[derive(Debug, Error)] -pub enum CompileError { +pub enum CommonError { + #[error("{ctx}: {err}")] + Io { + ctx: String, + #[source] + err: io::Error, + }, + + #[error("Deserialize {id} with `{lib}` failed: {err}")] + Deserialize { + id: String, + lib: String, + #[source] + err: anyhow::Error, + }, + + #[error("Failed to run command `{cmd}`: {err}")] + Command { + cmd: String, + #[source] + err: io::Error, + }, + + #[error("Command `{cmd}` exit with {status}{stdout}{stderr}", + stdout = if stdout.is_empty() { String::new() } else { format!("\nstdout: {}", String::from_utf8_lossy(stdout)) }, + stderr = if stderr.is_empty() { String::new() } else { format!("\nstdout: {}", String::from_utf8_lossy(stderr)) })] + CommandExitNonZero { + cmd: String, + status: ExitStatus, + stdout: Vec, + stderr: Vec, + }, + #[error("`cargo metadata` in {manifest_dir} failed: {err}")] CargoMetadata { manifest_dir: PathBuf, + #[source] err: cargo_metadata::Error, }, - #[error("Root package not found in {0}")] - RootPackageNotFound(PathBuf), - #[error("Failed to create temporary directory: {0}")] - Tempdir(io::Error), - #[error("Failed to create linker script: {0}")] - CreateLinkerScript(io::Error), - #[error("Failed to run `cargo build`: {0}")] - CargoBuild(io::Error), - #[error("`cargo build` failed: {0}")] - CargoBuildFailed(ExitStatus), - #[error("Failed to read built ELF: {0}")] - ReadElf(io::Error), + + #[error("Root package not found in {manifest_dir}")] + CargoRootPackageNotFound { manifest_dir: PathBuf }, +} + +impl CommonError { + pub fn io(ctx: impl AsRef, err: io::Error) -> Self { + let ctx = ctx.as_ref().to_string(); + Self::Io { ctx, err } + } + + pub fn tempdir(err: io::Error) -> Self { + Self::io("Failed to create temporary dir", err) + } + + pub fn create_dir(id: impl AsRef, path: impl AsRef, err: io::Error) -> Self { + let (id, path) = (id.as_ref(), path.as_ref().display()); + Self::io(format!("Failed to create dir {id} at {path}"), err) + } + + pub fn read_file(id: impl AsRef, path: impl AsRef, err: io::Error) -> Self { + let (id, path) = (id.as_ref(), path.as_ref().display()); + Self::io(format!("Failed to write {id} to {path}"), err) + } + + pub fn write_file(id: impl AsRef, path: impl AsRef, err: io::Error) -> Self { + let (id, path) = (id.as_ref(), path.as_ref().display()); + Self::io(format!("Failed to read {id} from {path}"), err) + } + + pub fn deserialize( + id: impl AsRef, + lib: impl AsRef, + err: impl Into, + ) -> Self { + let id = id.as_ref().to_string(); + let lib = lib.as_ref().to_string(); + let err = err.into(); + Self::Deserialize { id, lib, err } + } + + pub fn command(cmd: &Command, err: io::Error) -> Self { + Self::Command { + cmd: format!("{cmd:?}"), + err, + } + } + + pub fn command_exit_non_zero( + cmd: &Command, + status: ExitStatus, + output: Option<&Output>, + ) -> Self { + Self::CommandExitNonZero { + cmd: format!("{cmd:?}"), + status, + stdout: output + .map(|output| &output.stdout) + .cloned() + .unwrap_or_default(), + stderr: output + .map(|output| &output.stderr) + .cloned() + .unwrap_or_default(), + } + } + + pub fn cargo_metadata(manifest_dir: PathBuf, err: cargo_metadata::Error) -> Self { + Self::CargoMetadata { manifest_dir, err } + } + + pub fn cargo_root_package_not_found(manifest_dir: PathBuf) -> Self { + Self::CargoRootPackageNotFound { manifest_dir } + } } diff --git a/crates/compile-utils/src/lib.rs b/crates/compile-utils/src/lib.rs index 22d1db7..fcdc513 100644 --- a/crates/compile-utils/src/lib.rs +++ b/crates/compile-utils/src/lib.rs @@ -2,6 +2,6 @@ mod error; mod rust; pub use { - error::CompileError, - rust::{CargoBuildCmd, cargo_metadata}, + error::CommonError, + rust::{CargoBuildCmd, cargo_metadata, rustc_path}, }; diff --git a/crates/compile-utils/src/rust.rs b/crates/compile-utils/src/rust.rs index 0cf0dc5..dd4520d 100644 --- a/crates/compile-utils/src/rust.rs +++ b/crates/compile-utils/src/rust.rs @@ -1,6 +1,10 @@ -use crate::CompileError; +use crate::CommonError; use cargo_metadata::{Metadata, MetadataCommand}; -use std::{fs, iter, path::Path, process::Command}; +use std::{ + fs, iter, + path::{Path, PathBuf}, + process::Command, +}; use tempfile::tempdir; const CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f"; @@ -74,20 +78,20 @@ impl CargoBuildCmd { &self, manifest_dir: impl AsRef, target: impl AsRef, - ) -> Result, CompileError> { + ) -> Result, CommonError> { let metadata = cargo_metadata(manifest_dir.as_ref())?; - let package = metadata.root_package().unwrap(); - let tempdir = tempdir().map_err(CompileError::Tempdir)?; + let tempdir = tempdir().map_err(CommonError::tempdir)?; let linker_script_path = tempdir .path() .join("linker_script") .to_string_lossy() .to_string(); if let Some(linker_script) = &self.linker_script { - fs::write(&linker_script_path, linker_script.as_bytes()) - .map_err(CompileError::CreateLinkerScript)?; + fs::write(&linker_script_path, linker_script.as_bytes()).map_err(|err| { + CommonError::write_file("linker_script", &linker_script_path, err) + })?; } let encoded_rustflags = iter::empty() @@ -110,14 +114,15 @@ impl CargoBuildCmd { .chain(["--target".into(), target.as_ref().into()]) .chain(["--manifest-path".into(), package.manifest_path.to_string()]); - let status = Command::new("cargo") + let mut cmd = Command::new("cargo"); + let status = cmd .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) .args(args) .status() - .map_err(CompileError::CargoBuild)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !status.success() { - return Err(CompileError::CargoBuildFailed(status)); + return Err(CommonError::command_exit_non_zero(&cmd, status, None)); } let elf_path = metadata @@ -125,28 +130,49 @@ impl CargoBuildCmd { .join(target.as_ref()) .join(&self.profile) .join(&package.name); - let elf = fs::read(elf_path).map_err(CompileError::ReadElf)?; + let elf = + fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?; Ok(elf) } } /// Returns `Metadata` of `manifest_dir` and guarantees the `root_package` can be resolved. -pub fn cargo_metadata(manifest_dir: impl AsRef) -> Result { - let manifest_path = manifest_dir.as_ref().join("Cargo.toml"); - let metadata = MetadataCommand::new() - .manifest_path(&manifest_path) - .exec() - .map_err(|err| CompileError::CargoMetadata { - err, - manifest_dir: manifest_dir.as_ref().to_path_buf(), - })?; +pub fn cargo_metadata(manifest_dir: impl AsRef) -> Result { + let manifest_dir = manifest_dir.as_ref().to_path_buf(); + let manifest_path = manifest_dir.join("Cargo.toml"); + let metadata = match MetadataCommand::new().manifest_path(&manifest_path).exec() { + Ok(metadata) => metadata, + Err(err) => return Err(CommonError::CargoMetadata { err, manifest_dir }), + }; if metadata.root_package().is_none() { - return Err(CompileError::RootPackageNotFound( - manifest_dir.as_ref().to_path_buf(), - )); + return Err(CommonError::CargoRootPackageNotFound { manifest_dir }); } Ok(metadata) } + +/// Returns the path to `rustc` executable of the given toolchain. +pub fn rustc_path(toolchain: &str) -> Result { + let mut cmd = Command::new("rustc"); + let output = cmd + .env("RUSTUP_TOOLCHAIN", toolchain) + .args(["--print", "sysroot"]) + .output() + .map_err(|err| CommonError::command(&cmd, err))?; + + if !output.status.success() { + return Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + )); + } + + Ok( + PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()) + .join("bin") + .join("rustc"), + ) +} diff --git a/crates/dockerized/dockerized/Cargo.toml b/crates/dockerized/dockerized/Cargo.toml index e8cb9e8..b07d6ff 100644 --- a/crates/dockerized/dockerized/Cargo.toml +++ b/crates/dockerized/dockerized/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true serde = { workspace = true, features = ["derive"] } tempfile.workspace = true thiserror.workspace = true diff --git a/crates/dockerized/dockerized/src/error.rs b/crates/dockerized/dockerized/src/error.rs index d36e865..95ae4e5 100644 --- a/crates/dockerized/dockerized/src/error.rs +++ b/crates/dockerized/dockerized/src/error.rs @@ -1,14 +1,7 @@ use ere_server::client::{TwirpErrorResponse, zkVMClientError}; -use ere_zkvm_interface::zkVMError; use std::{io, path::PathBuf}; use thiserror::Error; -impl From for zkVMError { - fn from(value: DockerizedError) -> Self { - zkVMError::Other(Box::new(value)) - } -} - impl From for DockerizedError { fn from(value: zkVMClientError) -> Self { match value { diff --git a/crates/dockerized/dockerized/src/lib.rs b/crates/dockerized/dockerized/src/lib.rs index 54aa7f5..5048656 100644 --- a/crates/dockerized/dockerized/src/lib.rs +++ b/crates/dockerized/dockerized/src/lib.rs @@ -70,7 +70,7 @@ use crate::{ use ere_server::client::{Url, zkVMClient}; use ere_zkvm_interface::{ Compiler, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + PublicValues, zkVM, }; use serde::{Deserialize, Serialize}; use std::{ @@ -463,13 +463,13 @@ impl EreDockerizedzkVM { zkvm: ErezkVM, program: SerializedProgram, resource: ProverResourceType, - ) -> Result { + ) -> Result { zkvm.build_docker_image(matches!(resource, ProverResourceType::Gpu))?; let server_container = zkvm.spawn_server(&program, &resource)?; let url = Url::parse(&format!("http://127.0.0.1:{}", zkvm.server_port())).unwrap(); - let client = block_on(zkVMClient::new(url)).map_err(zkVMError::other)?; + let client = block_on(zkVMClient::new(url))?; Ok(Self { zkvm, @@ -494,7 +494,7 @@ impl EreDockerizedzkVM { } impl zkVM for EreDockerizedzkVM { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let (public_values, report) = block_on(self.client.execute(input.to_vec())).map_err(DockerizedError::from)?; @@ -505,7 +505,7 @@ impl zkVM for EreDockerizedzkVM { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { let (public_values, proof, report) = block_on(self.client.prove(input.to_vec(), proof_kind)) .map_err(DockerizedError::from)?; @@ -513,7 +513,7 @@ impl zkVM for EreDockerizedzkVM { Ok((public_values, proof, report)) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let public_values = block_on(self.client.verify(proof)).map_err(DockerizedError::from)?; Ok(public_values) @@ -554,7 +554,7 @@ mod test { workspace_dir, }; use ere_test_utils::{host::*, program::basic::BasicProgramInput}; - use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM, zkVMError}; + use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM}; use std::sync::{Mutex, MutexGuard, OnceLock}; macro_rules! test_compile { @@ -603,16 +603,11 @@ mod test { // Invalid test cases for input in $invalid_test_cases { - let Err(zkVMError::Other(err)) = zkvm.execute(&input) else { - unreachable!(); - }; - assert!( - matches!( - err.downcast_ref::().unwrap(), - DockerizedError::zkVM(_) - ), - "Unexpected err: {err:?}" - ); + let err = zkvm.execute(&input).unwrap_err(); + assert!(matches!( + err.downcast::().unwrap(), + DockerizedError::zkVM(_) + ),); } drop(zkvm); @@ -631,17 +626,11 @@ mod test { // Invalid test cases for input in $invalid_test_cases { - let Err(zkVMError::Other(err)) = zkvm.prove(&input, ProofKind::default()) - else { - unreachable!(); - }; - assert!( - matches!( - err.downcast_ref::().unwrap(), - DockerizedError::zkVM(_) - ), - "Unexpected err: {err:?}" - ); + let err = zkvm.prove(&input, ProofKind::default()).unwrap_err(); + assert!(matches!( + err.downcast::().unwrap(), + DockerizedError::zkVM(_) + ),); } drop(zkvm); diff --git a/crates/dockerized/server/src/main.rs b/crates/dockerized/server/src/main.rs index 28f3487..ab7fb54 100644 --- a/crates/dockerized/server/src/main.rs +++ b/crates/dockerized/server/src/main.rs @@ -113,7 +113,7 @@ fn construct_zkvm(program: Vec, resource: ProverResourceType) -> Result(ere_airbender::EreAirbender::new(program, resource)); + let zkvm = ere_airbender::EreAirbender::new(program, resource); #[cfg(feature = "jolt")] let zkvm = ere_jolt::EreJolt::new(program, resource); @@ -122,22 +122,22 @@ fn construct_zkvm(program: Vec, resource: ProverResourceType) -> Result(ere_nexus::EreNexus::new(program, resource)); + let zkvm = ere_nexus::EreNexus::new(program, resource); #[cfg(feature = "openvm")] let zkvm = ere_openvm::EreOpenVM::new(program, resource); #[cfg(feature = "pico")] - let zkvm = Ok::<_, Error>(ere_pico::ErePico::new(program, resource)); + let zkvm = ere_pico::ErePico::new(program, resource); #[cfg(feature = "risc0")] let zkvm = ere_risc0::EreRisc0::new(program, resource); #[cfg(feature = "sp1")] - let zkvm = Ok::<_, Error>(ere_sp1::EreSP1::new(program, resource)); + let zkvm = ere_sp1::EreSP1::new(program, resource); #[cfg(feature = "ziren")] - let zkvm = Ok::<_, Error>(ere_ziren::EreZiren::new(program, resource)); + let zkvm = ere_ziren::EreZiren::new(program, resource); #[cfg(feature = "zisk")] let zkvm = ere_zisk::EreZisk::new(program, resource); diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index d9249fc..be32fea 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -12,8 +12,7 @@ sha2.workspace = true # Local dependencies ere-zkvm-interface = { workspace = true, optional = true } -ere-io-serde = { workspace = true } - +ere-io-serde.workspace = true [lints] workspace = true diff --git a/crates/zkvm-interface/Cargo.toml b/crates/zkvm-interface/Cargo.toml index f8cd481..9b3e3ea 100644 --- a/crates/zkvm-interface/Cargo.toml +++ b/crates/zkvm-interface/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true auto_impl.workspace = true indexmap = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } diff --git a/crates/zkvm-interface/src/error.rs b/crates/zkvm-interface/src/error.rs new file mode 100644 index 0000000..960ac2d --- /dev/null +++ b/crates/zkvm-interface/src/error.rs @@ -0,0 +1,148 @@ +use crate::ProofKind; +use std::{ + io, + path::Path, + process::{Command, ExitStatus, Output}, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CommonError { + #[error("{ctx}: {err}")] + Io { + ctx: String, + #[source] + err: io::Error, + }, + + #[error("Serialize {id} with `{lib}` failed: {err}")] + Serialize { + id: String, + lib: String, + #[source] + err: anyhow::Error, + }, + + #[error("Deserialize {id} with `{lib}` failed: {err}")] + Deserialize { + id: String, + lib: String, + #[source] + err: anyhow::Error, + }, + + #[error("Failed to run command `{cmd}`: {err}")] + Command { + cmd: String, + #[source] + err: io::Error, + }, + + #[error("Command `{cmd}` exit with {status}{stdout}{stderr}", + stdout = if stdout.is_empty() { String::new() } else { format!("\nstdout: {}", String::from_utf8_lossy(stdout)) }, + stderr = if stderr.is_empty() { String::new() } else { format!("\nstdout: {}", String::from_utf8_lossy(stderr)) })] + CommandExitNonZero { + cmd: String, + status: ExitStatus, + stdout: Vec, + stderr: Vec, + }, + + #[error("Unsupported proof kind {unsupported:?}, expect one of {supported:?}")] + UnsupportedProofKind { + unsupported: ProofKind, + supported: Vec, + }, +} + +impl CommonError { + pub fn io(ctx: impl AsRef, err: io::Error) -> Self { + let ctx = ctx.as_ref().to_string(); + Self::Io { ctx, err } + } + + pub fn tempdir(err: io::Error) -> Self { + Self::io("Failed to create temporary dir", err) + } + + pub fn file_not_found(id: impl AsRef, path: impl AsRef) -> Self { + let (id, path) = (id.as_ref(), path.as_ref().display()); + Self::io( + format!("Failed to find {id} at {path}"), + io::ErrorKind::NotFound.into(), + ) + } + + pub fn create_dir(id: impl AsRef, path: impl AsRef, err: io::Error) -> Self { + let (id, path) = (id.as_ref(), path.as_ref().display()); + Self::io(format!("Failed to create dir {id} at {path}"), err) + } + + pub fn read_file(id: impl AsRef, path: impl AsRef, err: io::Error) -> Self { + let (id, path) = (id.as_ref(), path.as_ref().display()); + Self::io(format!("Failed to write {id} to {path}"), err) + } + + pub fn write_file(id: impl AsRef, path: impl AsRef, err: io::Error) -> Self { + let (id, path) = (id.as_ref(), path.as_ref().display()); + Self::io(format!("Failed to read {id} from {path}"), err) + } + + pub fn serialize( + id: impl AsRef, + lib: impl AsRef, + err: impl Into, + ) -> Self { + let id = id.as_ref().to_string(); + let lib = lib.as_ref().to_string(); + let err = err.into(); + Self::Serialize { id, lib, err } + } + + pub fn deserialize( + id: impl AsRef, + lib: impl AsRef, + err: impl Into, + ) -> Self { + let id = id.as_ref().to_string(); + let lib = lib.as_ref().to_string(); + let err = err.into(); + Self::Deserialize { id, lib, err } + } + + pub fn command(cmd: &Command, err: io::Error) -> Self { + Self::Command { + cmd: format!("{cmd:?}"), + err, + } + } + + pub fn command_exit_non_zero( + cmd: &Command, + status: ExitStatus, + output: Option<&Output>, + ) -> Self { + Self::CommandExitNonZero { + cmd: format!("{cmd:?}"), + status, + stdout: output + .map(|output| &output.stdout) + .cloned() + .unwrap_or_default(), + stderr: output + .map(|output| &output.stderr) + .cloned() + .unwrap_or_default(), + } + } + + pub fn unsupported_proof_kind( + unsupported: ProofKind, + supported: impl IntoIterator, + ) -> Self { + Self::UnsupportedProofKind { + unsupported, + supported: supported.into_iter().collect(), + } + } +} diff --git a/crates/zkvm-interface/src/lib.rs b/crates/zkvm-interface/src/lib.rs index 5de77d8..4d3199e 100644 --- a/crates/zkvm-interface/src/lib.rs +++ b/crates/zkvm-interface/src/lib.rs @@ -4,7 +4,9 @@ use serde::{Deserialize, Serialize, de::DeserializeOwned}; use std::path::Path; use strum::{EnumDiscriminants, EnumIs, EnumTryAs, FromRepr}; -use thiserror::Error; + +mod error; +pub use error::CommonError; mod reports; pub use reports::{ProgramExecutionReport, ProgramProvingReport}; @@ -48,46 +50,6 @@ impl ProverResourceType { } } -/// An error that can occur during prove, execute or verification -/// of a zkVM. -/// -/// Note: We use a concrete error type here, so that downstream crates -/// can do patterns such as Vec -#[allow(non_camel_case_types)] -#[derive(Debug, Error)] -pub enum zkVMError { - /// Network-related errors - #[error("Network error: {0}")] - Network(String), - - /// Authentication error - #[error("Authentication failed: {0}")] - Authentication(String), - - /// Timeout error - #[error("Operation timed out after {0:?}")] - Timeout(std::time::Duration), - - /// Service unavailable - #[error("Prover service unavailable: {0}")] - ServiceUnavailable(String), - - /// Invalid response from network - #[error("Invalid response from prover network: {0}")] - InvalidResponse(String), - - // TODO: We can add more variants as time goes by. - // TODO: for now, we use this catch-all as a way to prototype faster - #[error(transparent)] - Other(#[from] Box), -} - -impl zkVMError { - pub fn other(error: impl Into>) -> Self { - Self::Other(error.into()) - } -} - /// Public values committed/revealed by guest program. /// /// Use [`zkVM::deserialize_from`] to deserialize object from the bytes. @@ -142,19 +104,19 @@ impl Proof { /// implementation will have their own construction function. pub trait zkVM { /// Executes the program with the given input. - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError>; + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)>; /// Creates a proof of the program execution with given input. fn prove( &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError>; + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)>; /// Verifies a proof of the program used to create this zkVM instance, then /// returns the public values extracted from the proof. #[must_use = "Public values must be used"] - fn verify(&self, proof: &Proof) -> Result; + fn verify(&self, proof: &Proof) -> anyhow::Result; /// Returns the name of the zkVM fn name(&self) -> &'static str; diff --git a/crates/zkvm/airbender/Cargo.toml b/crates/zkvm/airbender/Cargo.toml index 8383bc5..99a4941 100644 --- a/crates/zkvm/airbender/Cargo.toml +++ b/crates/zkvm/airbender/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true bincode = { workspace = true, features = ["alloc", "serde"] } serde_json.workspace = true tempfile.workspace = true diff --git a/crates/zkvm/airbender/src/client.rs b/crates/zkvm/airbender/src/client.rs index 0594c50..801664b 100644 --- a/crates/zkvm/airbender/src/client.rs +++ b/crates/zkvm/airbender/src/client.rs @@ -3,7 +3,7 @@ use airbender_execution_utils::{ Machine, ProgramProof, compute_chain_encoding, generate_params_for_binary, universal_circuit_verifier_vk, verify_recursion_log_23_layer, }; -use ere_zkvm_interface::PublicValues; +use ere_zkvm_interface::{CommonError, PublicValues}; use std::{array, fs, io::BufRead, iter, process::Command}; use tempfile::tempdir; @@ -43,17 +43,18 @@ impl AirbenderSdk { } pub fn execute(&self, input: &[u8]) -> Result<(PublicValues, u64), AirbenderError> { - let tempdir = tempdir().map_err(AirbenderError::TempDir)?; + let tempdir = tempdir().map_err(CommonError::tempdir)?; let bin_path = tempdir.path().join("guest.bin"); fs::write(&bin_path, &self.bin) - .map_err(|err| AirbenderError::write_file(err, "guest.bin", &bin_path))?; + .map_err(|err| CommonError::write_file("guest.bin", &bin_path, err))?; let input_path = tempdir.path().join("input.hex"); fs::write(&input_path, encode_input(input)) - .map_err(|err| AirbenderError::write_file(err, "input.hex", &input_path))?; + .map_err(|err| CommonError::write_file("input.hex", &input_path, err))?; - let output = Command::new("airbender-cli") + let mut cmd = Command::new("airbender-cli"); + let output = cmd .arg("run") .arg("--bin") .arg(&bin_path) @@ -61,13 +62,14 @@ impl AirbenderSdk { .arg(&input_path) .args(["--cycles", &u64::MAX.to_string()]) .output() - .map_err(AirbenderError::AirbenderRun)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { - return Err(AirbenderError::AirbenderRunFailed { - status: output.status, - stderr: String::from_utf8_lossy(&output.stderr).into_owned(), - }); + Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))? } // Parse public values 8 u32 words (32 bytes) from stdout in format of: @@ -110,22 +112,23 @@ impl AirbenderSdk { } pub fn prove(&self, input: &[u8]) -> Result<(PublicValues, ProgramProof), AirbenderError> { - let tempdir = tempdir().map_err(AirbenderError::TempDir)?; + let tempdir = tempdir().map_err(CommonError::tempdir)?; let bin_path = tempdir.path().join("guest.bin"); fs::write(&bin_path, &self.bin) - .map_err(|err| AirbenderError::write_file(err, "guest.bin", &bin_path))?; + .map_err(|err| CommonError::write_file("guest.bin", &bin_path, err))?; let input_path = tempdir.path().join("input.hex"); fs::write(&input_path, encode_input(input)) - .map_err(|err| AirbenderError::write_file(err, "input.hex", &input_path))?; + .map_err(|err| CommonError::write_file("input.hex", &input_path, err))?; let output_dir = tempdir.path().join("output"); fs::create_dir_all(&output_dir) - .map_err(|err| AirbenderError::create_dir(err, "output", &output_dir))?; + .map_err(|err| CommonError::create_dir("output", &output_dir, err))?; // Prove guest program + 1st recursion layer (tree of recursive proofs until root). - let output = Command::new("airbender-cli") + let mut cmd = Command::new("airbender-cli"); + let output = cmd .arg("prove") .arg("--bin") .arg(&bin_path) @@ -137,22 +140,24 @@ impl AirbenderSdk { .args(["--cycles", &u64::MAX.to_string()]) .args(self.gpu.then_some("--gpu")) .output() - .map_err(AirbenderError::AirbenderProve)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { - return Err(AirbenderError::AirbenderProveFailed { - status: output.status, - stderr: String::from_utf8_lossy(&output.stderr).into_owned(), - }); + Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))? } let proof_path = output_dir.join("recursion_program_proof.json"); if !proof_path.exists() { - return Err(AirbenderError::RecursionProofNotFound { path: proof_path }); + Err(CommonError::file_not_found("proof", &proof_path))? } // Prove 2nd recursion layer (wrapping root of 1st recursion layer) - let output = Command::new("airbender-cli") + let mut cmd = Command::new("airbender-cli"); + let output = cmd .arg("prove-final") .arg("--input-file") .arg(&proof_path) @@ -160,26 +165,22 @@ impl AirbenderSdk { .arg(&output_dir) .args(self.gpu.then_some("--gpu")) .output() - .map_err(AirbenderError::AirbenderProveFinal)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { - return Err(AirbenderError::AirbenderProveFinalFailed { - status: output.status, - stderr: String::from_utf8_lossy(&output.stderr).into_owned(), - }); + Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))? } let proof_path = output_dir.join("final_program_proof.json"); - if !proof_path.exists() { - return Err(AirbenderError::FinalProofNotFound { path: proof_path }); - } + let proof_bytes = fs::read(&proof_path) + .map_err(|err| CommonError::read_file("proof", &proof_path, err))?; - let proof_bytes = fs::read(&proof_path).map_err(|err| { - AirbenderError::read_file(err, "final_program_proof.json", &proof_path) - })?; - - let proof: ProgramProof = - serde_json::from_slice(&proof_bytes).map_err(AirbenderError::JsonDeserialize)?; + let proof: ProgramProof = serde_json::from_slice(&proof_bytes) + .map_err(|err| CommonError::deserialize("proof", "serde_json", err))?; let (public_values, vk_hash_chain) = extract_public_values_and_vk_hash_chain(&proof)?; diff --git a/crates/zkvm/airbender/src/compiler/rust_rv32ima.rs b/crates/zkvm/airbender/src/compiler/rust_rv32ima.rs index 0c3d7ac..d78c778 100644 --- a/crates/zkvm/airbender/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/airbender/src/compiler/rust_rv32ima.rs @@ -1,5 +1,5 @@ -use crate::{compiler::AirbenderProgram, error::AirbenderError}; -use ere_compile_utils::CargoBuildCmd; +use crate::{compiler::AirbenderProgram, error::CompileError}; +use ere_compile_utils::{CargoBuildCmd, CommonError}; use ere_zkvm_interface::Compiler; use std::{ env, @@ -35,7 +35,7 @@ const LINKER_SCRIPT: &str = concat!( pub struct RustRv32ima; impl Compiler for RustRv32ima { - type Error = AirbenderError; + type Error = CompileError; type Program = AirbenderProgram; @@ -52,31 +52,33 @@ impl Compiler for RustRv32ima { } } -fn objcopy_binary(elf: &[u8]) -> Result, AirbenderError> { - let mut child = Command::new("rust-objcopy") +fn objcopy_binary(elf: &[u8]) -> Result, CompileError> { + let mut cmd = Command::new("rust-objcopy"); + let mut child = cmd .args(["-O", "binary", "-", "-"]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .map_err(AirbenderError::RustObjcopy)?; + .map_err(|err| CommonError::command(&cmd, err))?; child .stdin .as_mut() .unwrap() .write_all(elf) - .map_err(AirbenderError::RustObjcopyStdin)?; + .map_err(|err| CommonError::command(&cmd, err))?; let output = child .wait_with_output() - .map_err(AirbenderError::RustObjcopy)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { - return Err(AirbenderError::RustObjcopyFailed { - status: output.status, - stderr: String::from_utf8_lossy(&output.stderr).into_owned(), - }); + Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))? } Ok(output.stdout) diff --git a/crates/zkvm/airbender/src/error.rs b/crates/zkvm/airbender/src/error.rs index 55a87ee..6e9a0d0 100644 --- a/crates/zkvm/airbender/src/error.rs +++ b/crates/zkvm/airbender/src/error.rs @@ -1,73 +1,32 @@ use crate::client::VkHashChain; -use ere_zkvm_interface::zkVMError; -use std::{ - io, - path::{Path, PathBuf}, - process::ExitStatus, -}; +use ere_compile_utils::CommonError; use thiserror::Error; -impl From for zkVMError { - fn from(value: AirbenderError) -> Self { - zkVMError::Other(Box::new(value)) - } +#[derive(Debug, Error)] +pub enum CompileError { + #[error(transparent)] + CommonError(#[from] CommonError), } #[derive(Debug, Error)] pub enum AirbenderError { - // Compilation #[error(transparent)] - CompileError(#[from] ere_compile_utils::CompileError), - #[error("Failed to execute `rust-objcopy`: {0}")] - RustObjcopy(#[source] io::Error), - #[error("`rust-objcopy` failed with status: {status}\nstderr: {stderr}")] - RustObjcopyFailed { status: ExitStatus, stderr: String }, - #[error("Failed to write ELF to `rust-objcopy` stdin: {0}")] - RustObjcopyStdin(#[source] io::Error), - - // IO and file system - #[error("IO failure: {0}")] - Io(#[from] io::Error), - #[error("IO failure in temporary directory: {0}")] - TempDir(io::Error), - - // Serialization - #[error("Bincode encode failed: {0}")] - BincodeEncode(#[from] bincode::error::EncodeError), - #[error("Bincode decode failed: {0}")] - BincodeDecode(#[from] bincode::error::DecodeError), - #[error("JSON deserialization failed: {0}")] - JsonDeserialize(#[from] serde_json::Error), + CommonError(#[from] ere_zkvm_interface::CommonError), // Execution - #[error("Failed to execute `airbender-cli run`: {0}")] - AirbenderRun(#[source] io::Error), - #[error("`airbender-cli run` failed with status: {status}\nstderr: {stderr}")] - AirbenderRunFailed { status: ExitStatus, stderr: String }, #[error("Failed to parse public value from stdout: {0}")] ParsePublicValue(String), + #[error("Failed to parse cycles from stdout: {0}")] ParseCycles(String), - // Proving - #[error("Failed to execute `airbender-cli prove`: {0}")] - AirbenderProve(#[source] io::Error), - #[error("`airbender-cli prove` failed with status: {status}\nstderr: {stderr}")] - AirbenderProveFailed { status: ExitStatus, stderr: String }, - #[error("Failed to execute `airbender-cli prove-final`: {0}")] - AirbenderProveFinal(#[source] io::Error), - #[error("`airbender-cli prove-final` failed with status: {status}\nstderr: {stderr}")] - AirbenderProveFinalFailed { status: ExitStatus, stderr: String }, - #[error("Recursion proof not found at {path}")] - RecursionProofNotFound { path: PathBuf }, - #[error("Final proof not found at {path}")] - FinalProofNotFound { path: PathBuf }, - // Verification #[error("Proof verification failed")] ProofVerificationFailed, + #[error("Invalid final register count, expected 32 but got {0}")] InvalidRegisterCount(usize), + #[error( "Unexpected verification key hash chain - preprocessed: {preprocessed:?}, proved: {proved:?}" )] @@ -76,24 +35,3 @@ pub enum AirbenderError { proved: VkHashChain, }, } - -impl AirbenderError { - pub fn io(err: io::Error, context: impl Into) -> Self { - Self::Io(io::Error::other(format!("{}: {}", context.into(), err))) - } - - pub fn create_dir(err: io::Error, id: &str, path: impl AsRef) -> Self { - let ctx = format!("Failed to create dir {id} at {}", path.as_ref().display()); - Self::io(err, ctx) - } - - pub fn write_file(err: io::Error, id: &str, path: impl AsRef) -> Self { - let ctx = format!("Failed to write {id} to {}", path.as_ref().display()); - Self::io(err, ctx) - } - - pub fn read_file(err: io::Error, id: &str, path: impl AsRef) -> Self { - let ctx = format!("Failed to read {id} from {}", path.as_ref().display()); - Self::io(err, ctx) - } -} diff --git a/crates/zkvm/airbender/src/lib.rs b/crates/zkvm/airbender/src/lib.rs index 968a7b6..14b7ab5 100644 --- a/crates/zkvm/airbender/src/lib.rs +++ b/crates/zkvm/airbender/src/lib.rs @@ -2,9 +2,10 @@ use crate::{client::AirbenderSdk, compiler::AirbenderProgram, error::AirbenderError}; use airbender_execution_utils::ProgramProof; +use anyhow::bail; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use std::time::Instant; @@ -19,15 +20,18 @@ pub struct EreAirbender { } impl EreAirbender { - pub fn new(bin: AirbenderProgram, resource: ProverResourceType) -> Self { + pub fn new( + bin: AirbenderProgram, + resource: ProverResourceType, + ) -> Result { let gpu = matches!(resource, ProverResourceType::Gpu); let sdk = AirbenderSdk::new(&bin, gpu); - Self { sdk } + Ok(Self { sdk }) } } impl zkVM for EreAirbender { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let start = Instant::now(); let (public_values, cycles) = self.sdk.execute(input)?; let execution_duration = start.elapsed(); @@ -46,17 +50,19 @@ impl zkVM for EreAirbender { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if proof_kind != ProofKind::Compressed { - panic!("Only Compressed proof kind is supported."); + bail!(CommonError::unsupported_proof_kind( + proof_kind, + [ProofKind::Compressed] + )) } - let start = Instant::now(); let (public_values, proof) = self.sdk.prove(input)?; let proving_time = start.elapsed(); let proof_bytes = bincode::serde::encode_to_vec(&proof, bincode::config::legacy()) - .map_err(AirbenderError::BincodeEncode)?; + .map_err(|err| CommonError::serialize("proof", "bincode", err))?; Ok(( public_values, @@ -65,14 +71,17 @@ impl zkVM for EreAirbender { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - return Err(zkVMError::other("Only Compressed proof kind is supported.")); + bail!(CommonError::unsupported_proof_kind( + proof.kind(), + [ProofKind::Compressed] + )) }; let (proof, _): (ProgramProof, _) = bincode::serde::decode_from_slice(proof, bincode::config::legacy()) - .map_err(AirbenderError::BincodeDecode)?; + .map_err(|err| CommonError::deserialize("proof", "bincode", err))?; let public_values = self.sdk.verify(&proof)?; @@ -115,7 +124,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreAirbender::new(program, ProverResourceType::Cpu); + let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid().into_output_sha256(); run_zkvm_execute(&zkvm, &test_case); @@ -124,7 +133,7 @@ mod tests { #[test] fn test_execute_invalid_input() { let program = basic_program(); - let zkvm = EreAirbender::new(program, ProverResourceType::Cpu); + let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.execute(&input).unwrap_err(); @@ -134,7 +143,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreAirbender::new(program, ProverResourceType::Cpu); + let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid().into_output_sha256(); run_zkvm_execute(&zkvm, &test_case); @@ -144,7 +153,7 @@ mod tests { #[test] fn test_prove_invalid_input() { let program = basic_program(); - let zkvm = EreAirbender::new(program, ProverResourceType::Cpu); + let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.prove(&input, ProofKind::default()).unwrap_err(); diff --git a/crates/zkvm/jolt/Cargo.toml b/crates/zkvm/jolt/Cargo.toml index a148299..964ac65 100644 --- a/crates/zkvm/jolt/Cargo.toml +++ b/crates/zkvm/jolt/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true tempfile.workspace = true thiserror.workspace = true diff --git a/crates/zkvm/jolt/src/compiler/rust_rv32ima.rs b/crates/zkvm/jolt/src/compiler/rust_rv32ima.rs index eeacc2f..6f899bc 100644 --- a/crates/zkvm/jolt/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/jolt/src/compiler/rust_rv32ima.rs @@ -1,7 +1,4 @@ -use crate::{ - compiler::JoltProgram, - error::{CompileError, JoltError}, -}; +use crate::{compiler::JoltProgram, error::CompileError}; use ere_compile_utils::CargoBuildCmd; use ere_zkvm_interface::Compiler; use std::{env, path::Path}; @@ -39,7 +36,7 @@ fn make_linker_script() -> String { pub struct RustRv32ima; impl Compiler for RustRv32ima { - type Error = JoltError; + type Error = CompileError; type Program = JoltProgram; @@ -50,8 +47,7 @@ impl Compiler for RustRv32ima { .toolchain(toolchain) .build_options(CARGO_BUILD_OPTIONS) .rustflags(RUSTFLAGS) - .exec(guest_directory, TARGET_TRIPLE) - .map_err(CompileError::CompileUtilError)?; + .exec(guest_directory, TARGET_TRIPLE)?; Ok(elf) } } diff --git a/crates/zkvm/jolt/src/compiler/rust_rv32ima_customized.rs b/crates/zkvm/jolt/src/compiler/rust_rv32ima_customized.rs index 9d77a4a..4dcf1c8 100644 --- a/crates/zkvm/jolt/src/compiler/rust_rv32ima_customized.rs +++ b/crates/zkvm/jolt/src/compiler/rust_rv32ima_customized.rs @@ -1,8 +1,5 @@ -use crate::{ - compiler::JoltProgram, - error::{CompileError, JoltError}, -}; -use ere_compile_utils::cargo_metadata; +use crate::{compiler::JoltProgram, error::CompileError}; +use ere_compile_utils::{CommonError, cargo_metadata}; use ere_zkvm_interface::Compiler; use jolt::host::DEFAULT_TARGET_DIR; use std::{env::set_current_dir, fs, path::Path}; @@ -12,18 +9,18 @@ use std::{env::set_current_dir, fs, path::Path}; pub struct RustRv32imaCustomized; impl Compiler for RustRv32imaCustomized { - type Error = JoltError; + type Error = CompileError; type Program = JoltProgram; fn compile(&self, guest_directory: &Path) -> Result { // Change current directory for `Program::build` to build guest program. - set_current_dir(guest_directory).map_err(|source| CompileError::SetCurrentDirFailed { - source, + set_current_dir(guest_directory).map_err(|err| CompileError::SetCurrentDirFailed { + err, path: guest_directory.to_path_buf(), })?; - let metadata = cargo_metadata(guest_directory).map_err(CompileError::CompileUtilError)?; + let metadata = cargo_metadata(guest_directory)?; let package_name = &metadata.root_package().unwrap().name; // Note that if this fails, it will panic, hence we need to catch it. @@ -35,10 +32,8 @@ impl Compiler for RustRv32imaCustomized { }) .map_err(|_| CompileError::BuildFailed)?; - let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed { - source, - path: elf_path.to_path_buf(), - })?; + let elf = + fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?; Ok(elf) } diff --git a/crates/zkvm/jolt/src/error.rs b/crates/zkvm/jolt/src/error.rs index 3e27e0e..f2341d0 100644 --- a/crates/zkvm/jolt/src/error.rs +++ b/crates/zkvm/jolt/src/error.rs @@ -1,55 +1,30 @@ -use ark_serialize::SerializationError; -use ere_zkvm_interface::zkVMError; +use ere_compile_utils::CommonError; use jolt::jolt_core::utils::errors::ProofVerifyError; use std::{io, path::PathBuf}; use thiserror::Error; -impl From for zkVMError { - fn from(value: JoltError) -> Self { - zkVMError::Other(Box::new(value)) - } +#[derive(Debug, Error)] +pub enum CompileError { + #[error(transparent)] + CommonError(#[from] CommonError), + + #[error("Failed to set current directory to {path}: {err}")] + SetCurrentDirFailed { + path: PathBuf, + #[source] + err: io::Error, + }, + + #[error("Failed to build guest")] + BuildFailed, } #[derive(Debug, Error)] pub enum JoltError { #[error(transparent)] - Compile(#[from] CompileError), + CommonError(#[from] ere_zkvm_interface::CommonError), - #[error(transparent)] - Execute(#[from] ExecuteError), - - #[error(transparent)] - Prove(#[from] ProveError), - - #[error(transparent)] - Verify(#[from] VerifyError), -} - -#[derive(Debug, Error)] -pub enum CompileError { - #[error("Failed to build guest")] - BuildFailed, - #[error("Failed to read elf at {path}: {source}")] - ReadElfFailed { source: io::Error, path: PathBuf }, - #[error("Failed to set current directory to {path}: {source}")] - SetCurrentDirFailed { source: io::Error, path: PathBuf }, - #[error(transparent)] - CompileUtilError(#[from] ere_compile_utils::CompileError), -} - -#[derive(Debug, Error)] -pub enum ExecuteError {} - -#[derive(Debug, Error)] -pub enum ProveError { - #[error("Serialization failed")] - Serialization(#[from] SerializationError), -} - -#[derive(Debug, Error)] -pub enum VerifyError { - #[error("Deserialization failed")] - Serialization(#[from] SerializationError), + // Verify #[error("Failed to verify proof: {0}")] VerifyProofFailed(#[from] ProofVerifyError), } diff --git a/crates/zkvm/jolt/src/jolt_methods.rs b/crates/zkvm/jolt/src/jolt_methods.rs index 48177fb..68bbe03 100644 --- a/crates/zkvm/jolt/src/jolt_methods.rs +++ b/crates/zkvm/jolt/src/jolt_methods.rs @@ -1,4 +1,4 @@ -use crate::{EreJoltProof, error::VerifyError}; +use crate::{EreJoltProof, error::JoltError}; use common::constants::{DEFAULT_MAX_BYTECODE_SIZE, DEFAULT_MAX_TRACE_LENGTH, DEFAULT_MEMORY_SIZE}; use jolt::{ Jolt, JoltHyperKZGProof, JoltProverPreprocessing, JoltVerifierPreprocessing, MemoryConfig, @@ -67,7 +67,7 @@ pub fn prove_generic( pub fn verify_generic( proof: EreJoltProof, preprocessing: JoltVerifierPreprocessing<4, jolt::F, jolt::PCS, jolt::ProofTranscript>, -) -> Result<(), VerifyError> { +) -> Result<(), JoltError> { let mut io_device = JoltDevice::new(&MemoryConfig { max_input_size: preprocessing.memory_layout.max_input_size, max_output_size: preprocessing.memory_layout.max_output_size, diff --git a/crates/zkvm/jolt/src/lib.rs b/crates/zkvm/jolt/src/lib.rs index 1018645..2e49d8e 100644 --- a/crates/zkvm/jolt/src/lib.rs +++ b/crates/zkvm/jolt/src/lib.rs @@ -2,13 +2,14 @@ use crate::{ compiler::JoltProgram, - error::{JoltError, ProveError, VerifyError}, + error::JoltError, jolt_methods::{preprocess_prover, preprocess_verifier, prove_generic, verify_generic}, }; +use anyhow::bail; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use jolt::{JoltHyperKZGProof, JoltProverPreprocessing, JoltVerifierPreprocessing}; use std::{env, fs, io::Cursor}; @@ -34,7 +35,10 @@ pub struct EreJolt { } impl EreJolt { - pub fn new(elf: JoltProgram, _resource: ProverResourceType) -> Result { + pub fn new(elf: JoltProgram, resource: ProverResourceType) -> Result { + if !matches!(resource, ProverResourceType::Cpu) { + panic!("Network or GPU proving not yet implemented for Miden. Use CPU resource type."); + } let (_tempdir, program) = program(&elf)?; let prover_preprocessing = preprocess_prover(&program); let verifier_preprocessing = preprocess_verifier(&program); @@ -42,13 +46,13 @@ impl EreJolt { elf, prover_preprocessing, verifier_preprocessing, - _resource, + _resource: resource, }) } } impl zkVM for EreJolt { - fn execute(&self, _input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, _input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let (_tempdir, program) = program(&self.elf)?; // TODO: Check how to pass private input to jolt, issue for tracking: @@ -66,9 +70,12 @@ impl zkVM for EreJolt { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if proof_kind != ProofKind::Compressed { - panic!("Only Compressed proof kind is supported."); + bail!(CommonError::unsupported_proof_kind( + proof_kind, + [ProofKind::Compressed] + )) } let (_tempdir, program) = program(&self.elf)?; @@ -80,7 +87,7 @@ impl zkVM for EreJolt { let mut proof_bytes = Vec::new(); proof .serialize_compressed(&mut proof_bytes) - .map_err(|err| JoltError::Prove(ProveError::Serialization(err)))?; + .map_err(|err| CommonError::serialize("proof", "jolt", err))?; // TODO: Public values let public_values = Vec::new(); @@ -92,15 +99,18 @@ impl zkVM for EreJolt { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - return Err(zkVMError::other("Only Compressed proof kind is supported.")); + bail!(CommonError::unsupported_proof_kind( + proof.kind(), + [ProofKind::Compressed] + )) }; let proof = EreJoltProof::deserialize_compressed(&mut Cursor::new(proof)) - .map_err(|err| JoltError::Verify(VerifyError::Serialization(err)))?; + .map_err(|err| CommonError::deserialize("proof", "jolt", err))?; - verify_generic(proof, self.verifier_preprocessing.clone()).map_err(JoltError::Verify)?; + verify_generic(proof, self.verifier_preprocessing.clone())?; // TODO: Public values let public_values = Vec::new(); @@ -120,10 +130,10 @@ impl zkVM for EreJolt { /// Create `jolt::host::Program` by storing the compiled `elf` to a temporary /// file, and set the elf path for `program`, so methods like `decode`, `trace` /// and `trace_analyze` that depend on elf path will work. -pub fn program(elf: &[u8]) -> Result<(TempDir, jolt::host::Program), zkVMError> { - let tempdir = TempDir::new().map_err(zkVMError::other)?; +pub fn program(elf: &[u8]) -> Result<(TempDir, jolt::host::Program), JoltError> { + let tempdir = TempDir::new().map_err(CommonError::tempdir)?; let elf_path = tempdir.path().join("guest.elf"); - fs::write(&elf_path, elf).map_err(zkVMError::other)?; + fs::write(&elf_path, elf).map_err(|err| CommonError::write_file("elf", &elf_path, err))?; // Set a dummy package name because we don't need to compile anymore. let mut program = jolt::host::Program::new(""); program.elf = Some(elf_path); diff --git a/crates/zkvm/miden/Cargo.toml b/crates/zkvm/miden/Cargo.toml index 58b94ca..39f376f 100644 --- a/crates/zkvm/miden/Cargo.toml +++ b/crates/zkvm/miden/Cargo.toml @@ -6,7 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] -bincode = { workspace = true, features = ["alloc", "serde"] } +anyhow.workspace = true serde = { workspace = true, features = ["derive"] } thiserror.workspace = true diff --git a/crates/zkvm/miden/src/compiler/miden_asm.rs b/crates/zkvm/miden/src/compiler/miden_asm.rs index 666db29..f8a07b8 100644 --- a/crates/zkvm/miden/src/compiler/miden_asm.rs +++ b/crates/zkvm/miden/src/compiler/miden_asm.rs @@ -1,17 +1,14 @@ -use crate::{ - compiler::MidenProgram, - error::{CompileError, MidenError}, -}; +use crate::{compiler::MidenProgram, error::CompileError}; use ere_zkvm_interface::Compiler; use miden_assembly::Assembler; use miden_stdlib::StdLibrary; -use std::{fs, path::Path}; +use std::{env, fs, path::Path}; /// Compiler for Miden assembly guest program. pub struct MidenAsm; impl Compiler for MidenAsm { - type Error = MidenError; + type Error = CompileError; type Program = MidenProgram; fn compile(&self, guest_directory: &Path) -> Result { @@ -21,29 +18,29 @@ impl Compiler for MidenAsm { .ok_or(CompileError::InvalidProgramPath)?; let entrypoint = format!("{dir_name}.masm"); - let main_path = guest_directory.join(&entrypoint); - if !main_path.exists() { + let entrypoint_path = guest_directory.join(&entrypoint); + if !entrypoint_path.exists() { return Err(CompileError::MissingEntrypoint { program_dir: guest_directory.display().to_string(), entrypoint, - } - .into()); + }); } + let source = + fs::read_to_string(&entrypoint_path).map_err(|err| CompileError::ReadEntrypoint { + entrypoint_path, + err, + })?; // Compile using Miden assembler - let mut assembler = Assembler::default().with_debug_mode(true); + let mut assembler = + Assembler::default().with_debug_mode(env::var_os("MIDEN_DEBUG").is_some()); assembler .link_dynamic_library(StdLibrary::default()) - .map_err(|e| CompileError::LoadStdLibrary(e.to_string()))?; - - let source = fs::read_to_string(&main_path).map_err(|e| CompileError::ReadSource { - path: main_path.clone(), - source: e, - })?; + .map_err(CompileError::LoadStdLibrary)?; let program = assembler .assemble_program(&source) - .map_err(|e| CompileError::AssemblyCompilation(e.to_string()))?; + .map_err(CompileError::AssemblyCompilation)?; Ok(MidenProgram(program)) } diff --git a/crates/zkvm/miden/src/error.rs b/crates/zkvm/miden/src/error.rs index d762fef..4b2b8b4 100644 --- a/crates/zkvm/miden/src/error.rs +++ b/crates/zkvm/miden/src/error.rs @@ -1,79 +1,48 @@ -use ere_zkvm_interface::zkVMError; -use miden_core::utils::DeserializationError; +use miden_assembly::Report; use miden_processor::ExecutionError; use miden_verifier::VerificationError; use std::path::PathBuf; use thiserror::Error; -impl From for zkVMError { - fn from(value: MidenError) -> Self { - zkVMError::Other(Box::new(value)) - } +#[derive(Debug, Error)] +pub enum CompileError { + #[error("Invalid program directory name")] + InvalidProgramPath, + + #[error("Entrypoint `{entrypoint}` not found in {program_dir}")] + MissingEntrypoint { + program_dir: String, + entrypoint: String, + }, + + #[error("Failed to read assembly source at {entrypoint_path}: {err}")] + ReadEntrypoint { + entrypoint_path: PathBuf, + #[source] + err: std::io::Error, + }, + + #[error("Failed to load Miden standard library: {0}")] + LoadStdLibrary(Report), + + #[error("Miden assembly compilation failed: {0}")] + AssemblyCompilation(Report), } #[derive(Debug, Error)] pub enum MidenError { #[error(transparent)] - Compile(#[from] CompileError), - #[error(transparent)] - Execute(#[from] ExecuteError), - #[error(transparent)] - Prove(#[from] ProveError), - #[error(transparent)] - Verify(#[from] VerifyError), -} + CommonError(#[from] ere_zkvm_interface::CommonError), -#[derive(Debug, Error)] -pub enum CompileError { - #[error("Invalid program directory name")] - InvalidProgramPath, - #[error("Entrypoint '{entrypoint}' not found in {program_dir}")] - MissingEntrypoint { - program_dir: String, - entrypoint: String, - }, - #[error("Failed to read assembly source at {path}")] - ReadSource { - path: PathBuf, - #[source] - source: std::io::Error, - }, - #[error("Miden assembly compilation failed: {0}")] - AssemblyCompilation(String), - #[error("Failed to load Miden standard library: {0}")] - LoadStdLibrary(String), -} - -#[derive(Debug, Error)] -pub enum ExecuteError { + // Execute #[error("Miden execution failed")] - Execution(#[from] ExecutionError), - #[error("Invalid input format: {0}")] - InvalidInput(String), - #[error("Serialization failed")] - Serialization(#[from] bincode::error::EncodeError), - #[error("Failed to deserialize Miden program")] - ProgramDeserialization(#[from] DeserializationError), -} + Execute(#[from] ExecutionError), -#[derive(Debug, Error)] -pub enum ProveError { - #[error("Miden proving failed")] - Proving(#[from] ExecutionError), - #[error("Invalid input format: {0}")] - InvalidInput(String), - #[error("Output serialization failed")] - OutputSerialization(#[from] bincode::error::EncodeError), -} + // Prove + #[error("Miden proving failed: {0}")] + Prove(#[source] ExecutionError), -#[derive(Debug, Error)] -pub enum VerifyError { + // Verify #[error("Miden verification failed")] - Verification(#[from] VerificationError), - #[error("Proof or associated data deserialization failed")] - MidenDeserialization(#[from] DeserializationError), - #[error("Proof bundle deserialization failed")] - BundleDeserialization(#[from] bincode::error::DecodeError), - #[error("Output serialization failed")] - OutputSerialization(#[from] bincode::error::EncodeError), + Verify(#[from] VerificationError), } diff --git a/crates/zkvm/miden/src/lib.rs b/crates/zkvm/miden/src/lib.rs index f9adc8d..c781001 100644 --- a/crates/zkvm/miden/src/lib.rs +++ b/crates/zkvm/miden/src/lib.rs @@ -1,10 +1,10 @@ -use crate::{ - compiler::MidenProgram, - error::{ExecuteError, MidenError, ProveError, VerifyError}, -}; +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use crate::{compiler::MidenProgram, error::MidenError}; +use anyhow::bail; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use miden_core::{ Program, @@ -16,7 +16,6 @@ use miden_processor::{ use miden_prover::{AdviceInputs, ExecutionProof, ProvingOptions, prove as miden_prove}; use miden_stdlib::StdLibrary; use miden_verifier::verify as miden_verify; -use serde::{Deserialize, Serialize}; use std::{env, time::Instant}; include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); @@ -26,13 +25,6 @@ pub mod error; pub use miden_core::{Felt, FieldElement}; -#[derive(Serialize, Deserialize)] -struct MidenProofBundle { - stack_inputs: Vec, - stack_outputs: Vec, - proof: Vec, -} - /// [`zkVM`] implementation for Miden. /// /// Miden VM takes list of field elements as input instead of bytes, so in @@ -43,18 +35,24 @@ struct MidenProofBundle { /// [`felts_to_bytes`] as well. pub struct EreMiden { program: Program, + _resource: ProverResourceType, } impl EreMiden { - pub fn new(program: MidenProgram, _resource: ProverResourceType) -> Result { - Ok(Self { program: program.0 }) + pub fn new(program: MidenProgram, resource: ProverResourceType) -> Result { + if !matches!(resource, ProverResourceType::Cpu) { + panic!("Network or GPU proving not yet implemented for Miden. Use CPU resource type."); + } + Ok(Self { + program: program.0, + _resource: resource, + }) } fn setup_host() -> Result { let mut host = DefaultHost::default(); host.load_library(&StdLibrary::default()) - .map_err(ExecuteError::Execution) .map_err(MidenError::Execute)?; Ok(host) @@ -62,12 +60,9 @@ impl EreMiden { } impl zkVM for EreMiden { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let stack_inputs = StackInputs::default(); - let advice_inputs = AdviceInputs::default().with_stack( - bytes_to_felts(input) - .map_err(|err| MidenError::Execute(ExecuteError::InvalidInput(err)))?, - ); + let advice_inputs = AdviceInputs::default().with_stack(bytes_to_felts(input)?); let mut host = Self::setup_host()?; let start = Instant::now(); @@ -78,7 +73,7 @@ impl zkVM for EreMiden { &mut host, ExecutionOptions::default(), ) - .map_err(|e| MidenError::Execute(e.into()))?; + .map_err(MidenError::Execute)?; let public_values = felts_to_bytes(trace.stack_outputs().as_slice()); @@ -95,20 +90,21 @@ impl zkVM for EreMiden { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if proof_kind != ProofKind::Compressed { - panic!("Only Compressed proof kind is supported."); + bail!(CommonError::unsupported_proof_kind( + proof_kind, + [ProofKind::Compressed] + )) } let stack_inputs = StackInputs::default(); - let advice_inputs = AdviceInputs::default().with_stack( - bytes_to_felts(input) - .map_err(|err| MidenError::Prove(ProveError::InvalidInput(err)))?, - ); + let advice_inputs = AdviceInputs::default().with_stack(bytes_to_felts(input)?); let mut host = Self::setup_host()?; let start = Instant::now(); - let proving_options = ProvingOptions::with_96_bit_security(env::var("MIDEN_DEBUG").is_ok()); + let proving_options = + ProvingOptions::with_96_bit_security(env::var_os("MIDEN_DEBUG").is_some()); let (stack_outputs, proof) = miden_prove( &self.program, @@ -117,18 +113,10 @@ impl zkVM for EreMiden { &mut host, proving_options, ) - .map_err(|e| MidenError::Prove(e.into()))?; + .map_err(MidenError::Prove)?; let public_values = felts_to_bytes(stack_outputs.as_slice()); - - let bundle = MidenProofBundle { - stack_inputs: stack_inputs.to_bytes(), - stack_outputs: stack_outputs.to_bytes(), - proof: proof.to_bytes(), - }; - - let proof_bytes = bincode::serde::encode_to_vec(&bundle, bincode::config::legacy()) - .map_err(|e| MidenError::Prove(e.into()))?; + let proof_bytes = (stack_outputs, proof).to_bytes(); Ok(( public_values, @@ -137,31 +125,23 @@ impl zkVM for EreMiden { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - return Err(zkVMError::other("Only Compressed proof kind is supported.")); + bail!(CommonError::unsupported_proof_kind( + proof.kind(), + [ProofKind::Compressed] + )) }; - let (bundle, _): (MidenProofBundle, _) = - bincode::serde::decode_from_slice(proof, bincode::config::legacy()) - .map_err(|e| MidenError::Verify(VerifyError::BundleDeserialization(e)))?; - let program_info: ProgramInfo = self.program.clone().into(); - let stack_inputs = StackInputs::read_from_bytes(&bundle.stack_inputs) - .map_err(|e| MidenError::Verify(VerifyError::MidenDeserialization(e)))?; - let stack_outputs = StackOutputs::read_from_bytes(&bundle.stack_outputs) - .map_err(|e| MidenError::Verify(VerifyError::MidenDeserialization(e)))?; - let execution_proof = ExecutionProof::from_bytes(&bundle.proof) - .map_err(|e| MidenError::Verify(VerifyError::MidenDeserialization(e)))?; + let stack_inputs = StackInputs::default(); + let (stack_outputs, proof): (StackOutputs, ExecutionProof) = + Deserializable::read_from_bytes(proof) + .map_err(|err| CommonError::deserialize("proof", "miden", err))?; - miden_verify( - program_info, - stack_inputs, - stack_outputs.clone(), - execution_proof, - ) - .map_err(|e| MidenError::Verify(e.into()))?; + miden_verify(program_info, stack_inputs, stack_outputs.clone(), proof) + .map_err(MidenError::Verify)?; Ok(felts_to_bytes(stack_outputs.as_slice())) } @@ -184,18 +164,19 @@ pub fn felts_to_bytes(felts: &[Felt]) -> Vec { } /// Convert bytes into Miden field elements. -pub fn bytes_to_felts(bytes: &[u8]) -> Result, String> { +pub fn bytes_to_felts(bytes: &[u8]) -> Result, MidenError> { if bytes.len() % 8 != 0 { - return Err(format!( + let err = anyhow::anyhow!( "Invalid bytes length {}, expected multiple of 8", bytes.len() - )); + ); + Err(CommonError::serialize("input", "miden", err))?; } - bytes + Ok(bytes .chunks(8) .map(|bytes| Felt::try_from(u64::from_le_bytes(bytes.try_into().unwrap()))) .collect::, _>>() - .map_err(|err| err.to_string()) + .map_err(|err| CommonError::serialize("input", "miden", anyhow::anyhow!(err)))?) } #[cfg(test)] diff --git a/crates/zkvm/nexus/Cargo.toml b/crates/zkvm/nexus/Cargo.toml index ce92ea9..9ce99eb 100644 --- a/crates/zkvm/nexus/Cargo.toml +++ b/crates/zkvm/nexus/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true bincode = { workspace = true, features = ["alloc", "serde"] } postcard.workspace = true serde.workspace = true diff --git a/crates/zkvm/nexus/src/compiler/rust_rv32i.rs b/crates/zkvm/nexus/src/compiler/rust_rv32i.rs index c4fe1f0..dda0fcd 100644 --- a/crates/zkvm/nexus/src/compiler/rust_rv32i.rs +++ b/crates/zkvm/nexus/src/compiler/rust_rv32i.rs @@ -1,7 +1,4 @@ -use crate::{ - compiler::NexusProgram, - error::{CompileError, NexusError}, -}; +use crate::{compiler::NexusProgram, error::CompileError}; use ere_compile_utils::CargoBuildCmd; use ere_zkvm_interface::Compiler; use std::path::Path; @@ -20,7 +17,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[ pub struct RustRv32i; impl Compiler for RustRv32i { - type Error = NexusError; + type Error = CompileError; type Program = NexusProgram; @@ -32,8 +29,7 @@ impl Compiler for RustRv32i { .toolchain("nightly-2025-04-06") .build_options(CARGO_BUILD_OPTIONS) .rustflags(RUSTFLAGS) - .exec(guest_directory, TARGET_TRIPLE) - .map_err(CompileError::CompileUtilError)?; + .exec(guest_directory, TARGET_TRIPLE)?; Ok(elf) } } diff --git a/crates/zkvm/nexus/src/error.rs b/crates/zkvm/nexus/src/error.rs index b57299f..0865a06 100644 --- a/crates/zkvm/nexus/src/error.rs +++ b/crates/zkvm/nexus/src/error.rs @@ -1,53 +1,30 @@ -use ere_zkvm_interface::zkVMError; -use std::path::PathBuf; +use nexus_sdk::stwo::seq::Error as StwoError; +use nexus_vm::error::VMError; use thiserror::Error; -impl From for zkVMError { - fn from(value: NexusError) -> Self { - zkVMError::Other(Box::new(value)) - } +#[derive(Debug, Error)] +pub enum CompileError { + #[error(transparent)] + CommonError(#[from] ere_compile_utils::CommonError), } #[derive(Debug, Error)] pub enum NexusError { #[error(transparent)] - Compile(#[from] CompileError), + CommonError(#[from] ere_zkvm_interface::CommonError), - #[error(transparent)] - Prove(#[from] ProveError), + #[error("Parse ELF failed: {0}")] + ParseElf(#[source] VMError), - #[error(transparent)] - Verify(#[from] VerifyError), -} - -#[derive(Debug, Error)] -pub enum CompileError { - #[error("nexus execution failed: {0}")] - Client(#[source] Box), - /// Guest program directory does not exist. - #[error("guest program directory not found: {0}")] - PathNotFound(PathBuf), - /// Expected ELF file was not produced. - #[error("ELF file not found at {0}")] - ElfNotFound(PathBuf), - #[error(transparent)] - CompileUtilError(#[from] ere_compile_utils::CompileError), -} - -#[derive(Debug, Error)] -pub enum ProveError { - #[error("nexus execution failed: {0}")] - Client(#[source] Box), - #[error("Serialising proof with `bincode` failed: {0}")] - Bincode(#[from] bincode::error::EncodeError), - #[error("Serialising input with `postcard` failed: {0}")] - Postcard(String), -} - -#[derive(Debug, Error)] -pub enum VerifyError { - #[error("nexus verification failed: {0}")] - Client(#[source] Box), - #[error("Deserialising proof failed: {0}")] - Bincode(#[from] bincode::error::DecodeError), + // Execute + #[error("Nexus execution failed: {0}")] + Execute(#[source] VMError), + + // Prove + #[error("Nexus proving failed: {0}")] + Prove(#[source] StwoError), + + // Verify + #[error("Nexus verification failed: {0}")] + Verify(#[source] StwoError), } diff --git a/crates/zkvm/nexus/src/lib.rs b/crates/zkvm/nexus/src/lib.rs index 9f188fc..74cc881 100644 --- a/crates/zkvm/nexus/src/lib.rs +++ b/crates/zkvm/nexus/src/lib.rs @@ -1,12 +1,10 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use crate::{ - compiler::NexusProgram, - error::{NexusError, ProveError, VerifyError}, -}; +use crate::{compiler::NexusProgram, error::NexusError}; +use anyhow::bail; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use nexus_core::nvm::{self, ElfFile}; use nexus_sdk::{ @@ -34,18 +32,17 @@ pub struct EreNexus { } impl EreNexus { - pub fn new(elf: NexusProgram, resource: ProverResourceType) -> Self { + pub fn new(elf: NexusProgram, resource: ProverResourceType) -> Result { if !matches!(resource, ProverResourceType::Cpu) { panic!("Network or GPU proving not yet implemented for Nexus. Use CPU resource type."); } - Self { elf } + Ok(Self { elf }) } } impl zkVM for EreNexus { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { - let elf = ElfFile::from_bytes(&self.elf) - .map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?; + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { + let elf = ElfFile::from_bytes(&self.elf).map_err(NexusError::ParseElf)?; // Nexus sdk does not provide a trace, so we need to use core `nvm` // Encoding is copied directly from `prove_with_input` @@ -53,7 +50,7 @@ impl zkVM for EreNexus { Vec::new() } else { postcard::to_stdvec_cobs(&input) - .map_err(|e| NexusError::Prove(ProveError::Postcard(e.to_string())))? + .map_err(|err| CommonError::serialize("input", "postcard", err))? }; if !private_encoded.is_empty() { @@ -64,12 +61,12 @@ impl zkVM for EreNexus { let start = Instant::now(); let (view, trace) = nvm::k_trace(elf, &[], &[], private_encoded.as_slice(), 1) - .map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?; + .map_err(NexusError::Execute)?; let execution_duration = start.elapsed(); let public_values = view .public_output() - .map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?; + .map_err(|err| CommonError::deserialize("public_values", "postcard", err))?; Ok(( public_values, @@ -85,26 +82,27 @@ impl zkVM for EreNexus { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if proof_kind != ProofKind::Compressed { - panic!("Only Compressed proof kind is supported."); + bail!(CommonError::unsupported_proof_kind( + proof_kind, + [ProofKind::Compressed] + )) } - let elf = ElfFile::from_bytes(&self.elf) - .map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?; + let elf = ElfFile::from_bytes(&self.elf).map_err(NexusError::ParseElf)?; - let prover = - Stwo::new(&elf).map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?; + let prover = Stwo::new(&elf).map_err(NexusError::Prove)?; let start = Instant::now(); let (view, proof) = prover .prove_with_input(&input, &()) - .map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?; + .map_err(NexusError::Prove)?; let proving_time = start.elapsed(); let public_values = view .public_output() - .map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?; + .map_err(|err| CommonError::deserialize("public_values", "postcard", err))?; let proof_bundle = NexusProofBundle { proof, @@ -112,7 +110,7 @@ impl zkVM for EreNexus { }; let proof_bytes = bincode::serde::encode_to_vec(&proof_bundle, bincode::config::legacy()) - .map_err(|err| NexusError::Prove(ProveError::Bincode(err)))?; + .map_err(|err| CommonError::serialize("proof", "bincode", err))?; Ok(( proof_bundle.public_values, @@ -121,16 +119,19 @@ impl zkVM for EreNexus { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - return Err(zkVMError::other("Only Compressed proof kind is supported.")); + bail!(CommonError::unsupported_proof_kind( + proof.kind(), + [ProofKind::Compressed] + )) }; info!("Verifying proof..."); let (proof_bundle, _): (NexusProofBundle, _) = bincode::serde::decode_from_slice(proof, bincode::config::legacy()) - .map_err(|err| NexusError::Verify(VerifyError::Bincode(err)))?; + .map_err(|err| CommonError::deserialize("proof", "bincode", err))?; proof_bundle .proof @@ -141,7 +142,7 @@ impl zkVM for EreNexus { &self.elf, &[], ) - .map_err(|e| NexusError::Verify(VerifyError::Client(e.into())))?; + .map_err(NexusError::Verify)?; info!("Verify Succeeded!"); @@ -181,7 +182,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreNexus::new(program, ProverResourceType::Cpu); + let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_execute(&zkvm, &test_case); @@ -190,7 +191,7 @@ mod tests { #[test] fn test_execute_invalid_input() { let program = basic_program(); - let zkvm = EreNexus::new(program, ProverResourceType::Cpu); + let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.execute(&input).unwrap_err(); @@ -200,7 +201,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreNexus::new(program, ProverResourceType::Cpu); + let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_prove(&zkvm, &test_case); @@ -209,7 +210,7 @@ mod tests { #[test] fn test_prove_invalid_input() { let program = basic_program(); - let zkvm = EreNexus::new(program, ProverResourceType::Cpu); + let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.prove(&input, ProofKind::default()).unwrap_err(); diff --git a/crates/zkvm/openvm/Cargo.toml b/crates/zkvm/openvm/Cargo.toml index 5acb972..fc3be78 100644 --- a/crates/zkvm/openvm/Cargo.toml +++ b/crates/zkvm/openvm/Cargo.toml @@ -6,6 +6,8 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true +eyre.workspace = true serde = { workspace = true, features = ["derive"] } thiserror.workspace = true toml.workspace = true diff --git a/crates/zkvm/openvm/src/compiler.rs b/crates/zkvm/openvm/src/compiler.rs index eb07eb5..6278f9e 100644 --- a/crates/zkvm/openvm/src/compiler.rs +++ b/crates/zkvm/openvm/src/compiler.rs @@ -1,4 +1,5 @@ use crate::error::CompileError; +use ere_compile_utils::CommonError; use openvm_sdk::config::{AppConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP, SdkVmConfig}; use openvm_stark_sdk::config::FriParameters; use serde::{Deserialize, Serialize}; @@ -22,13 +23,10 @@ impl OpenVMProgram { app_config_path: impl AsRef, ) -> Result { let app_config = if app_config_path.as_ref().exists() { - let toml = fs::read_to_string(app_config_path.as_ref()).map_err(|source| { - CompileError::ReadConfigFailed { - source, - path: app_config_path.as_ref().to_path_buf(), - } - })?; - toml::from_str(&toml).map_err(CompileError::DeserializeConfigFailed)? + let toml = fs::read_to_string(app_config_path.as_ref()) + .map_err(|err| CommonError::read_file("app_config", &app_config_path, err))?; + toml::from_str(&toml) + .map_err(|err| CommonError::deserialize("app_config", "toml", err))? } else { // The default `AppConfig` copied from https://github.com/openvm-org/openvm/blob/v1.4.0/crates/cli/src/default.rs#L35. AppConfig { diff --git a/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs b/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs index b734f15..378e7ea 100644 --- a/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs @@ -1,7 +1,4 @@ -use crate::{ - compiler::OpenVMProgram, - error::{CompileError, OpenVMError}, -}; +use crate::{compiler::OpenVMProgram, error::CompileError}; use ere_compile_utils::CargoBuildCmd; use ere_zkvm_interface::Compiler; use std::{env, path::Path}; @@ -38,7 +35,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[ pub struct RustRv32ima; impl Compiler for RustRv32ima { - type Error = OpenVMError; + type Error = CompileError; type Program = OpenVMProgram; @@ -48,12 +45,8 @@ impl Compiler for RustRv32ima { .toolchain(toolchain) .build_options(CARGO_BUILD_OPTIONS) .rustflags(RUSTFLAGS) - .exec(guest_directory, TARGET_TRIPLE) - .map_err(CompileError::CompileUtilError)?; - Ok(OpenVMProgram::from_elf_and_app_config_path( - elf, - guest_directory.join("openvm.toml"), - )?) + .exec(guest_directory, TARGET_TRIPLE)?; + OpenVMProgram::from_elf_and_app_config_path(elf, guest_directory.join("openvm.toml")) } } diff --git a/crates/zkvm/openvm/src/compiler/rust_rv32ima_customized.rs b/crates/zkvm/openvm/src/compiler/rust_rv32ima_customized.rs index 6a8f69d..20f81af 100644 --- a/crates/zkvm/openvm/src/compiler/rust_rv32ima_customized.rs +++ b/crates/zkvm/openvm/src/compiler/rust_rv32ima_customized.rs @@ -1,7 +1,5 @@ -use crate::{ - compiler::OpenVMProgram, - error::{CompileError, OpenVMError}, -}; +use crate::{compiler::OpenVMProgram, error::CompileError}; +use ere_compile_utils::CommonError; use ere_zkvm_interface::Compiler; use openvm_build::GuestOptions; use std::{fs, path::Path}; @@ -11,7 +9,7 @@ use std::{fs, path::Path}; pub struct RustRv32imaCustomized; impl Compiler for RustRv32imaCustomized { - type Error = OpenVMError; + type Error = CompileError; type Program = OpenVMProgram; @@ -26,16 +24,11 @@ impl Compiler for RustRv32imaCustomized { }; 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, - path: elf_path.to_path_buf(), - })?; + .map_err(CompileError::UniqueElfNotFound)?; + let elf = + fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?; - Ok(OpenVMProgram::from_elf_and_app_config_path( - elf, - guest_directory.join("openvm.toml"), - )?) + OpenVMProgram::from_elf_and_app_config_path(elf, guest_directory.join("openvm.toml")) } } diff --git a/crates/zkvm/openvm/src/error.rs b/crates/zkvm/openvm/src/error.rs index 03ff790..6610f89 100644 --- a/crates/zkvm/openvm/src/error.rs +++ b/crates/zkvm/openvm/src/error.rs @@ -1,98 +1,60 @@ -use ere_zkvm_interface::zkVMError; use openvm_sdk::{SdkError, commit::AppExecutionCommit}; -use std::{io, path::PathBuf}; use thiserror::Error; -impl From for zkVMError { - fn from(value: OpenVMError) -> Self { - zkVMError::Other(Box::new(value)) - } -} +#[derive(Debug, Error)] +pub enum CompileError { + #[error(transparent)] + CommonError(#[from] ere_compile_utils::CommonError), -impl From for zkVMError { - fn from(value: CommonError) -> Self { - zkVMError::Other(Box::new(value)) - } + #[error("Failed to build guest, code: {0}")] + BuildFailed(i32), + + #[error("Guest building skipped (OPENVM_SKIP_BUILD is set)")] + BuildSkipped, + + #[error("Missing to find unique elf: {0}")] + UniqueElfNotFound(eyre::Error), } #[derive(Debug, Error)] pub enum OpenVMError { #[error(transparent)] - Compile(#[from] CompileError), + CommonError(#[from] ere_zkvm_interface::CommonError), - #[error(transparent)] - Execute(#[from] ExecuteError), - - #[error(transparent)] - Prove(#[from] ProveError), - - #[error(transparent)] - Verify(#[from] VerifyError), -} - -#[derive(Debug, Error)] -pub enum CompileError { - #[error("Failed to build guest, code: {0}")] - BuildFailed(i32), - #[error("Guest building skipped (OPENVM_SKIP_BUILD is set)")] - BuildSkipped, - #[error("Missing to find unique elf: {0}")] - UniqueElfNotFound(Box), - #[error("Failed to read elf at {path}: {source}")] - ReadElfFailed { source: io::Error, path: PathBuf }, - #[error("Failed to read OpenVM's config file at {path}: {source}")] - ReadConfigFailed { source: io::Error, path: PathBuf }, - #[error("Failed to deserialize OpenVM's config file: {0}")] - DeserializeConfigFailed(toml::de::Error), - #[error(transparent)] - CompileUtilError(#[from] ere_compile_utils::CompileError), -} - -#[derive(Debug, Error)] -pub enum ExecuteError { - #[error("OpenVM execute failed: {0}")] - Execute(#[source] SdkError), - #[error(transparent)] - Common(#[from] CommonError), -} - -#[derive(Debug, Error)] -pub enum ProveError { - #[error("OpenVM prove failed: {0}")] - Prove(#[source] SdkError), - #[error("Unexpected app commit: {proved:?}, expected: {preprocessed:?}")] - UnexpectedAppCommit { - preprocessed: AppExecutionCommit, - proved: AppExecutionCommit, - }, - #[error("Serialize proof failed: {0}")] - SerializeProof(io::Error), - #[error(transparent)] - Common(#[from] CommonError), -} - -#[derive(Debug, Error)] -pub enum VerifyError { - #[error("OpenVM verification failed: {0}")] - Verify(#[source] SdkError), - #[error("Deserialize proof failed: {0}")] - DeserializeProof(io::Error), - #[error(transparent)] - Common(#[from] CommonError), -} - -#[derive(Debug, Error)] -pub enum CommonError { + // Common #[error("Initialize SDK failed: {0}")] SdkInit(SdkError), + #[error("Decode elf failed: {0}")] - ElfDecode(Box), + ElfDecode(eyre::Error), + #[error("Transpile elf failed: {0}")] Transpile(SdkError), + #[error("Read aggregation key failed: {0}")] - ReadAggKeyFailed(Box), + ReadAggKeyFailed(eyre::Error), + #[error("Initialize prover failed: {0}")] ProverInit(SdkError), + #[error("Invalid public value")] InvalidPublicValue, + + // Execute + #[error("OpenVM execution failed: {0}")] + Execute(#[source] SdkError), + + // Prove + #[error("OpenVM proving failed: {0}")] + Prove(#[source] SdkError), + + #[error("Unexpected app commit: {proved:?}, expected: {preprocessed:?}")] + UnexpectedAppCommit { + preprocessed: Box, + proved: Box, + }, + + // Verify + #[error("OpenVM verification failed: {0}")] + Verify(#[source] SdkError), } diff --git a/crates/zkvm/openvm/src/lib.rs b/crates/zkvm/openvm/src/lib.rs index bb368cb..e4ebad2 100644 --- a/crates/zkvm/openvm/src/lib.rs +++ b/crates/zkvm/openvm/src/lib.rs @@ -1,12 +1,10 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use crate::{ - compiler::OpenVMProgram, - error::{CommonError, ExecuteError, OpenVMError, ProveError, VerifyError}, -}; +use crate::{compiler::OpenVMProgram, error::OpenVMError}; +use anyhow::bail; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use openvm_circuit::arch::instructions::exe::VmExe; use openvm_continuations::verifier::internal::types::VmStarkProof; @@ -38,7 +36,7 @@ pub struct EreOpenVM { } impl EreOpenVM { - pub fn new(program: OpenVMProgram, resource: ProverResourceType) -> Result { + pub fn new(program: OpenVMProgram, resource: ProverResourceType) -> Result { match resource { #[cfg(not(feature = "cuda"))] ProverResourceType::Gpu => { @@ -52,24 +50,23 @@ impl EreOpenVM { _ => {} } - let sdk = CpuSdk::new(program.app_config.clone()).map_err(CommonError::SdkInit)?; + let sdk = CpuSdk::new(program.app_config.clone()).map_err(OpenVMError::SdkInit)?; - let elf = Elf::decode(&program.elf, MEM_SIZE as u32) - .map_err(|e| CommonError::ElfDecode(e.into()))?; + let elf = Elf::decode(&program.elf, MEM_SIZE as u32).map_err(OpenVMError::ElfDecode)?; - let app_exe = sdk.convert_to_exe(elf).map_err(CommonError::Transpile)?; + let app_exe = sdk.convert_to_exe(elf).map_err(OpenVMError::Transpile)?; let (app_pk, _) = sdk.app_keygen(); let agg_pk = read_object_from_file::(agg_pk_path()) - .map_err(|e| CommonError::ReadAggKeyFailed(e.into()))?; + .map_err(OpenVMError::ReadAggKeyFailed)?; let agg_vk = agg_pk.get_agg_vk(); let _ = sdk.set_agg_pk(agg_pk.clone()); let app_commit = sdk .prover(app_exe.clone()) - .map_err(CommonError::ProverInit)? + .map_err(OpenVMError::ProverInit)? .app_commit(); Ok(Self { @@ -83,18 +80,18 @@ impl EreOpenVM { }) } - fn cpu_sdk(&self) -> Result { + fn cpu_sdk(&self) -> Result { let sdk = CpuSdk::new_without_transpiler(self.app_config.clone()) - .map_err(CommonError::SdkInit)?; + .map_err(OpenVMError::SdkInit)?; let _ = sdk.set_app_pk(self.app_pk.clone()); let _ = sdk.set_agg_pk(self.agg_pk.clone()); Ok(sdk) } #[cfg(feature = "cuda")] - fn gpu_sdk(&self) -> Result { + fn gpu_sdk(&self) -> Result { let sdk = openvm_sdk::GpuSdk::new_without_transpiler(self.app_config.clone()) - .map_err(CommonError::SdkInit)?; + .map_err(OpenVMError::SdkInit)?; let _ = sdk.set_app_pk(self.app_pk.clone()); let _ = sdk.set_agg_pk(self.agg_pk.clone()); Ok(sdk) @@ -102,7 +99,7 @@ impl EreOpenVM { } impl zkVM for EreOpenVM { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let mut stdin = StdIn::default(); stdin.write_bytes(input); @@ -110,7 +107,7 @@ impl zkVM for EreOpenVM { let public_values = self .cpu_sdk()? .execute(self.app_exe.clone(), stdin) - .map_err(|e| OpenVMError::from(ExecuteError::Execute(e)))?; + .map_err(OpenVMError::Execute)?; Ok(( public_values, @@ -125,9 +122,12 @@ impl zkVM for EreOpenVM { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if proof_kind != ProofKind::Compressed { - panic!("Only Compressed proof kind is supported."); + bail!(CommonError::unsupported_proof_kind( + proof_kind, + [ProofKind::Compressed] + )) } let mut stdin = StdIn::default(); @@ -148,20 +148,20 @@ impl zkVM for EreOpenVM { ); } } - .map_err(|e| OpenVMError::from(ProveError::Prove(e)))?; + .map_err(OpenVMError::Prove)?; let elapsed = now.elapsed(); if app_commit != self.app_commit { - return Err(OpenVMError::from(ProveError::UnexpectedAppCommit { - preprocessed: self.app_commit, - proved: app_commit, - }))?; + bail!(OpenVMError::UnexpectedAppCommit { + preprocessed: self.app_commit.into(), + proved: app_commit.into(), + }); } let public_values = extract_public_values(&proof.user_public_values)?; let proof_bytes = proof .encode_to_vec() - .map_err(|e| OpenVMError::from(ProveError::SerializeProof(e)))?; + .map_err(|err| CommonError::serialize("proof", "openvm_sdk", err))?; Ok(( public_values, @@ -170,16 +170,18 @@ impl zkVM for EreOpenVM { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - return Err(zkVMError::other("Only Compressed proof kind is supported.")); + bail!(CommonError::unsupported_proof_kind( + proof.kind(), + [ProofKind::Compressed] + )) }; let proof = VmStarkProof::::decode(&mut proof.as_slice()) - .map_err(|e| OpenVMError::from(VerifyError::DeserializeProof(e)))?; + .map_err(|err| CommonError::deserialize("proof", "openvm_sdk", err))?; - CpuSdk::verify_proof(&self.agg_vk, self.app_commit, &proof) - .map_err(|e| OpenVMError::Verify(VerifyError::Verify(e)))?; + CpuSdk::verify_proof(&self.agg_vk, self.app_commit, &proof).map_err(OpenVMError::Verify)?; let public_values = extract_public_values(&proof.user_public_values)?; @@ -199,12 +201,12 @@ impl zkVM for EreOpenVM { /// /// The public values revealed in guest program will be flatten into `Vec` /// then converted to field elements `Vec`, so here we try to downcast it. -fn extract_public_values(user_public_values: &[F]) -> Result, CommonError> { +fn extract_public_values(user_public_values: &[F]) -> Result, OpenVMError> { user_public_values .iter() .map(|v| u8::try_from(v.as_canonical_u32()).ok()) .collect::>() - .ok_or(CommonError::InvalidPublicValue) + .ok_or(OpenVMError::InvalidPublicValue) } fn agg_pk_path() -> PathBuf { diff --git a/crates/zkvm/pico/src/compiler/rust_rv32ima.rs b/crates/zkvm/pico/src/compiler/rust_rv32ima.rs index 7cb6f5f..51e2a11 100644 --- a/crates/zkvm/pico/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/pico/src/compiler/rust_rv32ima.rs @@ -1,7 +1,4 @@ -use crate::{ - compiler::PicoProgram, - error::{CompileError, PicoError}, -}; +use crate::{compiler::PicoProgram, error::CompileError}; use ere_compile_utils::CargoBuildCmd; use ere_zkvm_interface::Compiler; use std::{env, path::Path}; @@ -35,7 +32,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[ pub struct RustRv32ima; impl Compiler for RustRv32ima { - type Error = PicoError; + type Error = CompileError; type Program = PicoProgram; @@ -45,8 +42,7 @@ impl Compiler for RustRv32ima { .toolchain(toolchain) .build_options(CARGO_BUILD_OPTIONS) .rustflags(RUSTFLAGS) - .exec(guest_directory, TARGET_TRIPLE) - .map_err(CompileError::CompileUtilError)?; + .exec(guest_directory, TARGET_TRIPLE)?; Ok(elf) } } @@ -68,7 +64,7 @@ mod tests { fn test_execute() { let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std"); let program = RustRv32ima.compile(&guest_directory).unwrap(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu); + let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); zkvm.execute(&[]).unwrap(); } diff --git a/crates/zkvm/pico/src/compiler/rust_rv32ima_customized.rs b/crates/zkvm/pico/src/compiler/rust_rv32ima_customized.rs index da1e3c6..59573db 100644 --- a/crates/zkvm/pico/src/compiler/rust_rv32ima_customized.rs +++ b/crates/zkvm/pico/src/compiler/rust_rv32ima_customized.rs @@ -1,4 +1,5 @@ -use crate::error::{CompileError, PicoError}; +use crate::error::CompileError; +use ere_compile_utils::{CommonError, cargo_metadata}; use ere_zkvm_interface::Compiler; use std::{fs, path::Path, process::Command}; use tempfile::tempdir; @@ -8,43 +9,31 @@ use tempfile::tempdir; pub struct RustRv32imaCustomized; impl Compiler for RustRv32imaCustomized { - type Error = PicoError; + type Error = CompileError; type Program = Vec; fn compile(&self, guest_directory: &Path) -> Result { - let tempdir = tempdir().map_err(CompileError::Tempdir)?; + let tempdir = tempdir().map_err(CommonError::tempdir)?; - // 1. Check guest path - if !guest_directory.exists() { - return Err(CompileError::PathNotFound(guest_directory.to_path_buf()))?; - } + cargo_metadata(guest_directory)?; - // 2. Run `cargo pico build` - let status = Command::new("cargo") + let mut cmd = Command::new("cargo"); + let status = cmd .current_dir(guest_directory) .env("RUST_LOG", "info") .args(["pico", "build", "--output-directory"]) .arg(tempdir.path()) .status() - .map_err(CompileError::CargoPicoBuild)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !status.success() { - return Err(CompileError::CargoPicoBuildFailed { status })?; + return Err(CommonError::command_exit_non_zero(&cmd, status, None))?; } - // 3. Locate the ELF file let elf_path = tempdir.path().join("riscv32im-pico-zkvm-elf"); - - if !elf_path.exists() { - return Err(CompileError::ElfNotFound(elf_path))?; - } - - // 4. Read the ELF file - let elf_bytes = fs::read(&elf_path).map_err(|e| CompileError::ReadElf { - path: elf_path, - source: e, - })?; + let elf_bytes = + fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?; Ok(elf_bytes) } diff --git a/crates/zkvm/pico/src/error.rs b/crates/zkvm/pico/src/error.rs index e19242b..9d207c4 100644 --- a/crates/zkvm/pico/src/error.rs +++ b/crates/zkvm/pico/src/error.rs @@ -1,85 +1,40 @@ -use ere_zkvm_interface::zkVMError; -use std::{io, path::PathBuf, process::ExitStatus}; use thiserror::Error; -impl From for zkVMError { - fn from(value: PicoError) -> Self { - zkVMError::Other(Box::new(value)) - } +#[derive(Debug, Error)] +pub enum CompileError { + #[error(transparent)] + CommonError(#[from] ere_compile_utils::CommonError), } #[derive(Debug, Error)] pub enum PicoError { #[error(transparent)] - Compile(#[from] CompileError), + CommonError(#[from] ere_zkvm_interface::CommonError), - #[error(transparent)] - Execute(#[from] ExecuteError), - - #[error(transparent)] - Prove(#[from] ProveError), - - #[error(transparent)] - Verify(#[from] VerifyError), -} - -#[derive(Debug, Error)] -pub enum CompileError { - #[error("Failed to create temporary directory: {0}")] - Tempdir(io::Error), - /// Guest program directory does not exist. - #[error("guest program directory not found: {0}")] - PathNotFound(PathBuf), - /// Failed to spawn or run `cargo pico build`. - #[error("failed to run `cargo pico build`: {0}")] - CargoPicoBuild(#[from] io::Error), - /// `cargo pico build` exited with a non-zero status. - #[error("`cargo pico build` failed with status {status:?}")] - CargoPicoBuildFailed { status: ExitStatus }, - /// Expected ELF file was not produced. - #[error("ELF file not found at {0}")] - ElfNotFound(PathBuf), - /// Reading the ELF file failed. - #[error("failed to read ELF file at {path}: {source}")] - ReadElf { - path: PathBuf, - #[source] - source: io::Error, - }, - #[error(transparent)] - CompileUtilError(#[from] ere_compile_utils::CompileError), -} - -#[derive(Debug, Error)] -pub enum ExecuteError { - #[error("Pico execution failed: {0}")] - Client(anyhow::Error), + // Execute #[error("Pico execution panicked: {0}")] - Panic(String), -} + ExecutePanic(String), -#[derive(Debug, Error)] -pub enum ProveError { + // Prove #[error("Pico proving failed: {0}")] - Client(anyhow::Error), - #[error("Pico proving panicked: {0}")] - Panic(String), - #[error("Serialising proof with `bincode` failed: {0}")] - Bincode(#[from] bincode::error::EncodeError), -} + Prove(#[source] anyhow::Error), -#[derive(Debug, Error)] -pub enum VerifyError { + #[error("Pico proving panicked: {0}")] + ProvePanic(String), + + // Verify #[error("Pico verifying failed: {0}")] - Client(anyhow::Error), - #[error("Deserialising proof with `bincode` failed: {0}")] - Bincode(#[from] bincode::error::DecodeError), + Verify(#[source] anyhow::Error), + #[error("Invalid base proof length {0}, expected 1")] InvalidBaseProofLength(usize), + #[error("Invalid public values length {0}, expected at least 32")] InvalidPublicValuesLength(usize), + #[error("First 32 public values are expected in byte")] InvalidPublicValues, - #[error("Public values digest are expected in bytes")] - InvalidPublicValuesDigest, + + #[error("Unexpected public value digest - claimed: {claimed:?}, proved: {proved:?}")] + UnexpectedPublicValuesDigest { claimed: [u8; 32], proved: [u8; 32] }, } diff --git a/crates/zkvm/pico/src/lib.rs b/crates/zkvm/pico/src/lib.rs index 9ee5694..d7b10d7 100644 --- a/crates/zkvm/pico/src/lib.rs +++ b/crates/zkvm/pico/src/lib.rs @@ -3,11 +3,12 @@ use crate::{ client::{MetaProof, ProverClient}, compiler::PicoProgram, - error::{ExecuteError, PicoError, ProveError, VerifyError}, + error::PicoError, }; +use anyhow::bail; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use pico_p3_field::PrimeField32; use pico_vm::emulator::stdin::EmulatorStdinBuilder; @@ -32,11 +33,11 @@ pub struct ErePico { } impl ErePico { - pub fn new(program: PicoProgram, resource: ProverResourceType) -> Self { + pub fn new(program: PicoProgram, resource: ProverResourceType) -> Result { if !matches!(resource, ProverResourceType::Cpu) { panic!("Network or GPU proving not yet implemented for Pico. Use CPU resource type."); } - ErePico { program } + Ok(ErePico { program }) } pub fn client(&self) -> ProverClient { @@ -45,7 +46,7 @@ impl ErePico { } impl zkVM for ErePico { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let mut stdin = EmulatorStdinBuilder::default(); stdin.write_slice(input); @@ -55,7 +56,7 @@ impl zkVM for ErePico { let result = client.execute(stdin); (result, start.elapsed()) }) - .map_err(|err| PicoError::Execute(ExecuteError::Panic(panic_msg(err))))?; + .map_err(|err| PicoError::ExecutePanic(panic_msg(err)))?; Ok(( public_values, @@ -71,16 +72,12 @@ impl zkVM for ErePico { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result< - ( - PublicValues, - Proof, - ere_zkvm_interface::ProgramProvingReport, - ), - zkVMError, - > { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if proof_kind != ProofKind::Compressed { - panic!("Only Compressed proof kind is implemented."); + bail!(CommonError::unsupported_proof_kind( + proof_kind, + [ProofKind::Compressed] + )) } let mut stdin = EmulatorStdinBuilder::default(); @@ -92,8 +89,8 @@ impl zkVM for ErePico { let result = client.prove(stdin)?; Ok((result, start.elapsed())) }) - .map_err(|err| PicoError::Prove(ProveError::Panic(panic_msg(err))))? - .map_err(|err| PicoError::Prove(ProveError::Client(err)))?; + .map_err(|err| PicoError::ProvePanic(panic_msg(err)))? + .map_err(PicoError::Prove)?; let proof_bytes = bincode::serde::encode_to_vec( &PicoProofWithPublicValues { @@ -102,7 +99,7 @@ impl zkVM for ErePico { }, bincode::config::legacy(), ) - .map_err(|err| PicoError::Prove(ProveError::Bincode(err)))?; + .map_err(|err| CommonError::serialize("proof", "bincode", err))?; Ok(( public_values, @@ -111,27 +108,26 @@ impl zkVM for ErePico { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - return Err(zkVMError::other( - "Only Compressed proof kind is implemented.", - )); + bail!(CommonError::unsupported_proof_kind( + proof.kind(), + [ProofKind::Compressed] + )) }; let client = self.client(); let (proof, _): (PicoProofWithPublicValues, _) = bincode::serde::decode_from_slice(proof, bincode::config::legacy()) - .map_err(|err| PicoError::Verify(VerifyError::Bincode(err)))?; + .map_err(|err| CommonError::deserialize("proof", "bincode", err))?; - client - .verify(&proof.proof) - .map_err(|err| PicoError::Verify(VerifyError::Client(err)))?; + client.verify(&proof.proof).map_err(PicoError::Verify)?; - if extract_public_values_sha256_digest(&proof.proof).map_err(PicoError::Verify)? - != <[u8; 32]>::from(Sha256::digest(&proof.public_values)) - { - return Err(PicoError::Verify(VerifyError::InvalidPublicValuesDigest))?; + let claimed = <[u8; 32]>::from(Sha256::digest(&proof.public_values)); + let proved = extract_public_values_sha256_digest(&proof.proof)?; + if claimed != proved { + bail!(PicoError::UnexpectedPublicValuesDigest { claimed, proved }); } Ok(proof.public_values) @@ -149,13 +145,13 @@ impl zkVM for ErePico { /// Extract public values sha256 digest from base proof of compressed proof. /// The sha256 digest will be placed at the first 32 field elements of the /// public values of the only base proof. -fn extract_public_values_sha256_digest(proof: &MetaProof) -> Result<[u8; 32], VerifyError> { +fn extract_public_values_sha256_digest(proof: &MetaProof) -> Result<[u8; 32], PicoError> { if proof.proofs().len() != 1 { - return Err(VerifyError::InvalidBaseProofLength(proof.proofs().len())); + return Err(PicoError::InvalidBaseProofLength(proof.proofs().len())); } if proof.proofs()[0].public_values.len() < 32 { - return Err(VerifyError::InvalidPublicValuesLength( + return Err(PicoError::InvalidPublicValuesLength( proof.proofs()[0].public_values.len(), )); } @@ -164,7 +160,7 @@ fn extract_public_values_sha256_digest(proof: &MetaProof) -> Result<[u8; 32], Ve .iter() .map(|value| u8::try_from(value.as_canonical_u32())) .collect::, _>>() - .map_err(|_| VerifyError::InvalidPublicValues)? + .map_err(|_| PicoError::InvalidPublicValues)? .try_into() .unwrap()) } @@ -203,7 +199,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu); + let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_execute(&zkvm, &test_case); @@ -212,7 +208,7 @@ mod tests { #[test] fn test_execute_invalid_input() { let program = basic_program(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu); + let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.execute(&input).unwrap_err(); @@ -222,7 +218,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu); + let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_prove(&zkvm, &test_case); @@ -231,7 +227,7 @@ mod tests { #[test] fn test_prove_invalid_input() { let program = basic_program(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu); + let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.prove(&input, ProofKind::default()).unwrap_err(); diff --git a/crates/zkvm/risc0/Cargo.toml b/crates/zkvm/risc0/Cargo.toml index ea7a1b5..7cb7698 100644 --- a/crates/zkvm/risc0/Cargo.toml +++ b/crates/zkvm/risc0/Cargo.toml @@ -14,6 +14,7 @@ tracing.workspace = true # Risc0 dependencies risc0-build = { workspace = true, features = ["unstable"] } +risc0-zkp.workspace = true risc0-zkvm = { workspace = true, features = ["client", "unstable"] } risc0-binfmt.workspace = true diff --git a/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs b/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs index 84dab72..633c8bc 100644 --- a/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs @@ -1,10 +1,8 @@ -use crate::compiler::Risc0Program; -use crate::error::{CompileError, Risc0Error}; +use crate::{compiler::Risc0Program, error::CompileError}; use ere_compile_utils::CargoBuildCmd; use ere_zkvm_interface::Compiler; use risc0_binfmt::ProgramBinary; -use std::env; -use std::path::Path; +use std::{env, path::Path}; use tracing::info; // TODO: Make this with `zkos` package building to avoid binary file storing in repo. @@ -34,7 +32,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[ pub struct RustRv32ima; impl Compiler for RustRv32ima { - type Error = Risc0Error; + type Error = CompileError; type Program = Risc0Program; @@ -44,8 +42,7 @@ impl Compiler for RustRv32ima { .toolchain(toolchain) .build_options(CARGO_BUILD_OPTIONS) .rustflags(RUSTFLAGS) - .exec(guest_directory, TARGET_TRIPLE) - .map_err(CompileError::CompileUtilError)?; + .exec(guest_directory, TARGET_TRIPLE)?; let program = ProgramBinary::new(elf.as_slice(), V1COMPAT_ELF); let image_id = program diff --git a/crates/zkvm/risc0/src/compiler/rust_rv32ima_customized.rs b/crates/zkvm/risc0/src/compiler/rust_rv32ima_customized.rs index 7fe3575..145d73b 100644 --- a/crates/zkvm/risc0/src/compiler/rust_rv32ima_customized.rs +++ b/crates/zkvm/risc0/src/compiler/rust_rv32ima_customized.rs @@ -1,7 +1,4 @@ -use crate::{ - compiler::Risc0Program, - error::{CompileError, Risc0Error}, -}; +use crate::{compiler::Risc0Program, error::CompileError}; use ere_compile_utils::cargo_metadata; use ere_zkvm_interface::Compiler; use risc0_build::GuestOptions; @@ -13,14 +10,14 @@ use tracing::info; pub struct RustRv32imaCustomized; impl Compiler for RustRv32imaCustomized { - type Error = Risc0Error; + type Error = CompileError; type Program = Risc0Program; fn compile(&self, guest_directory: &Path) -> Result { info!("Compiling Risc0 program at {}", guest_directory.display()); - let metadata = cargo_metadata(guest_directory).map_err(CompileError::CompileUtilError)?; + let metadata = cargo_metadata(guest_directory)?; let package = metadata.root_package().unwrap(); // Use `risc0_build::build_package` to build package instead of calling @@ -30,9 +27,9 @@ impl Compiler for RustRv32imaCustomized { &metadata.target_directory, GuestOptions::default(), ) - .map_err(|source| CompileError::BuildFailure { - source, - crate_path: guest_directory.to_path_buf(), + .map_err(|err| CompileError::BuildFailure { + err, + guest_path: guest_directory.to_path_buf(), })? .into_iter() .next() diff --git a/crates/zkvm/risc0/src/error.rs b/crates/zkvm/risc0/src/error.rs index 04988f8..1ec9aac 100644 --- a/crates/zkvm/risc0/src/error.rs +++ b/crates/zkvm/risc0/src/error.rs @@ -1,24 +1,47 @@ +use ere_zkvm_interface::ProofKind; +use risc0_zkp::verify::VerificationError; use std::path::PathBuf; use thiserror::Error; #[derive(Debug, Error)] -pub enum Risc0Error { +pub enum CompileError { #[error(transparent)] - Compile(#[from] CompileError), + CommonError(#[from] ere_compile_utils::CommonError), + + #[error("`risc0_build::build_package` for {guest_path} failed: {err}")] + BuildFailure { + #[source] + err: anyhow::Error, + guest_path: PathBuf, + }, + + #[error("`risc0_build::build_package` succeeded but failed to find guest")] + Risc0BuildMissingGuest, + + #[error("ELF binary image calculation failure: {0}")] + ImageIDCalculationFailure(anyhow::Error), } #[derive(Debug, Error)] -pub enum CompileError { - #[error("`risc0_build::build_package` for {crate_path} failed: {source}")] - BuildFailure { - #[source] - source: anyhow::Error, - crate_path: PathBuf, - }, - #[error("`risc0_build::build_package` succeeded but failed to find guest")] - Risc0BuildMissingGuest, - #[error("ELF binary image calculation failure : {0}")] - ImageIDCalculationFailure(anyhow::Error), - #[error(transparent)] - CompileUtilError(#[from] ere_compile_utils::CompileError), +pub enum Risc0Error { + // Execute + #[error("Failed to build `ExecutorEnv`: {0}")] + BuildExecutorEnv(anyhow::Error), + + #[error("Failed to execute: {0}")] + Execute(anyhow::Error), + + // Prove + #[error("Failed to initialize cuda prover: {0}")] + InitializeCudaProver(anyhow::Error), + + #[error("Failed to prove: {0}")] + Prove(anyhow::Error), + + // Verify + #[error("Invalid proof kind, expected: {0:?}, got: {1}")] + InvalidProofKind(ProofKind, String), + + #[error("Failed to verify: {0}")] + Verify(VerificationError), } diff --git a/crates/zkvm/risc0/src/lib.rs b/crates/zkvm/risc0/src/lib.rs index 693735e..bdd9a63 100644 --- a/crates/zkvm/risc0/src/lib.rs +++ b/crates/zkvm/risc0/src/lib.rs @@ -1,9 +1,10 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use crate::compiler::Risc0Program; +use crate::{compiler::Risc0Program, error::Risc0Error}; +use anyhow::bail; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use risc0_zkvm::{ DEFAULT_MAX_PO2, DefaultProver, ExecutorEnv, ExternalProver, InnerReceipt, ProverOpts, Receipt, @@ -50,7 +51,7 @@ pub struct EreRisc0 { } impl EreRisc0 { - pub fn new(program: Risc0Program, resource: ProverResourceType) -> Result { + pub fn new(program: Risc0Program, resource: ProverResourceType) -> Result { if matches!(resource, ProverResourceType::Network(_)) { panic!( "Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type." @@ -82,17 +83,17 @@ impl EreRisc0 { } impl zkVM for EreRisc0 { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let executor = default_executor(); let env = ExecutorEnv::builder() .write_slice(input) .build() - .map_err(zkVMError::other)?; + .map_err(Risc0Error::BuildExecutorEnv)?; let start = Instant::now(); let session_info = executor .execute(env, &self.program.elf) - .map_err(zkVMError::other)?; + .map_err(Risc0Error::Execute)?; let public_values = session_info.journal.bytes.clone(); @@ -110,7 +111,7 @@ impl zkVM for EreRisc0 { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { let prover = match self.resource { ProverResourceType::Cpu => Rc::new(ExternalProver::new("ipc", "r0vm")), ProverResourceType::Gpu => { @@ -124,7 +125,10 @@ impl zkVM for EreRisc0 { // workers to do multi-gpu proving. // It uses env `RISC0_DEFAULT_PROVER_NUM_GPUS` to determine // how many available GPUs there are. - Rc::new(DefaultProver::new("r0vm-cuda").map_err(zkVMError::other)?) + Rc::new( + DefaultProver::new("r0vm-cuda") + .map_err(Risc0Error::InitializeCudaProver)?, + ) } } ProverResourceType::Network(_) => { @@ -138,25 +142,25 @@ impl zkVM for EreRisc0 { .write_slice(input) .segment_limit_po2(self.segment_po2 as _) .keccak_max_po2(self.keccak_po2 as _) - .map_err(zkVMError::other)? - .build() - .map_err(zkVMError::other)?; + .and_then(|builder| builder.build()) + .map_err(Risc0Error::BuildExecutorEnv)?; let opts = match proof_kind { ProofKind::Compressed => ProverOpts::succinct(), ProofKind::Groth16 => ProverOpts::groth16(), }; - let now = std::time::Instant::now(); + let now = Instant::now(); let prove_info = prover .prove_with_opts(env, &self.program.elf, &opts) - .map_err(zkVMError::other)?; + .map_err(Risc0Error::Prove)?; let proving_time = now.elapsed(); let public_values = prove_info.receipt.journal.bytes.clone(); let proof = Proof::new( proof_kind, - borsh::to_vec(&prove_info.receipt).map_err(zkVMError::other)?, + borsh::to_vec(&prove_info.receipt) + .map_err(|err| CommonError::serialize("proof", "borsh", err))?, ); Ok(( @@ -166,24 +170,30 @@ impl zkVM for EreRisc0 { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let proof_kind = proof.kind(); - let receipt: Receipt = borsh::from_slice(proof.as_bytes()).map_err(zkVMError::other)?; + let receipt: Receipt = borsh::from_slice(proof.as_bytes()) + .map_err(|err| CommonError::deserialize("proof", "borsh", err))?; if !matches!( (proof_kind, &receipt.inner), (ProofKind::Compressed, InnerReceipt::Succinct(_)) | (ProofKind::Groth16, InnerReceipt::Groth16(_)) ) { - return Err(zkVMError::other(format!( - "Invalid inner receipt kind, expected {proof_kind:?}", - )))?; + let got = match &receipt.inner { + InnerReceipt::Composite(_) => "Composite", + InnerReceipt::Succinct(_) => "Succinct", + InnerReceipt::Groth16(_) => "Groth16", + InnerReceipt::Fake(_) => "Fake", + _ => "Unknown", + }; + bail!(Risc0Error::InvalidProofKind(proof_kind, got.to_string())); } receipt .verify(self.program.image_id) - .map_err(zkVMError::other)?; + .map_err(Risc0Error::Verify)?; let public_values = receipt.journal.bytes.clone(); diff --git a/crates/zkvm/sp1/Cargo.toml b/crates/zkvm/sp1/Cargo.toml index adef140..be8abb9 100644 --- a/crates/zkvm/sp1/Cargo.toml +++ b/crates/zkvm/sp1/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true bincode = { workspace = true, features = ["alloc", "serde"] } tempfile.workspace = true thiserror.workspace = true diff --git a/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs b/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs index 09ce7d9..fd9902a 100644 --- a/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs @@ -1,7 +1,4 @@ -use crate::{ - compiler::SP1Program, - error::{CompileError, SP1Error}, -}; +use crate::{compiler::SP1Program, error::CompileError}; use ere_compile_utils::CargoBuildCmd; use ere_zkvm_interface::Compiler; use std::{env, path::Path}; @@ -35,7 +32,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[ pub struct RustRv32ima; impl Compiler for RustRv32ima { - type Error = SP1Error; + type Error = CompileError; type Program = SP1Program; @@ -45,8 +42,7 @@ impl Compiler for RustRv32ima { .toolchain(toolchain) .build_options(CARGO_BUILD_OPTIONS) .rustflags(RUSTFLAGS) - .exec(guest_directory, TARGET_TRIPLE) - .map_err(CompileError::CompileUtilError)?; + .exec(guest_directory, TARGET_TRIPLE)?; Ok(elf) } } @@ -68,7 +64,7 @@ mod tests { fn test_execute() { let guest_directory = testing_guest_directory("sp1", "stock_nightly_no_std"); let program = RustRv32ima.compile(&guest_directory).unwrap(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu); + let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); zkvm.execute(&[]).unwrap(); } diff --git a/crates/zkvm/sp1/src/compiler/rust_rv32ima_customized.rs b/crates/zkvm/sp1/src/compiler/rust_rv32ima_customized.rs index f984ad6..4c3df10 100644 --- a/crates/zkvm/sp1/src/compiler/rust_rv32ima_customized.rs +++ b/crates/zkvm/sp1/src/compiler/rust_rv32ima_customized.rs @@ -1,7 +1,5 @@ -use crate::{ - compiler::SP1Program, - error::{CompileError, SP1Error}, -}; +use crate::{compiler::SP1Program, error::CompileError}; +use ere_compile_utils::{CommonError, cargo_metadata}; use ere_zkvm_interface::Compiler; use std::{fs, path::Path, process::Command}; use tempfile::tempdir; @@ -12,68 +10,44 @@ use tracing::info; pub struct RustRv32imaCustomized; impl Compiler for RustRv32imaCustomized { - type Error = SP1Error; + type Error = CompileError; type Program = SP1Program; fn compile(&self, guest_directory: &Path) -> Result { info!("Compiling SP1 program at {}", guest_directory.display()); - if !guest_directory.exists() || !guest_directory.is_dir() { - return Err(CompileError::InvalidProgramPath( - guest_directory.to_path_buf(), - ))?; - } - - 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(), - })?; - } + cargo_metadata(guest_directory)?; // ── build into a temp dir ───────────────────────────────────────────── - let temp_output_dir = tempdir().map_err(CompileError::TempDir)?; - let temp_output_dir_path = temp_output_dir.path(); + let output_dir = tempdir().map_err(CommonError::tempdir)?; info!( "Running `cargo prove build` → dir: {}", - temp_output_dir_path.display(), + output_dir.path().display(), ); - let status = Command::new("cargo") + let mut cmd = Command::new("cargo"); + let status = cmd .current_dir(guest_directory) .args([ "prove", "build", "--output-directory", - temp_output_dir_path.to_str().unwrap(), + &output_dir.path().to_string_lossy(), "--elf-name", "guest.elf", ]) .status() - .map_err(|e| CompileError::CargoProveBuild { - cwd: guest_directory.to_path_buf(), - source: e, - })?; + .map_err(|err| CommonError::command(&cmd, err))?; if !status.success() { - return Err(CompileError::CargoProveBuildFailed { - status, - path: guest_directory.to_path_buf(), - })?; + return Err(CommonError::command_exit_non_zero(&cmd, status, None))?; } - 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, - })?; + let elf_path = output_dir.path().join("guest.elf"); + let elf_bytes = + fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?; info!("SP1 program compiled OK - {} bytes", elf_bytes.len()); Ok(elf_bytes) diff --git a/crates/zkvm/sp1/src/error.rs b/crates/zkvm/sp1/src/error.rs index d59b0ae..6920ffe 100644 --- a/crates/zkvm/sp1/src/error.rs +++ b/crates/zkvm/sp1/src/error.rs @@ -1,92 +1,33 @@ -use std::{path::PathBuf, process::ExitStatus}; - -use ere_zkvm_interface::{ProofKind, zkVMError}; -use sp1_sdk::SP1ProofMode; +use ere_zkvm_interface::ProofKind; +use sp1_sdk::{SP1ProofMode, SP1VerificationError}; use thiserror::Error; -impl From for zkVMError { - fn from(value: SP1Error) -> Self { - zkVMError::Other(Box::new(value)) - } +#[derive(Debug, Error)] +pub enum CompileError { + #[error(transparent)] + CommonError(#[from] ere_compile_utils::CommonError), } #[derive(Debug, Error)] pub enum SP1Error { #[error(transparent)] - CompileError(#[from] CompileError), + CommonError(#[from] ere_zkvm_interface::CommonError), - #[error(transparent)] - Execute(#[from] ExecuteError), - - #[error(transparent)] - Prove(#[from] ProveError), - - #[error(transparent)] - Verify(#[from] VerifyError), -} - -/// Errors that can be encountered while compiling a SP1 program -#[derive(Debug, Error)] -pub enum CompileError { - #[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("Failed to create temporary output directory: {0}")] - TempDir(#[from] std::io::Error), - #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] - MissingPackageName { path: PathBuf }, - #[error("Failed to execute `cargo prove build` in {cwd}: {source}")] - CargoProveBuild { - cwd: PathBuf, - #[source] - source: std::io::Error, - }, - #[error("`cargo prove build` failed with status: {status} for program at {path}")] - CargoProveBuildFailed { status: ExitStatus, path: PathBuf }, - #[error("Compiled ELF not found at expected path: {0}")] - ElfNotFound(PathBuf), - #[error("Failed to read file at {path}: {source}")] - ReadFile { - path: PathBuf, - #[source] - source: std::io::Error, - }, - #[error(transparent)] - CompileUtilError(#[from] ere_compile_utils::CompileError), -} - -#[derive(Debug, Error)] -pub enum ExecuteError { + // Execute #[error("SP1 execution failed: {0}")] - Client(#[source] Box), -} + Execute(#[source] anyhow::Error), -#[derive(Debug, Error)] -pub enum ProveError { + // Prove #[error("SP1 SDK proving failed: {0}")] - Client(#[source] Box), + Prove(#[source] anyhow::Error), #[error("SP1 proving panicked: {0}")] Panic(String), - #[error("Serialising proof with `bincode` failed: {0}")] - Bincode(#[from] bincode::error::EncodeError), -} - -#[derive(Debug, Error)] -pub enum VerifyError { - #[error("Deserialising proof failed: {0}")] - Bincode(#[from] bincode::error::DecodeError), - - #[error("Invalid proof kind, expected: {}, got: {}", 0.to_string(), 1.to_string() )] + // Verify + #[error("Invalid proof kind, expected: {0:?}, got: {1:?}")] InvalidProofKind(ProofKind, SP1ProofMode), #[error("SP1 SDK verification failed: {0}")] - Client(#[source] Box), + Verify(#[source] SP1VerificationError), } diff --git a/crates/zkvm/sp1/src/lib.rs b/crates/zkvm/sp1/src/lib.rs index b9a1a8b..99ad750 100644 --- a/crates/zkvm/sp1/src/lib.rs +++ b/crates/zkvm/sp1/src/lib.rs @@ -1,12 +1,10 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use crate::{ - compiler::SP1Program, - error::{ExecuteError, ProveError, SP1Error, VerifyError}, -}; +use crate::{compiler::SP1Program, error::SP1Error}; +use anyhow::bail; use ere_zkvm_interface::{ - NetworkProverConfig, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMError, + CommonError, NetworkProverConfig, ProgramExecutionReport, ProgramProvingReport, Proof, + ProofKind, ProverResourceType, PublicValues, zkVM, }; use sp1_sdk::{ CpuProver, CudaProver, NetworkProver, Prover, ProverClient, SP1ProofMode, @@ -47,9 +45,7 @@ impl ProverType { ProverType::Network(network_prover) => network_prover.execute(program, input), }; - cpu_executor_builder - .run() - .map_err(|e| SP1Error::Execute(ExecuteError::Client(e.into()))) + cpu_executor_builder.run().map_err(SP1Error::Execute) } fn prove( @@ -63,7 +59,7 @@ impl ProverType { ProverType::Gpu(cuda_prover) => cuda_prover.prove(pk, input).mode(mode).run(), ProverType::Network(network_prover) => network_prover.prove(pk, input).mode(mode).run(), } - .map_err(|e| SP1Error::Prove(ProveError::Client(e.into()))) + .map_err(SP1Error::Prove) } fn verify( @@ -76,7 +72,7 @@ impl ProverType { ProverType::Gpu(cuda_prover) => cuda_prover.verify(proof, vk), ProverType::Network(network_prover) => network_prover.verify(proof, vk), } - .map_err(|e| SP1Error::Verify(VerifyError::Client(e.into()))) + .map_err(SP1Error::Verify) } } @@ -131,20 +127,20 @@ impl EreSP1 { } } - pub fn new(program: SP1Program, resource: ProverResourceType) -> Self { + pub fn new(program: SP1Program, resource: ProverResourceType) -> Result { let (pk, vk) = Self::create_client(&resource).setup(&program); - Self { + Ok(Self { program, pk, vk, resource, - } + }) } } impl zkVM for EreSP1 { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let mut stdin = SP1Stdin::new(); stdin.write_slice(input); @@ -166,7 +162,7 @@ impl zkVM for EreSP1 { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { info!("Generating proof…"); let mut stdin = SP1Stdin::new(); @@ -179,17 +175,17 @@ impl zkVM for EreSP1 { let (proof, proving_time) = panic::catch_unwind(|| { let client = Self::create_client(&self.resource); - let start = std::time::Instant::now(); + let start = Instant::now(); let proof = client.prove(&self.pk, &stdin, mode)?; Ok::<_, SP1Error>((proof, start.elapsed())) }) - .map_err(|err| SP1Error::Prove(ProveError::Panic(panic_msg(err))))??; + .map_err(|err| SP1Error::Panic(panic_msg(err)))??; let public_values = proof.public_values.to_vec(); let proof = Proof::new( proof_kind, bincode::serde::encode_to_vec(&proof, bincode::config::legacy()) - .map_err(|err| SP1Error::Prove(ProveError::Bincode(err)))?, + .map_err(|err| CommonError::serialize("proof", "bincode", err))?, ); Ok(( @@ -199,14 +195,14 @@ impl zkVM for EreSP1 { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { info!("Verifying proof…"); let proof_kind = proof.kind(); let (proof, _): (SP1ProofWithPublicValues, _) = bincode::serde::decode_from_slice(proof.as_bytes(), bincode::config::legacy()) - .map_err(|err| SP1Error::Verify(VerifyError::Bincode(err)))?; + .map_err(|err| CommonError::deserialize("proof", "bincode", err))?; let inner_proof_kind = SP1ProofMode::from(&proof.proof); if !matches!( @@ -214,14 +210,11 @@ impl zkVM for EreSP1 { (ProofKind::Compressed, SP1ProofMode::Compressed) | (ProofKind::Groth16, SP1ProofMode::Groth16) ) { - return Err(SP1Error::Verify(VerifyError::InvalidProofKind( - proof_kind, - inner_proof_kind, - )))?; + bail!(SP1Error::InvalidProofKind(proof_kind, inner_proof_kind)); } let client = Self::create_client(&self.resource); - client.verify(&proof, &self.vk).map_err(zkVMError::from)?; + client.verify(&proof, &self.vk)?; let public_values_bytes = proof.public_values.as_slice().to_vec(); @@ -267,7 +260,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu); + let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_execute(&zkvm, &test_case); @@ -276,7 +269,7 @@ mod tests { #[test] fn test_execute_invalid_input() { let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu); + let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.execute(&input).unwrap_err(); @@ -286,7 +279,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu); + let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_prove(&zkvm, &test_case); @@ -295,7 +288,7 @@ mod tests { #[test] fn test_prove_invalid_input() { let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu); + let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.prove(&input, ProofKind::default()).unwrap_err(); @@ -317,7 +310,7 @@ mod tests { api_key: std::env::var("NETWORK_PRIVATE_KEY").ok(), }; let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Network(network_config)); + let zkvm = EreSP1::new(program, ProverResourceType::Network(network_config)).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_prove(&zkvm, &test_case); diff --git a/crates/zkvm/ziren/Cargo.toml b/crates/zkvm/ziren/Cargo.toml index 2453c51..aa917e4 100644 --- a/crates/zkvm/ziren/Cargo.toml +++ b/crates/zkvm/ziren/Cargo.toml @@ -6,16 +6,17 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true bincode = { workspace = true, features = ["std", "serde"] } thiserror.workspace = true tracing.workspace = true # Ziren dependencies -zkm-sdk = { workspace = true } +zkm-sdk.workspace = true # Local dependencies ere-compile-utils.workspace = true -ere-zkvm-interface = { workspace = true } +ere-zkvm-interface.workspace = true [dev-dependencies] ere-test-utils = { workspace = true, features = ["host"] } diff --git a/crates/zkvm/ziren/src/compiler/rust_mips32r2_customized.rs b/crates/zkvm/ziren/src/compiler/rust_mips32r2_customized.rs index cca1988..42776db 100644 --- a/crates/zkvm/ziren/src/compiler/rust_mips32r2_customized.rs +++ b/crates/zkvm/ziren/src/compiler/rust_mips32r2_customized.rs @@ -1,14 +1,7 @@ -use crate::{ - compiler::ZirenProgram, - error::{CompileError, ZirenError}, -}; -use ere_compile_utils::cargo_metadata; +use crate::{compiler::ZirenProgram, error::CompileError}; +use ere_compile_utils::{CommonError, cargo_metadata, rustc_path}; use ere_zkvm_interface::Compiler; -use std::{ - fs, - path::{Path, PathBuf}, - process::Command, -}; +use std::{fs, path::Path, process::Command}; const ZKM_TOOLCHAIN: &str = "zkm"; @@ -17,68 +10,42 @@ const ZKM_TOOLCHAIN: &str = "zkm"; pub struct RustMips32r2Customized; impl Compiler for RustMips32r2Customized { - type Error = ZirenError; + type Error = CompileError; type Program = ZirenProgram; fn compile(&self, guest_directory: &Path) -> Result { - let metadata = cargo_metadata(guest_directory).map_err(CompileError::CompileUtilError)?; + let metadata = cargo_metadata(guest_directory)?; let package = metadata.root_package().unwrap(); - let rustc = { - let output = Command::new("rustc") - .env("RUSTUP_TOOLCHAIN", ZKM_TOOLCHAIN) - .args(["--print", "sysroot"]) - .output() - .map_err(CompileError::RustcSysrootFailed)?; - - if !output.status.success() { - return Err(CompileError::RustcSysrootExitNonZero { - status: output.status, - stdout: String::from_utf8_lossy(&output.stdout).to_string(), - stderr: String::from_utf8_lossy(&output.stderr).to_string(), - })?; - } - - PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()) - .join("bin") - .join("rustc") - }; - // Use `cargo ziren build` instead of using crate `zkm-build`, because // it exits if the underlying `cargo build` fails, and there is no way // to recover. - let output = Command::new("cargo") + let mut cmd = Command::new("cargo"); + let output = cmd .current_dir(guest_directory) - .env("RUSTC", rustc) + .env("RUSTC", rustc_path(ZKM_TOOLCHAIN)?) .env("ZIREN_ZKM_CC", "mipsel-zkm-zkvm-elf-gcc") .args(["ziren", "build"]) .output() - .map_err(CompileError::CargoZirenBuildFailed)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { - return Err(CompileError::CargoZirenBuildExitNonZero { - status: output.status, - stdout: String::from_utf8_lossy(&output.stdout).to_string(), - stderr: String::from_utf8_lossy(&output.stderr).to_string(), - })?; + return Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))?; } - let elf_path = String::from_utf8_lossy(&output.stdout) - .lines() - .find_map(|line| { - let line = line.strip_prefix("cargo:rustc-env=ZKM_ELF_")?; - let (package_name, elf_path) = line.split_once("=")?; - (package_name == package.name).then(|| PathBuf::from(elf_path)) - }) - .ok_or_else(|| CompileError::GuestNotFound { - name: package.name.clone(), - })?; - - let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadFile { - path: elf_path, - source, - })?; + let elf_path = metadata + .target_directory + .join("elf-compilation") + .join("mipsel-zkm-zkvm-elf") + .join("release") + .join(&package.name); + let elf = + fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?; Ok(elf) } diff --git a/crates/zkvm/ziren/src/error.rs b/crates/zkvm/ziren/src/error.rs index 8c2586e..1b6519c 100644 --- a/crates/zkvm/ziren/src/error.rs +++ b/crates/zkvm/ziren/src/error.rs @@ -1,89 +1,30 @@ -use ere_zkvm_interface::{ProofKind, zkVMError}; -use std::{io, path::PathBuf, process::ExitStatus}; +use ere_zkvm_interface::ProofKind; use thiserror::Error; -use zkm_sdk::ZKMProofKind; +use zkm_sdk::{ZKMProofKind, ZKMVerificationError}; -impl From for zkVMError { - fn from(value: ZirenError) -> Self { - zkVMError::Other(Box::new(value)) - } +#[derive(Debug, Error)] +pub enum CompileError { + #[error(transparent)] + CommonError(#[from] ere_compile_utils::CommonError), } #[derive(Debug, Error)] pub enum ZirenError { - #[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("`RUSTUP_TOOLCHAIN=zkm rustc --print sysroot` failed to execute: {0}")] - RustcSysrootFailed(#[source] io::Error), - #[error( - "`RUSTUP_TOOLCHAIN=zkm rustc --print sysroot` exited with non-zero status {status}, stdout: {stdout}, stderr: {stderr}" - )] - RustcSysrootExitNonZero { - status: ExitStatus, - stdout: String, - stderr: String, - }, - #[error("`cargo ziren build` failed to execute: {0}")] - CargoZirenBuildFailed(#[source] io::Error), - #[error( - "`cargo ziren build` exited with non-zero status {status}, stdout: {stdout}, stderr: {stderr}" - )] - CargoZirenBuildExitNonZero { - status: ExitStatus, - stdout: String, - stderr: String, - }, - #[error("Failed to find guest in built packages")] - GuestNotFound { name: String }, - #[error("Failed to read file at {path}: {source}")] - ReadFile { - path: PathBuf, - #[source] - source: std::io::Error, - }, - #[error(transparent)] - CompileUtilError(#[from] ere_compile_utils::CompileError), -} - -#[derive(Debug, Error)] -pub enum ExecuteError { + // Execute #[error("Ziren execution failed: {0}")] - Client(#[source] Box), -} - -#[derive(Debug, Error)] -pub enum ProveError { - #[error("Serialising proof with `bincode` failed: {0}")] - Bincode(#[from] bincode::error::EncodeError), + Execute(#[source] anyhow::Error), + // Prove #[error("Ziren proving failed: {0}")] - Client(#[source] Box), + Prove(#[source] anyhow::Error), #[error("Ziren proving panicked: {0}")] - Panic(String), -} + ProvePanic(String), -#[derive(Debug, Error)] -pub enum VerifyError { - #[error("Deserialising proof with `bincode` failed: {0}")] - Bincode(#[from] bincode::error::DecodeError), - - #[error("Invalid proof kind, expected: {}, got: {}", 0.to_string(), 1.to_string() )] + // Verify + #[error("Invalid proof kind, expected: {0:?}, got: {1:?}")] InvalidProofKind(ProofKind, ZKMProofKind), #[error("Ziren verification failed: {0}")] - Client(#[source] Box), + Verify(#[source] ZKMVerificationError), } diff --git a/crates/zkvm/ziren/src/lib.rs b/crates/zkvm/ziren/src/lib.rs index 0b08916..7ff9921 100644 --- a/crates/zkvm/ziren/src/lib.rs +++ b/crates/zkvm/ziren/src/lib.rs @@ -1,12 +1,10 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use crate::{ - compiler::ZirenProgram, - error::{ExecuteError, ProveError, VerifyError, ZirenError}, -}; +use crate::{compiler::ZirenProgram, error::ZirenError}; +use anyhow::bail; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use std::{panic, time::Instant}; use tracing::info; @@ -27,7 +25,7 @@ pub struct EreZiren { } impl EreZiren { - pub fn new(program: ZirenProgram, resource: ProverResourceType) -> Self { + pub fn new(program: ZirenProgram, resource: ProverResourceType) -> Result { if matches!( resource, ProverResourceType::Gpu | ProverResourceType::Network(_) @@ -35,19 +33,19 @@ impl EreZiren { panic!("Network or Gpu proving not yet implemented for ZKM. Use CPU resource type."); } let (pk, vk) = CpuProver::new().setup(&program); - Self { program, pk, vk } + Ok(Self { program, pk, vk }) } } impl zkVM for EreZiren { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let mut stdin = ZKMStdin::new(); stdin.write_slice(input); let start = Instant::now(); let (public_inputs, exec_report) = CpuProver::new() .execute(&self.program, &stdin) - .map_err(|err| ZirenError::Execute(ExecuteError::Client(err.into())))?; + .map_err(ZirenError::Execute)?; let execution_duration = start.elapsed(); Ok(( @@ -64,7 +62,7 @@ impl zkVM for EreZiren { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { info!("Generating proof…"); let mut stdin = ZKMStdin::new(); @@ -78,15 +76,15 @@ impl zkVM for EreZiren { let start = std::time::Instant::now(); let proof = panic::catch_unwind(|| CpuProver::new().prove(&self.pk, stdin, inner_proof_kind)) - .map_err(|err| ZirenError::Prove(ProveError::Panic(panic_msg(err))))? - .map_err(|err| ZirenError::Prove(ProveError::Client(err.into())))?; + .map_err(|err| ZirenError::ProvePanic(panic_msg(err)))? + .map_err(ZirenError::Prove)?; let proving_time = start.elapsed(); let public_values = proof.public_values.to_vec(); let proof = Proof::new( proof_kind, bincode::serde::encode_to_vec(&proof, bincode::config::legacy()) - .map_err(|err| ZirenError::Prove(ProveError::Bincode(err)))?, + .map_err(|err| CommonError::serialize("proof", "bincode", err))?, ); Ok(( @@ -96,14 +94,14 @@ impl zkVM for EreZiren { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { info!("Verifying proof…"); let proof_kind = proof.kind(); let (proof, _): (ZKMProofWithPublicValues, _) = bincode::serde::decode_from_slice(proof.as_bytes(), bincode::config::legacy()) - .map_err(|err| ZirenError::Verify(VerifyError::Bincode(err)))?; + .map_err(|err| CommonError::deserialize("proof", "bincode", err))?; let inner_proof_kind = ZKMProofKind::from(&proof.proof); if !matches!( @@ -111,15 +109,12 @@ impl zkVM for EreZiren { (ProofKind::Compressed, ZKMProofKind::Compressed) | (ProofKind::Groth16, ZKMProofKind::Groth16) ) { - return Err(ZirenError::Verify(VerifyError::InvalidProofKind( - proof_kind, - inner_proof_kind, - )))?; + bail!(ZirenError::InvalidProofKind(proof_kind, inner_proof_kind)); } CpuProver::new() .verify(&proof, &self.vk) - .map_err(|err| ZirenError::Verify(VerifyError::Client(err.into())))?; + .map_err(ZirenError::Verify)?; Ok(proof.public_values.to_vec()) } @@ -163,7 +158,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreZiren::new(program, ProverResourceType::Cpu); + let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_execute(&zkvm, &test_case); @@ -172,7 +167,7 @@ mod tests { #[test] fn test_execute_invalid_input() { let program = basic_program(); - let zkvm = EreZiren::new(program, ProverResourceType::Cpu); + let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.execute(&input).unwrap_err(); @@ -182,7 +177,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreZiren::new(program, ProverResourceType::Cpu); + let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap(); let test_case = BasicProgramInput::valid(); run_zkvm_prove(&zkvm, &test_case); @@ -191,7 +186,7 @@ mod tests { #[test] fn test_prove_invalid_input() { let program = basic_program(); - let zkvm = EreZiren::new(program, ProverResourceType::Cpu); + let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap(); for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { zkvm.prove(&input, ProofKind::default()).unwrap_err(); diff --git a/crates/zkvm/zisk/Cargo.toml b/crates/zkvm/zisk/Cargo.toml index deb3c1e..01db299 100644 --- a/crates/zkvm/zisk/Cargo.toml +++ b/crates/zkvm/zisk/Cargo.toml @@ -6,7 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] -bincode = { workspace = true, features = ["alloc", "serde"] } +anyhow.workspace = true blake3.workspace = true bytemuck.workspace = true strum = { workspace = true, features = ["derive"] } diff --git a/crates/zkvm/zisk/src/client.rs b/crates/zkvm/zisk/src/client.rs index 484ee64..917484b 100644 --- a/crates/zkvm/zisk/src/client.rs +++ b/crates/zkvm/zisk/src/client.rs @@ -1,12 +1,12 @@ use crate::error::ZiskError; -use ere_zkvm_interface::{ProverResourceType, PublicValues}; +use ere_zkvm_interface::{CommonError, ProverResourceType, PublicValues}; use std::{ collections::BTreeMap, env, fs, io::BufRead, iter, path::{Path, PathBuf}, - process::{Child, Command, Stdio}, + process::{Child, Command}, sync::OnceLock, thread, time::{Duration, Instant}, @@ -151,17 +151,15 @@ impl ZiskSdk { // Save ELF to `~/.zisk/cache` along with the ROM binaries, to avoid it // been cleaned up during a long run process. let cache_dir_path = dot_zisk_dir_path().join("cache"); - fs::create_dir_all(&cache_dir_path)?; + fs::create_dir_all(&cache_dir_path) + .map_err(|err| CommonError::create_dir("cache", &cache_dir_path, err))?; // Use blake3 hash as the ELF file name, since ROM setup uses blake3 as // unique identifier as well. let elf_hash = blake3::hash(&elf); let elf_path = cache_dir_path.join(format!("{elf_hash}.elf")); - fs::write(&elf_path, elf).map_err(|source| ZiskError::WriteFile { - path: elf_path.clone(), - source, - })?; + fs::write(&elf_path, elf).map_err(|err| CommonError::write_file("elf", &elf_path, err))?; Ok(Self { elf_path, @@ -173,16 +171,15 @@ impl ZiskSdk { /// Execute the ELF with the given `input`. pub fn execute(&self, input: &[u8]) -> Result<(PublicValues, u64), ZiskError> { - let tempdir = tempdir().map_err(ZiskError::TempDir)?; + let tempdir = tempdir().map_err(CommonError::tempdir)?; let input_path = tempdir.path().join("input"); let output_path = tempdir.path().join("output"); - fs::write(&input_path, input).map_err(|source| ZiskError::WriteFile { - path: input_path.clone(), - source, - })?; + fs::write(&input_path, input) + .map_err(|err| CommonError::write_file("input", &input_path, err))?; - let output = Command::new("ziskemu") + let mut cmd = Command::new("ziskemu"); + let output = cmd .arg("--elf") .arg(&self.elf_path) .arg("--inputs") @@ -190,14 +187,15 @@ impl ZiskSdk { .arg("--output") .arg(&output_path) .arg("--stats") // Enable stats in order to get total steps. - .stderr(Stdio::inherit()) .output() - .map_err(ZiskError::Ziskemu)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { - return Err(ZiskError::ZiskemuFailed { - status: output.status, - }); + return Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))?; } // Extract cycle count from the stdout. @@ -212,10 +210,8 @@ impl ZiskSdk { }) .ok_or(ZiskError::TotalStepsNotFound)?; - let public_values = fs::read(&output_path).map_err(|source| ZiskError::ReadFile { - path: output_path, - source, - })?; + let public_values = fs::read(&output_path) + .map_err(|err| CommonError::read_file("output", &output_path, err))?; Ok((public_values, total_num_cycles)) } @@ -269,7 +265,7 @@ impl ZiskSdk { cmd.arg("--witness-lib").arg(witness_lib_path); } - let child = cmd.spawn().map_err(ZiskError::CargoZiskServer)?; + let child = cmd.spawn().map_err(|err| CommonError::command(&cmd, err))?; let server = ZiskServer { options: self.options.clone(), rom_digest, @@ -285,20 +281,19 @@ impl ZiskSdk { pub fn verify(&self, proof: &[u8]) -> Result { let rom_digest = self.rom_digest()?; - let tempdir = tempdir().map_err(ZiskError::TempDir)?; + let tempdir = tempdir().map_err(CommonError::tempdir)?; let proof_path = tempdir.path().join("proof"); - fs::write(&proof_path, proof).map_err(|source| ZiskError::WriteFile { - path: proof_path.clone(), - source, - })?; + fs::write(&proof_path, proof) + .map_err(|err| CommonError::write_file("proof", &proof_path, err))?; - let output = Command::new("cargo-zisk") + let mut cmd = Command::new("cargo-zisk"); + let output = cmd .arg("verify") .arg("--proof") .arg(&proof_path) .output() - .map_err(ZiskError::CargoZiskVerify)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { Err(ZiskError::InvalidProof( @@ -306,10 +301,8 @@ impl ZiskSdk { ))? } - let proof = fs::read(&proof_path).map_err(|source| ZiskError::ReadFile { - path: proof_path, - source, - })?; + let proof = fs::read(&proof_path) + .map_err(|err| CommonError::read_file("proof", &proof_path, err))?; // Deserialize public values. let (proved_rom_digest, public_values) = deserialize_public_values(&proof)?; @@ -356,25 +349,30 @@ impl Drop for ZiskServer { impl ZiskServer { /// Get status of server. pub fn status(&self) -> Result { - let output = Command::new("cargo-zisk") + let mut cmd = Command::new("cargo-zisk"); + let output = cmd .args(["prove-client", "status"]) .args(self.options.prove_client_args()) .output() - .map_err(ZiskError::CargoZiskStatus)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { - return Err(ZiskError::CargoZiskStatusFailed { - status: output.status, - }); + return Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))?; } - let output = String::from_utf8_lossy(&output.stdout); - if output.contains("idle") { + let stdout = String::from_utf8_lossy(&output.stdout); + if stdout.contains("idle") { Ok(ZiskServerStatus::Idle) - } else if output.contains("working") { + } else if stdout.contains("working") { Ok(ZiskServerStatus::Working) } else { - Err(ZiskError::UnknownServerStatus) + Err(ZiskError::UnknownServerStatus { + stdout: stdout.to_string(), + }) } } @@ -385,19 +383,18 @@ impl ZiskServer { // so there will be no conflict. const PREFIX: &str = "ere"; - let tempdir = tempdir().map_err(ZiskError::TempDir)?; + let tempdir = tempdir().map_err(CommonError::tempdir)?; let input_path = tempdir.path().join("input"); let output_path = tempdir.path().join("output"); let proof_path = output_path.join(format!("{PREFIX}-vadcop_final_proof.bin")); - fs::write(&input_path, input).map_err(|source| ZiskError::WriteFile { - path: input_path.clone(), - source, - })?; + fs::write(&input_path, input) + .map_err(|err| CommonError::write_file("input", &input_path, err))?; // NOTE: Use snake case for `prove-client` command // Issue for tracking: https://github.com/eth-act/ere/issues/151. - let status = Command::new("cargo-zisk") + let mut cmd = Command::new("cargo-zisk"); + let output = cmd .args(["prove-client", "prove"]) .arg("--input") .arg(input_path) @@ -406,11 +403,15 @@ impl ZiskServer { .args(["-p", PREFIX]) .args(["--aggregation", "--verify_proofs"]) .args(self.options.prove_args()) - .status() - .map_err(ZiskError::CargoZiskProve)?; + .output() + .map_err(|err| CommonError::command(&cmd, err))?; - if !status.success() { - return Err(ZiskError::CargoZiskProveFailed { status }); + if !output.status.success() { + return Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))?; } // ZisK server will finish the `prove` requested above then respond the @@ -418,10 +419,8 @@ impl ZiskServer { // should also be ready. self.status()?; - let proof = fs::read(&proof_path).map_err(|source| ZiskError::ReadFile { - path: proof_path, - source, - })?; + let proof = fs::read(&proof_path) + .map_err(|err| CommonError::read_file("proof", &proof_path, err))?; // Deserialize public values. let (proved_rom_digest, public_values) = deserialize_public_values(&proof)?; @@ -458,13 +457,18 @@ impl ZiskServer { fn check_setup() -> Result<(), ZiskError> { info!("Running command `cargo-zisk check-setup --aggregation`..."); - let status = Command::new("cargo-zisk") + let mut cmd = Command::new("cargo-zisk"); + let output = cmd .args(["check-setup", "--aggregation"]) - .status() - .map_err(ZiskError::CargoZiskCheckSetup)?; + .output() + .map_err(|err| CommonError::command(&cmd, err))?; - if !status.success() { - return Err(ZiskError::CargoZiskCheckSetupFailed { status }); + if !output.status.success() { + Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))?; } info!("Command `cargo-zisk check-setup --aggregation` succeeded"); @@ -476,18 +480,20 @@ fn check_setup() -> Result<(), ZiskError> { fn rom_setup(elf_path: &Path) -> Result { info!("Running command `cargo-zisk rom-setup` ..."); - let output = Command::new("cargo-zisk") + let mut cmd = Command::new("cargo-zisk"); + let output = cmd .arg("rom-setup") .arg("--elf") .arg(elf_path) - .stderr(Stdio::inherit()) .output() - .map_err(ZiskError::CargoZiskRomSetup)?; + .map_err(|err| CommonError::command(&cmd, err))?; if !output.status.success() { - return Err(ZiskError::CargoZiskRomSetupFailed { - status: output.status, - }); + Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))?; } // Parse the ROM digest from the stdout. diff --git a/crates/zkvm/zisk/src/compiler/rust_rv64ima_customized.rs b/crates/zkvm/zisk/src/compiler/rust_rv64ima_customized.rs index fd1544c..3c41727 100644 --- a/crates/zkvm/zisk/src/compiler/rust_rv64ima_customized.rs +++ b/crates/zkvm/zisk/src/compiler/rust_rv64ima_customized.rs @@ -1,11 +1,7 @@ -use crate::error::ZiskError; -use ere_compile_utils::cargo_metadata; +use crate::error::CompileError; +use ere_compile_utils::{CommonError, cargo_metadata, rustc_path}; use ere_zkvm_interface::Compiler; -use std::{ - fs, - path::{Path, PathBuf}, - process::Command, -}; +use std::{fs, path::Path, process::Command}; use tracing::info; const ZISK_TOOLCHAIN: &str = "zisk"; @@ -16,7 +12,7 @@ const ZISK_TARGET: &str = "riscv64ima-zisk-zkvm-elf"; pub struct RustRv64imaCustomized; impl Compiler for RustRv64imaCustomized { - type Error = ZiskError; + type Error = CompileError; type Program = Vec; @@ -24,68 +20,31 @@ impl Compiler for RustRv64imaCustomized { info!("Compiling ZisK program at {}", guest_directory.display()); let metadata = cargo_metadata(guest_directory)?; - let package_name = &metadata.root_package().unwrap().name; + let package = metadata.root_package().unwrap(); - info!("Parsed program name: {package_name}"); + info!("Parsed program name: {}", package.name); - // ── build ───────────────────────────────────────────────────────────── - // Get the path to ZisK toolchain's `rustc` so we could set the env - // `RUSTC=...` for `cargo` instead of using `cargo +zisk ...`. - let zisk_rustc = { - let output = Command::new("rustc") - .env("RUSTUP_TOOLCHAIN", ZISK_TOOLCHAIN) - .arg("--print") - .arg("sysroot") - .output() - .map_err(ZiskError::RustcSysroot)?; - PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()) - .join("bin") - .join("rustc") - }; - - let status = Command::new("cargo") - .current_dir(guest_directory) - .env("RUSTC", zisk_rustc) - .args(["build", "--release", "--target", ZISK_TARGET]) + let mut cmd = Command::new("cargo"); + let status = cmd + .env("RUSTC", rustc_path(ZISK_TOOLCHAIN)?) + .args(["build", "--release"]) + .args(["--target", ZISK_TARGET]) + .arg("--manifest-path") + .arg(&package.manifest_path) .status() - .map_err(|e| ZiskError::CargoBuild { - cwd: guest_directory.to_path_buf(), - source: e, - })?; + .map_err(|err| CommonError::command(&cmd, err))?; if !status.success() { - return Err(ZiskError::CargoBuildFailed { - status, - path: guest_directory.to_path_buf(), - }); + return Err(CommonError::command_exit_non_zero(&cmd, status, None))?; } - // Get the workspace directory. - let program_workspace_path = { - let output = Command::new("cargo") - .current_dir(guest_directory) - .arg("locate-project") - .arg("--workspace") - .arg("--message-format=plain") - .output() - .map_err(ZiskError::CargoLocateProject)?; - PathBuf::from( - String::from_utf8_lossy(&output.stdout) - .trim() - .strip_suffix("Cargo.toml") - .expect("location to be path to Cargo.toml"), - ) - }; - - let elf_path = program_workspace_path - .join("target") + let elf_path = metadata + .target_directory .join("riscv64ima-zisk-zkvm-elf") .join("release") - .join(package_name); - let elf_bytes = fs::read(&elf_path).map_err(|e| ZiskError::ReadFile { - path: elf_path.clone(), - source: e, - })?; + .join(&package.name); + let elf_bytes = + fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", elf_path, err))?; Ok(elf_bytes) } diff --git a/crates/zkvm/zisk/src/error.rs b/crates/zkvm/zisk/src/error.rs index 820b6d3..2ac9f03 100644 --- a/crates/zkvm/zisk/src/error.rs +++ b/crates/zkvm/zisk/src/error.rs @@ -1,112 +1,52 @@ use crate::client::RomDigest; use bytemuck::PodCastError; -use ere_zkvm_interface::zkVMError; -use std::{io, path::PathBuf, process::ExitStatus}; use thiserror::Error; -impl From for zkVMError { - fn from(value: ZiskError) -> Self { - zkVMError::Other(Box::new(value)) - } +#[derive(Debug, Error)] +pub enum CompileError { + #[error(transparent)] + CommonError(#[from] ere_compile_utils::CommonError), } #[derive(Debug, Error)] pub enum ZiskError { - // IO and file system - #[error("IO failure: {0}")] - Io(#[from] io::Error), - #[error("IO failure in temporary directory: {0}")] - TempDir(io::Error), - #[error("Failed to read file at {path}: {source}")] - ReadFile { - path: PathBuf, - #[source] - source: io::Error, - }, - #[error("Failed to write file at {path}: {source}")] - WriteFile { - path: PathBuf, - #[source] - source: io::Error, - }, - - // Compilation - #[error("Failed to execute `RUSTUP_TOOLCHAIN=zisk rustc --print sysroot`")] - RustcSysroot(#[source] io::Error), - #[error("Failed to execute `cargo locate-project --workspace --message-format=plain`")] - CargoLocateProject(#[source] io::Error), - #[error("Failed to execute `RUSTC=$ZISK_RUSTC cargo build --release ...` in {cwd}: {source}")] - CargoBuild { - cwd: PathBuf, - #[source] - source: io::Error, - }, - #[error( - "`RUSTC=$ZISK_RUSTC cargo build --release ...` failed with status: {status} for program at {path}" - )] - CargoBuildFailed { status: ExitStatus, path: PathBuf }, #[error(transparent)] - CompileUtilError(#[from] ere_compile_utils::CompileError), - - // Serialization - #[error("Bincode encode failed: {0}")] - BincodeEncode(#[from] bincode::error::EncodeError), - #[error("Bincode decode failed: {0}")] - BincodeDecode(#[from] bincode::error::DecodeError), + CommonError(#[from] ere_zkvm_interface::CommonError), // Execution - #[error("Failed to execute `ziskemu`: {0}")] - Ziskemu(#[source] io::Error), - #[error("`ziskemu` failed with status: {status}")] - ZiskemuFailed { status: ExitStatus }, #[error("Total steps not found in execution report")] TotalStepsNotFound, - // Check setup - #[error("Failed to execute `cargo-zisk check-setup`: {0}")] - CargoZiskCheckSetup(#[source] io::Error), - #[error("`cargo-zisk check-setup` failed with status: {status}")] - CargoZiskCheckSetupFailed { status: ExitStatus }, - // Rom setup - #[error("Failed to execute `cargo-zisk rom-setup`: {0}")] - CargoZiskRomSetup(#[source] io::Error), - #[error("`cargo-zisk rom-setup` failed with status: {status}")] - CargoZiskRomSetupFailed { status: ExitStatus }, #[error("Failed to find ROM digest in output")] RomDigestNotFound, + #[error("`cargo-zisk rom-setup` failed in another thread")] RomSetupFailedBefore, // Prove #[error("Mutex of ZiskServer is poisoned")] MutexPoisoned, - #[error("Failed to execute `cargo-zisk server`: {0}")] - CargoZiskServer(#[source] io::Error), + #[error("Timeout waiting for server ready")] TimeoutWaitingServerReady, - #[error("Failed to execute `cargo-zisk prove-client status`: {0}")] - CargoZiskStatus(#[source] io::Error), - #[error("`cargo-zisk prove-client status` failed with status: {status}")] - CargoZiskStatusFailed { status: ExitStatus }, - #[error("Uknown server status")] - UnknownServerStatus, - #[error("Failed to execute `cargo-zisk prove-client prove`: {0}")] - CargoZiskProve(#[source] io::Error), - #[error("`cargo-zisk prove-client prove` failed with status: {status}")] - CargoZiskProveFailed { status: ExitStatus }, + + #[error("Uknown server status, stdout: {stdout}")] + UnknownServerStatus { stdout: String }, // Verify - #[error("Failed to execute `cargo-zisk verify`: {0}")] - CargoZiskVerify(#[source] io::Error), #[error("Invalid proof: {0}")] InvalidProof(String), + #[error("Cast proof to `u64` slice failed: {0}")] CastProofBytesToU64s(PodCastError), + #[error("Invalid public value format")] InvalidPublicValue, + #[error("Public values length {0}, but expected at least 6")] InvalidPublicValuesLength(usize), + #[error("Unexpected ROM digest - preprocessed: {preprocessed:?}, proved: {proved:?}")] UnexpectedRomDigest { preprocessed: RomDigest, diff --git a/crates/zkvm/zisk/src/lib.rs b/crates/zkvm/zisk/src/lib.rs index c3bca47..c737bea 100644 --- a/crates/zkvm/zisk/src/lib.rs +++ b/crates/zkvm/zisk/src/lib.rs @@ -5,9 +5,10 @@ use crate::{ compiler::ZiskProgram, error::ZiskError, }; +use anyhow::bail; use ere_zkvm_interface::{ - ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, - PublicValues, zkVM, zkVMError, + CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResourceType, PublicValues, zkVM, }; use std::{ sync::{Mutex, MutexGuard}, @@ -30,7 +31,7 @@ pub struct EreZisk { } impl EreZisk { - pub fn new(elf: ZiskProgram, resource: ProverResourceType) -> Result { + pub fn new(elf: ZiskProgram, resource: ProverResourceType) -> Result { if matches!(resource, ProverResourceType::Network(_)) { panic!("Network proving not yet implemented for ZisK. Use CPU or GPU resource type."); } @@ -63,7 +64,7 @@ impl EreZisk { } impl zkVM for EreZisk { - fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { let start = Instant::now(); let (public_values, total_num_cycles) = self.sdk.execute(input)?; let execution_duration = start.elapsed(); @@ -82,9 +83,12 @@ impl zkVM for EreZisk { &self, input: &[u8], proof_kind: ProofKind, - ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if proof_kind != ProofKind::Compressed { - panic!("Only Compressed proof kind is supported."); + bail!(CommonError::unsupported_proof_kind( + proof_kind, + [ProofKind::Compressed] + )) } let mut server = self.server()?; @@ -101,9 +105,12 @@ impl zkVM for EreZisk { )) } - fn verify(&self, proof: &Proof) -> Result { + fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - return Err(zkVMError::other("Only Compressed proof kind is supported.")); + bail!(CommonError::unsupported_proof_kind( + proof.kind(), + [ProofKind::Compressed] + )) }; Ok(self.sdk.verify(proof)?)