Refactor zkVM error handling (#179)

This commit is contained in:
Han
2025-10-28 08:24:27 +08:00
committed by GitHub
parent 5c342eec44
commit 0d0bb451ff
63 changed files with 1138 additions and 1418 deletions

15
Cargo.lock generated
View File

@@ -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",

View File

@@ -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" }

View File

@@ -92,7 +92,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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();

View File

@@ -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

View File

@@ -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<u8>,
stderr: Vec<u8>,
},
#[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<str>, 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<str>, path: impl AsRef<Path>, 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<str>, path: impl AsRef<Path>, 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<str>, path: impl AsRef<Path>, 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<str>,
lib: impl AsRef<str>,
err: impl Into<anyhow::Error>,
) -> 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 }
}
}

View File

@@ -2,6 +2,6 @@ mod error;
mod rust;
pub use {
error::CompileError,
rust::{CargoBuildCmd, cargo_metadata},
error::CommonError,
rust::{CargoBuildCmd, cargo_metadata, rustc_path},
};

View File

@@ -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<Path>,
target: impl AsRef<str>,
) -> Result<Vec<u8>, CompileError> {
) -> Result<Vec<u8>, 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<Path>) -> Result<Metadata, CompileError> {
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<Path>) -> Result<Metadata, CommonError> {
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<PathBuf, CommonError> {
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"),
)
}

View File

@@ -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

View File

@@ -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<DockerizedError> for zkVMError {
fn from(value: DockerizedError) -> Self {
zkVMError::Other(Box::new(value))
}
}
impl From<zkVMClientError> for DockerizedError {
fn from(value: zkVMClientError) -> Self {
match value {

View File

@@ -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<Self, zkVMError> {
) -> Result<Self, DockerizedError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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::<DockerizedError>().unwrap(),
DockerizedError::zkVM(_)
),
"Unexpected err: {err:?}"
);
let err = zkvm.execute(&input).unwrap_err();
assert!(matches!(
err.downcast::<DockerizedError>().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::<DockerizedError>().unwrap(),
DockerizedError::zkVM(_)
),
"Unexpected err: {err:?}"
);
let err = zkvm.prove(&input, ProofKind::default()).unwrap_err();
assert!(matches!(
err.downcast::<DockerizedError>().unwrap(),
DockerizedError::zkVM(_)
),);
}
drop(zkvm);

View File

@@ -113,7 +113,7 @@ fn construct_zkvm(program: Vec<u8>, resource: ProverResourceType) -> Result<impl
.with_context(|| "Failed to deserialize program")?;
#[cfg(feature = "airbender")]
let zkvm = Ok::<_, Error>(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<u8>, resource: ProverResourceType) -> Result<impl
let zkvm = ere_miden::EreMiden::new(program, resource);
#[cfg(feature = "nexus")]
let zkvm = Ok::<_, Error>(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);

View File

@@ -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

View File

@@ -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"] }

View File

@@ -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<u8>,
stderr: Vec<u8>,
},
#[error("Unsupported proof kind {unsupported:?}, expect one of {supported:?}")]
UnsupportedProofKind {
unsupported: ProofKind,
supported: Vec<ProofKind>,
},
}
impl CommonError {
pub fn io(ctx: impl AsRef<str>, 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<str>, path: impl AsRef<Path>) -> 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<str>, path: impl AsRef<Path>, 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<str>, path: impl AsRef<Path>, 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<str>, path: impl AsRef<Path>, 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<str>,
lib: impl AsRef<str>,
err: impl Into<anyhow::Error>,
) -> 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<str>,
lib: impl AsRef<str>,
err: impl Into<anyhow::Error>,
) -> 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<Item = ProofKind>,
) -> Self {
Self::UnsupportedProofKind {
unsupported,
supported: supported.into_iter().collect(),
}
}
}

View File

@@ -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<dyn zkVM>
#[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<dyn std::error::Error + Send + Sync + 'static>),
}
impl zkVMError {
pub fn other(error: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>) -> 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<PublicValues, zkVMError>;
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues>;
/// Returns the name of the zkVM
fn name(&self) -> &'static str;

View File

@@ -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

View File

@@ -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)?;

View File

@@ -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<Vec<u8>, AirbenderError> {
let mut child = Command::new("rust-objcopy")
fn objcopy_binary(elf: &[u8]) -> Result<Vec<u8>, 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)

View File

@@ -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<AirbenderError> 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<String>) -> Self {
Self::Io(io::Error::other(format!("{}: {}", context.into(), err)))
}
pub fn create_dir(err: io::Error, id: &str, path: impl AsRef<Path>) -> 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<Path>) -> 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<Path>) -> Self {
let ctx = format!("Failed to read {id} from {}", path.as_ref().display());
Self::io(err, ctx)
}
}

View File

@@ -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<Self, AirbenderError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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();

View File

@@ -6,6 +6,7 @@ rust-version.workspace = true
license.workspace = true
[dependencies]
anyhow.workspace = true
tempfile.workspace = true
thiserror.workspace = true

View File

@@ -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)
}
}

View File

@@ -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<Self::Program, Self::Error> {
// 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)
}

View File

@@ -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<JoltError> 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),
}

View File

@@ -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,

View File

@@ -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<Self, zkVMError> {
pub fn new(elf: JoltProgram, resource: ProverResourceType) -> Result<Self, JoltError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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);

View File

@@ -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

View File

@@ -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<Self::Program, Self::Error> {
@@ -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))
}

View File

@@ -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<MidenError> 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),
}

View File

@@ -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<u8>,
stack_outputs: Vec<u8>,
proof: Vec<u8>,
}
/// [`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<Self, MidenError> {
Ok(Self { program: program.0 })
pub fn new(program: MidenProgram, resource: ProverResourceType) -> Result<Self, MidenError> {
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<DefaultHost, MidenError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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<u8> {
}
/// Convert bytes into Miden field elements.
pub fn bytes_to_felts(bytes: &[u8]) -> Result<Vec<Felt>, String> {
pub fn bytes_to_felts(bytes: &[u8]) -> Result<Vec<Felt>, 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::<Result<Vec<Felt>, _>>()
.map_err(|err| err.to_string())
.map_err(|err| CommonError::serialize("input", "miden", anyhow::anyhow!(err)))?)
}
#[cfg(test)]

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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<NexusError> 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<dyn std::error::Error + Send + Sync + 'static>),
/// 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<dyn std::error::Error + Send + Sync + 'static>),
#[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<dyn std::error::Error + Send + Sync + 'static>),
#[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),
}

View File

@@ -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<Self, NexusError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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();

View File

@@ -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

View File

@@ -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<Path>,
) -> Result<Self, CompileError> {
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 {

View File

@@ -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"))
}
}

View File

@@ -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"))
}
}

View File

@@ -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<OpenVMError> 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<CommonError> 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<dyn std::error::Error + Send + Sync + 'static>),
#[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<dyn std::error::Error + Send + Sync + 'static>),
ElfDecode(eyre::Error),
#[error("Transpile elf failed: {0}")]
Transpile(SdkError),
#[error("Read aggregation key failed: {0}")]
ReadAggKeyFailed(Box<dyn std::error::Error + Send + Sync + 'static>),
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<AppExecutionCommit>,
proved: Box<AppExecutionCommit>,
},
// Verify
#[error("OpenVM verification failed: {0}")]
Verify(#[source] SdkError),
}

View File

@@ -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<Self, zkVMError> {
pub fn new(program: OpenVMProgram, resource: ProverResourceType) -> Result<Self, OpenVMError> {
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::<AggProvingKey, _>(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<CpuSdk, CommonError> {
fn cpu_sdk(&self) -> Result<CpuSdk, OpenVMError> {
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<openvm_sdk::GpuSdk, CommonError> {
fn gpu_sdk(&self) -> Result<openvm_sdk::GpuSdk, OpenVMError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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::<SC>::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<u8>`
/// then converted to field elements `Vec<F>`, so here we try to downcast it.
fn extract_public_values(user_public_values: &[F]) -> Result<Vec<u8>, CommonError> {
fn extract_public_values(user_public_values: &[F]) -> Result<Vec<u8>, OpenVMError> {
user_public_values
.iter()
.map(|v| u8::try_from(v.as_canonical_u32()).ok())
.collect::<Option<_>>()
.ok_or(CommonError::InvalidPublicValue)
.ok_or(OpenVMError::InvalidPublicValue)
}
fn agg_pk_path() -> PathBuf {

View File

@@ -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();
}

View File

@@ -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<u8>;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
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)
}

View File

@@ -1,85 +1,40 @@
use ere_zkvm_interface::zkVMError;
use std::{io, path::PathBuf, process::ExitStatus};
use thiserror::Error;
impl From<PicoError> 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] },
}

View File

@@ -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<Self, PicoError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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::<Result<Vec<_>, _>>()
.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();

View File

@@ -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

View File

@@ -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

View File

@@ -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<Self::Program, Self::Error> {
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()

View File

@@ -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),
}

View File

@@ -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<Self, zkVMError> {
pub fn new(program: Risc0Program, resource: ProverResourceType) -> Result<Self, Risc0Error> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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();

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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<Self::Program, Self::Error> {
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)

View File

@@ -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<SP1Error> 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<dyn std::error::Error + Send + Sync + 'static>),
}
Execute(#[source] anyhow::Error),
#[derive(Debug, Error)]
pub enum ProveError {
// Prove
#[error("SP1 SDK proving failed: {0}")]
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
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<dyn std::error::Error + Send + Sync + 'static>),
Verify(#[source] SP1VerificationError),
}

View File

@@ -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<Self, SP1Error> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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);

View File

@@ -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"] }

View File

@@ -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<Self::Program, Self::Error> {
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)
}

View File

@@ -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<ZirenError> 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<dyn std::error::Error + Send + Sync + 'static>),
}
#[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<dyn std::error::Error + Send + Sync + 'static>),
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<dyn std::error::Error + Send + Sync + 'static>),
Verify(#[source] ZKMVerificationError),
}

View File

@@ -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<Self, ZirenError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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();

View File

@@ -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"] }

View File

@@ -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<PublicValues, ZiskError> {
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<ZiskServerStatus, ZiskError> {
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<RomDigest, ZiskError> {
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.

View File

@@ -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<u8>;
@@ -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)
}

View File

@@ -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<ZiskError> 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,

View File

@@ -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<Self, zkVMError> {
pub fn new(elf: ZiskProgram, resource: ProverResourceType) -> Result<Self, ZiskError> {
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<PublicValues, zkVMError> {
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
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)?)