Refactor mods to separate compiler and zkvm (#184)

This commit is contained in:
Han
2025-10-30 18:54:08 +08:00
committed by GitHub
parent 9957836858
commit ffc2fccaea
109 changed files with 3410 additions and 3019 deletions

5
Cargo.lock generated
View File

@@ -3737,6 +3737,7 @@ dependencies = [
"ere-test-utils",
"ere-zkvm-interface",
"execution_utils",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.12",
@@ -3820,6 +3821,7 @@ dependencies = [
"ere-zkvm-interface",
"jolt-core",
"jolt-sdk",
"serde",
"tempfile",
"thiserror 2.0.12",
]
@@ -3960,6 +3962,7 @@ dependencies = [
"ere-compile-utils",
"ere-test-utils",
"ere-zkvm-interface",
"serde",
"sp1-sdk",
"tempfile",
"thiserror 2.0.12",
@@ -3987,6 +3990,7 @@ dependencies = [
"ere-compile-utils",
"ere-test-utils",
"ere-zkvm-interface",
"serde",
"thiserror 2.0.12",
"tracing",
"zkm-sdk",
@@ -4003,6 +4007,7 @@ dependencies = [
"ere-compile-utils",
"ere-test-utils",
"ere-zkvm-interface",
"serde",
"strum 0.27.2",
"tempfile",
"thiserror 2.0.12",

View File

@@ -82,7 +82,10 @@ ere-sp1 = { git = "https://github.com/eth-act/ere.git", tag = "v0.0.12" }
```rust
// main.rs
use ere_sp1::{EreSP1, RV32_IM_SUCCINCT_ZKVM_ELF};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let guest_directory = std::path::Path::new("workspace/guest");
@@ -128,7 +131,10 @@ ere-dockerized = { git = "https://github.com/eth-act/ere.git", tag = "v0.0.12" }
```rust
// main.rs
use ere_dockerized::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let guest_directory = std::path::Path::new("workspace/guest");

View File

@@ -13,16 +13,16 @@ serde.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
# Local dependencies
ere-airbender = { workspace = true, optional = true }
ere-jolt = { workspace = true, optional = true }
ere-miden = { workspace = true, optional = true }
ere-nexus = { workspace = true, optional = true }
ere-openvm = { workspace = true, optional = true }
ere-pico = { workspace = true, optional = true }
ere-risc0 = { workspace = true, optional = true }
ere-sp1 = { workspace = true, optional = true }
ere-ziren = { workspace = true, optional = true }
ere-zisk = { workspace = true, optional = true }
ere-airbender = { workspace = true, features = ["compiler"], optional = true }
ere-jolt = { workspace = true, features = ["compiler"], optional = true }
ere-miden = { workspace = true, features = ["compiler"], optional = true }
ere-nexus = { workspace = true, features = ["compiler"], optional = true }
ere-openvm = { workspace = true, features = ["compiler"], optional = true }
ere-pico = { workspace = true, features = ["compiler"], optional = true }
ere-risc0 = { workspace = true, features = ["compiler"], optional = true }
ere-sp1 = { workspace = true, features = ["compiler"], optional = true }
ere-ziren = { workspace = true, features = ["compiler"], optional = true }
ere-zisk = { workspace = true, features = ["compiler"], optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]

View File

@@ -1,6 +1,6 @@
use anyhow::{Context, Error};
use clap::Parser;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use serde::Serialize;
use std::{env, fs::File, path::PathBuf};
use tracing_subscriber::EnvFilter;

View File

@@ -1,4 +1,4 @@
use crate::error::DockerizedError;
use crate::error::Error;
use std::{
env,
fmt::{self, Display, Formatter},
@@ -213,26 +213,27 @@ impl DockerRunCmd {
}
}
pub fn stop_docker_container(container_name: impl AsRef<str>) -> Result<(), DockerizedError> {
pub fn stop_docker_container(container_name: impl AsRef<str>) -> Result<(), Error> {
let output = Command::new("docker")
.args(["container", "stop", container_name.as_ref()])
.output()
.map_err(DockerizedError::DockerContainerCmd)?;
.map_err(Error::DockerContainerCmd)?;
if String::from_utf8_lossy(&output.stdout).starts_with("Error") {
return Err(DockerizedError::DockerContainerCmd(io::Error::other(
format!("Failed to stop container {}", container_name.as_ref()),
)));
return Err(Error::DockerContainerCmd(io::Error::other(format!(
"Failed to stop container {}",
container_name.as_ref()
))));
}
Ok(())
}
pub fn docker_image_exists(image: impl AsRef<str>) -> Result<bool, DockerizedError> {
pub fn docker_image_exists(image: impl AsRef<str>) -> Result<bool, Error> {
let output = Command::new("docker")
.args(["images", "--quiet", image.as_ref()])
.output()
.map_err(DockerizedError::DockerImageCmd)?;
.map_err(Error::DockerImageCmd)?;
// If image exists, image id will be printed hence stdout will be non-empty.
Ok(!output.stdout.is_empty())
}

View File

@@ -1,20 +1,20 @@
use ere_server::client::{TwirpErrorResponse, zkVMClientError};
use ere_server::client::{self, TwirpErrorResponse};
use std::{io, path::PathBuf};
use thiserror::Error;
impl From<zkVMClientError> for DockerizedError {
fn from(value: zkVMClientError) -> Self {
impl From<client::Error> for Error {
fn from(value: client::Error) -> Self {
match value {
zkVMClientError::zkVM(err) => DockerizedError::zkVM(err),
zkVMClientError::ConnectionTimeout => DockerizedError::ConnectionTimeout,
zkVMClientError::Rpc(err) => DockerizedError::Rpc(err),
client::Error::zkVM(err) => Self::zkVM(err),
client::Error::ConnectionTimeout => Self::ConnectionTimeout,
client::Error::Rpc(err) => Self::Rpc(err),
}
}
}
#[derive(Debug, Error)]
#[allow(non_camel_case_types)]
pub enum DockerizedError {
pub enum Error {
#[error(
"Guest directory must be in mounting directory, mounting_directory: {mounting_directory}, guest_directory: {guest_directory}"
)]
@@ -44,7 +44,7 @@ pub enum DockerizedError {
Rpc(TwirpErrorResponse),
}
impl DockerizedError {
impl Error {
pub fn io(source: io::Error, context: impl ToString) -> Self {
Self::Io {
source,

View File

@@ -27,7 +27,10 @@
//! ```rust,no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ere_dockerized::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM};
//! use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
//! use ere_zkvm_interface::{
//! compiler::Compiler,
//! zkvm::{ProofKind, ProverResourceType, zkVM},
//! };
//! use std::path::Path;
//!
//! // The zkVM we plan to use
@@ -65,12 +68,14 @@
use crate::{
cuda::cuda_arch,
docker::{DockerBuildCmd, DockerRunCmd, docker_image_exists, stop_docker_container},
error::DockerizedError,
};
use ere_server::client::{Url, zkVMClient};
use ere_zkvm_interface::{
Compiler, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM,
compiler::Compiler,
zkvm::{
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM,
},
};
use serde::{Deserialize, Serialize};
use std::{
@@ -83,13 +88,15 @@ use std::{
use tempfile::TempDir;
use tracing::error;
mod cuda;
mod docker;
mod error;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/crate_version.rs"));
include!(concat!(env!("OUT_DIR"), "/zkvm_sdk_version_impl.rs"));
pub mod cuda;
pub mod docker;
pub mod error;
/// Offset of port used for `ere-server` for [`ErezkVM`]s.
const ERE_SERVER_PORT_OFFSET: u16 = 4174;
@@ -163,7 +170,7 @@ impl ErezkVM {
///
/// Images are cached and only rebuilt if they don't exist or if the
/// `ERE_FORCE_REBUILD_DOCKER_IMAGE` environment variable is set.
pub fn build_docker_image(&self, gpu: bool) -> Result<(), DockerizedError> {
pub fn build_docker_image(&self, gpu: bool) -> Result<(), Error> {
let workspace_dir = workspace_dir();
let docker_dir = workspace_dir.join("docker");
@@ -180,8 +187,7 @@ impl ErezkVM {
cmd = cmd.build_arg("CUDA", "1");
}
cmd.exec(&workspace_dir)
.map_err(DockerizedError::DockerBuildCmd)?;
cmd.exec(&workspace_dir).map_err(Error::DockerBuildCmd)?;
}
// Build `ere-base-{zkvm}`
@@ -206,8 +212,7 @@ impl ErezkVM {
}
}
cmd.exec(&workspace_dir)
.map_err(DockerizedError::DockerBuildCmd)?;
cmd.exec(&workspace_dir).map_err(Error::DockerBuildCmd)?;
}
// Build `ere-compiler-{zkvm}`
@@ -218,7 +223,7 @@ impl ErezkVM {
.tag(self.compiler_zkvm_image("latest"))
.build_arg("BASE_ZKVM_IMAGE", self.base_zkvm_image(CRATE_VERSION, gpu))
.exec(&workspace_dir)
.map_err(DockerizedError::DockerBuildCmd)?;
.map_err(Error::DockerBuildCmd)?;
}
// Build `ere-server-{zkvm}`
@@ -233,8 +238,7 @@ impl ErezkVM {
cmd = cmd.build_arg("CUDA", "1");
}
cmd.exec(&workspace_dir)
.map_err(DockerizedError::DockerBuildCmd)?;
cmd.exec(&workspace_dir).map_err(Error::DockerBuildCmd)?;
}
Ok(())
@@ -248,7 +252,7 @@ impl ErezkVM {
&self,
program: &SerializedProgram,
resource: &ProverResourceType,
) -> Result<ServerContainer, DockerizedError> {
) -> Result<ServerContainer, Error> {
let port = self.server_port().to_string();
let name = format!("ere-server-{self}-{port}");
let gpu = matches!(resource, ProverResourceType::Gpu);
@@ -296,8 +300,8 @@ impl ErezkVM {
}
}
let tempdir = TempDir::new()
.map_err(|err| DockerizedError::io(err, "Failed to create temporary directory"))?;
let tempdir =
TempDir::new().map_err(|err| Error::io(err, "Failed to create temporary directory"))?;
// zkVM specific options needed for proving Groth16 proof.
cmd = match self {
@@ -327,8 +331,7 @@ impl ErezkVM {
let args = iter::empty()
.chain(["--port", &port])
.chain(resource.to_args());
cmd.spawn(args, &program.0)
.map_err(DockerizedError::DockerRunCmd)?;
cmd.spawn(args, &program.0).map_err(Error::DockerRunCmd)?;
Ok(ServerContainer { name, tempdir })
}
@@ -366,7 +369,7 @@ pub struct EreDockerizedCompiler {
}
impl EreDockerizedCompiler {
pub fn new(zkvm: ErezkVM, mount_directory: impl AsRef<Path>) -> Result<Self, DockerizedError> {
pub fn new(zkvm: ErezkVM, mount_directory: impl AsRef<Path>) -> Result<Self, Error> {
zkvm.build_docker_image(false)?;
Ok(Self {
zkvm,
@@ -384,20 +387,20 @@ impl EreDockerizedCompiler {
pub struct SerializedProgram(Vec<u8>);
impl Compiler for EreDockerizedCompiler {
type Error = DockerizedError;
type Error = Error;
type Program = SerializedProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let guest_relative_path = guest_directory
.strip_prefix(&self.mount_directory)
.map_err(|_| DockerizedError::GuestNotInMountingDirecty {
.map_err(|_| Error::GuestNotInMountingDirecty {
mounting_directory: self.mount_directory.to_path_buf(),
guest_directory: guest_directory.to_path_buf(),
})?;
let guest_path_in_docker = PathBuf::from("/guest").join(guest_relative_path);
let tempdir = TempDir::new()
.map_err(|err| DockerizedError::io(err, "Failed to create temporary directory"))?;
let tempdir =
TempDir::new().map_err(|err| Error::io(err, "Failed to create temporary directory"))?;
let mut cmd = DockerRunCmd::new(self.zkvm.compiler_zkvm_image(CRATE_VERSION))
.rm()
@@ -419,11 +422,11 @@ impl Compiler for EreDockerizedCompiler {
"--output-path",
"/output/program",
])
.map_err(DockerizedError::DockerRunCmd)?;
.map_err(Error::DockerRunCmd)?;
let program_path = tempdir.path().join("program");
let program = fs::read(&program_path).map_err(|err| {
DockerizedError::io(
Error::io(
err,
format!(
"Failed to read compiled program at {}",
@@ -463,7 +466,7 @@ impl EreDockerizedzkVM {
zkvm: ErezkVM,
program: SerializedProgram,
resource: ProverResourceType,
) -> Result<Self, DockerizedError> {
) -> Result<Self, Error> {
zkvm.build_docker_image(matches!(resource, ProverResourceType::Gpu))?;
let server_container = zkvm.spawn_server(&program, &resource)?;
@@ -496,7 +499,7 @@ impl EreDockerizedzkVM {
impl zkVM for EreDockerizedzkVM {
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)?;
block_on(self.client.execute(input.to_vec())).map_err(Error::from)?;
Ok((public_values, report))
}
@@ -507,14 +510,13 @@ impl zkVM for EreDockerizedzkVM {
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
let (public_values, proof, report) =
block_on(self.client.prove(input.to_vec(), proof_kind))
.map_err(DockerizedError::from)?;
block_on(self.client.prove(input.to_vec(), proof_kind)).map_err(Error::from)?;
Ok((public_values, proof, report))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let public_values = block_on(self.client.verify(proof)).map_err(DockerizedError::from)?;
let public_values = block_on(self.client.verify(proof)).map_err(Error::from)?;
Ok(public_values)
}
@@ -550,11 +552,13 @@ fn home_dir() -> PathBuf {
#[cfg(test)]
mod test {
use crate::{
DockerizedError, EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM, SerializedProgram,
workspace_dir,
EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM, Error, SerializedProgram, workspace_dir,
};
use ere_test_utils::{host::*, program::basic::BasicProgramInput};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::{Mutex, MutexGuard, OnceLock};
macro_rules! test_compile {
@@ -604,10 +608,7 @@ mod test {
// Invalid test cases
for input in $invalid_test_cases {
let err = zkvm.execute(&input).unwrap_err();
assert!(matches!(
err.downcast::<DockerizedError>().unwrap(),
DockerizedError::zkVM(_)
),);
assert!(matches!(err.downcast::<Error>().unwrap(), Error::zkVM(_)),);
}
drop(zkvm);
@@ -627,10 +628,7 @@ mod test {
// Invalid test cases
for input in $invalid_test_cases {
let err = zkvm.prove(&input, ProofKind::default()).unwrap_err();
assert!(matches!(
err.downcast::<DockerizedError>().unwrap(),
DockerizedError::zkVM(_)
),);
assert!(matches!(err.downcast::<Error>().unwrap(), Error::zkVM(_)),);
}
drop(zkvm);

View File

@@ -21,16 +21,16 @@ tracing = { workspace = true, optional = true }
tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true }
# Local dependencies
ere-airbender = { workspace = true, optional = true }
ere-jolt = { workspace = true, optional = true }
ere-miden = { workspace = true, optional = true }
ere-nexus = { workspace = true, optional = true }
ere-openvm = { workspace = true, optional = true }
ere-pico = { workspace = true, optional = true }
ere-risc0 = { workspace = true, optional = true }
ere-sp1 = { workspace = true, optional = true }
ere-ziren = { workspace = true, optional = true }
ere-zisk = { workspace = true, optional = true }
ere-airbender = { workspace = true, features = ["zkvm"], optional = true }
ere-jolt = { workspace = true, features = ["zkvm"], optional = true }
ere-miden = { workspace = true, features = ["zkvm"], optional = true }
ere-nexus = { workspace = true, features = ["zkvm"], optional = true }
ere-openvm = { workspace = true, features = ["zkvm"], optional = true }
ere-pico = { workspace = true, features = ["zkvm"], optional = true }
ere-risc0 = { workspace = true, features = ["zkvm"], optional = true }
ere-sp1 = { workspace = true, features = ["zkvm"], optional = true }
ere-ziren = { workspace = true, features = ["zkvm"], optional = true }
ere-zisk = { workspace = true, features = ["zkvm"], optional = true }
ere-zkvm-interface = { workspace = true, features = ["clap"] }
[dev-dependencies]

View File

@@ -3,7 +3,7 @@ use crate::api::{
execute_response::Result as ExecuteResult, prove_response::Result as ProveResult,
verify_response::Result as VerifyResult,
};
use ere_zkvm_interface::{
use ere_zkvm_interface::zkvm::{
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, PublicValues,
};
use std::time::{Duration, Instant};
@@ -15,7 +15,7 @@ pub use twirp::{TwirpErrorResponse, url::Url};
#[derive(Debug, Error)]
#[allow(non_camel_case_types)]
pub enum zkVMClientError {
pub enum Error {
#[error("zkVM method error: {0}")]
zkVM(String),
#[error("Connection to zkVM server timeout after 5 minutes")]
@@ -31,7 +31,7 @@ pub struct zkVMClient {
}
impl zkVMClient {
pub async fn new(url: Url) -> Result<Self, zkVMClientError> {
pub async fn new(url: Url) -> Result<Self, Error> {
const TIMEOUT: Duration = Duration::from_secs(300); // 5mins
const INTERVAL: Duration = Duration::from_millis(500);
@@ -40,7 +40,7 @@ impl zkVMClient {
let start = Instant::now();
loop {
if start.elapsed() > TIMEOUT {
return Err(zkVMClientError::ConnectionTimeout);
return Err(Error::ConnectionTimeout);
}
match http_client.get(url.join("health").unwrap()).send().await {
@@ -57,7 +57,7 @@ impl zkVMClient {
pub async fn execute(
&self,
input: Vec<u8>,
) -> Result<(PublicValues, ProgramExecutionReport), zkVMClientError> {
) -> Result<(PublicValues, ProgramExecutionReport), Error> {
let request = Request::new(ExecuteRequest { input });
let response = self.client.execute(request).await?;
@@ -69,7 +69,7 @@ impl zkVMClient {
.map_err(deserialize_report_err)?
.0,
)),
ExecuteResult::Err(err) => Err(zkVMClientError::zkVM(err)),
ExecuteResult::Err(err) => Err(Error::zkVM(err)),
}
}
@@ -77,7 +77,7 @@ impl zkVMClient {
&self,
input: Vec<u8>,
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMClientError> {
) -> Result<(PublicValues, Proof, ProgramProvingReport), Error> {
let request = Request::new(ProveRequest {
input,
proof_kind: proof_kind as i32,
@@ -93,11 +93,11 @@ impl zkVMClient {
.map_err(deserialize_report_err)?
.0,
)),
ProveResult::Err(err) => Err(zkVMClientError::zkVM(err)),
ProveResult::Err(err) => Err(Error::zkVM(err)),
}
}
pub async fn verify(&self, proof: &Proof) -> Result<PublicValues, zkVMClientError> {
pub async fn verify(&self, proof: &Proof) -> Result<PublicValues, Error> {
let request = Request::new(VerifyRequest {
proof: proof.as_bytes().to_vec(),
proof_kind: proof.kind() as i32,
@@ -107,7 +107,7 @@ impl zkVMClient {
match response.into_body().result.ok_or_else(result_none_err)? {
VerifyResult::Ok(result) => Ok(result.public_values),
VerifyResult::Err(err) => Err(zkVMClientError::zkVM(err)),
VerifyResult::Err(err) => Err(Error::zkVM(err)),
}
}
}

View File

@@ -1,7 +1,7 @@
use anyhow::{Context, Error};
use clap::Parser;
use ere_server::server::{router, zkVMServer};
use ere_zkvm_interface::{ProverResourceType, zkVM};
use ere_zkvm_interface::zkvm::{ProverResourceType, zkVM};
use std::{
io::{self, Read},
net::{Ipv4Addr, SocketAddr},
@@ -113,34 +113,34 @@ fn construct_zkvm(program: Vec<u8>, resource: ProverResourceType) -> Result<impl
.with_context(|| "Failed to deserialize program")?;
#[cfg(feature = "airbender")]
let zkvm = ere_airbender::EreAirbender::new(program, resource);
let zkvm = ere_airbender::zkvm::EreAirbender::new(program, resource);
#[cfg(feature = "jolt")]
let zkvm = ere_jolt::EreJolt::new(program, resource);
let zkvm = ere_jolt::zkvm::EreJolt::new(program, resource);
#[cfg(feature = "miden")]
let zkvm = ere_miden::EreMiden::new(program, resource);
let zkvm = ere_miden::zkvm::EreMiden::new(program, resource);
#[cfg(feature = "nexus")]
let zkvm = ere_nexus::EreNexus::new(program, resource);
let zkvm = ere_nexus::zkvm::EreNexus::new(program, resource);
#[cfg(feature = "openvm")]
let zkvm = ere_openvm::EreOpenVM::new(program, resource);
let zkvm = ere_openvm::zkvm::EreOpenVM::new(program, resource);
#[cfg(feature = "pico")]
let zkvm = ere_pico::ErePico::new(program, resource);
let zkvm = ere_pico::zkvm::ErePico::new(program, resource);
#[cfg(feature = "risc0")]
let zkvm = ere_risc0::EreRisc0::new(program, resource);
let zkvm = ere_risc0::zkvm::EreRisc0::new(program, resource);
#[cfg(feature = "sp1")]
let zkvm = ere_sp1::EreSP1::new(program, resource);
let zkvm = ere_sp1::zkvm::EreSP1::new(program, resource);
#[cfg(feature = "ziren")]
let zkvm = ere_ziren::EreZiren::new(program, resource);
let zkvm = ere_ziren::zkvm::EreZiren::new(program, resource);
#[cfg(feature = "zisk")]
let zkvm = ere_zisk::EreZisk::new(program, resource);
let zkvm = ere_zisk::zkvm::EreZisk::new(program, resource);
zkvm.with_context(|| "Failed to instantiate zkVM")
}

View File

@@ -4,7 +4,7 @@ use crate::api::{
execute_response::Result as ExecuteResult, prove_response::Result as ProveResult,
verify_response::Result as VerifyResult,
};
use ere_zkvm_interface::{Proof, ProofKind, zkVM};
use ere_zkvm_interface::zkvm::{Proof, ProofKind, zkVM};
use twirp::{
Request, Response, TwirpErrorResponse, async_trait::async_trait, internal, invalid_argument,
};

View File

@@ -1,6 +1,6 @@
use crate::program::{Program, ProgramInput};
use ere_io_serde::IoSerde;
use ere_zkvm_interface::{ProofKind, PublicValues, zkVM};
use ere_zkvm_interface::zkvm::{ProofKind, PublicValues, zkVM};
use sha2::Digest;
use std::{marker::PhantomData, path::PathBuf};

View File

@@ -0,0 +1,14 @@
use serde::{Serialize, de::DeserializeOwned};
use std::path::Path;
/// Compiler trait for compiling programs into an opaque sequence of bytes.
pub trait Compiler {
type Error: std::error::Error + Send + Sync + 'static;
type Program: Clone + Send + Sync + Serialize + DeserializeOwned;
/// Compiles the program and returns the program
///
/// # Arguments
/// * `guest_directory` - The path to the guest program directory
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error>;
}

View File

@@ -1,134 +1,7 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![allow(clippy::double_parens)]
#![allow(non_camel_case_types)]
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::path::Path;
use strum::{EnumDiscriminants, EnumIs, EnumTryAs, FromRepr};
pub mod compiler;
pub mod zkvm;
mod error;
pub use error::CommonError;
mod reports;
pub use reports::{ProgramExecutionReport, ProgramProvingReport};
mod network;
pub use network::NetworkProverConfig;
/// Compiler trait for compiling programs into an opaque sequence of bytes.
pub trait Compiler {
type Error: std::error::Error + Send + Sync + 'static;
type Program: Clone + Send + Sync + Serialize + DeserializeOwned;
/// Compiles the program and returns the program
///
/// # Arguments
/// * `guest_directory` - The path to the guest program directory
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error>;
}
/// ResourceType specifies what resource will be used to create the proofs.
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "clap", derive(clap::Subcommand))]
pub enum ProverResourceType {
#[default]
Cpu,
Gpu,
/// Use a remote prover network
Network(NetworkProverConfig),
}
#[cfg(feature = "clap")]
impl ProverResourceType {
pub fn to_args(&self) -> Vec<&str> {
match self {
Self::Cpu => vec!["cpu"],
Self::Gpu => vec!["gpu"],
Self::Network(config) => core::iter::once("network")
.chain(config.to_args())
.collect(),
}
}
}
/// Public values committed/revealed by guest program.
///
/// Use [`zkVM::deserialize_from`] to deserialize object from the bytes.
pub type PublicValues = Vec<u8>;
/// Proof generated by [`zkVM::prove`], that also includes the [`PublicValues`]
/// for [`zkVM::verify`] to work.
#[derive(Clone, Debug, Serialize, Deserialize, EnumDiscriminants, EnumIs, EnumTryAs)]
#[strum_discriminants(derive(Default, FromRepr))]
#[strum_discriminants(name(ProofKind))]
pub enum Proof {
/// Compressed proof in contant size regardless of the cycle count.
#[strum_discriminants(default)]
Compressed(Vec<u8>),
/// Groth16 proof that internally verifies a Compressed proof.
Groth16(Vec<u8>),
}
impl Proof {
/// Construct [`Proof`] from [`ProofKind`] and bytes.
pub fn new(proof_kind: ProofKind, bytes: Vec<u8>) -> Self {
match proof_kind {
ProofKind::Compressed => Self::Compressed(bytes),
ProofKind::Groth16 => Self::Groth16(bytes),
}
}
/// Returns [`ProofKind`].
pub fn kind(&self) -> ProofKind {
ProofKind::from(self)
}
/// Returns inner proof as bytes.
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Compressed(bytes) => bytes,
Self::Groth16(bytes) => bytes,
}
}
}
#[auto_impl::auto_impl(&, Arc, Box)]
/// zkVM trait to abstract away the differences between each zkVM.
///
/// This trait provides a unified interface, the workflow is:
/// 1. Compile a guest program using the corresponding `Compiler`.
/// 2. Create a zkVM instance with the compiled program and prover resource.
/// 3. Execute, prove, and verify using the zkVM instance methods.
///
/// Note that a zkVM instance is created for specific program, each zkVM
/// implementation will have their own construction function.
pub trait zkVM {
/// Executes the program with the given input.
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,
) -> 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) -> anyhow::Result<PublicValues>;
/// Returns the name of the zkVM
fn name(&self) -> &'static str;
/// Returns the version of the zkVM SDK (e.g. 0.1.0)
fn sdk_version(&self) -> &'static str;
}
pub trait zkVMProgramDigest {
/// Digest of specific compiled guest program used when verify a proof.
type ProgramDigest: Clone + Serialize + DeserializeOwned;
/// Returns [`zkVMProgramDigest::ProgramDigest`].
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest>;
}
pub use compiler::*;
pub use zkvm::*;

View File

@@ -0,0 +1,59 @@
#![allow(non_camel_case_types)]
use serde::{Serialize, de::DeserializeOwned};
mod error;
mod proof;
mod report;
mod resource;
pub use error::CommonError;
pub use proof::{Proof, ProofKind};
pub use report::{ProgramExecutionReport, ProgramProvingReport};
pub use resource::{NetworkProverConfig, ProverResourceType};
/// Public values committed/revealed by guest program.
///
/// Use [`zkVM::deserialize_from`] to deserialize object from the bytes.
pub type PublicValues = Vec<u8>;
/// zkVM trait to abstract away the differences between each zkVM.
///
/// This trait provides a unified interface, the workflow is:
/// 1. Compile a guest program using the corresponding `Compiler`.
/// 2. Create a zkVM instance with the compiled program and prover resource.
/// 3. Execute, prove, and verify using the zkVM instance methods.
///
/// Note that a zkVM instance is created for specific program, each zkVM
/// implementation will have their own construction function.
#[auto_impl::auto_impl(&, Arc, Box)]
pub trait zkVM {
/// Executes the program with the given input.
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,
) -> 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) -> anyhow::Result<PublicValues>;
/// Returns the name of the zkVM
fn name(&self) -> &'static str;
/// Returns the version of the zkVM SDK (e.g. 0.1.0)
fn sdk_version(&self) -> &'static str;
}
pub trait zkVMProgramDigest {
/// Digest of specific compiled guest program used when verify a proof.
type ProgramDigest: Clone + Serialize + DeserializeOwned;
/// Returns [`zkVMProgramDigest::ProgramDigest`].
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest>;
}

View File

@@ -1,4 +1,4 @@
use crate::ProofKind;
use crate::zkvm::ProofKind;
use std::{
io,
path::Path,

View File

@@ -0,0 +1,40 @@
#![allow(clippy::double_parens)]
use serde::{Deserialize, Serialize};
use strum::{EnumDiscriminants, EnumIs, EnumTryAs, FromRepr};
/// Proof generated by [`zkVM::prove`], that also includes the [`PublicValues`]
/// for [`zkVM::verify`] to work.
#[derive(Clone, Debug, Serialize, Deserialize, EnumDiscriminants, EnumIs, EnumTryAs)]
#[strum_discriminants(derive(Default, FromRepr))]
#[strum_discriminants(name(ProofKind))]
pub enum Proof {
/// Compressed proof in contant size regardless of the cycle count.
#[strum_discriminants(default)]
Compressed(Vec<u8>),
/// Groth16 proof that internally verifies a Compressed proof.
Groth16(Vec<u8>),
}
impl Proof {
/// Construct [`Proof`] from [`ProofKind`] and bytes.
pub fn new(proof_kind: ProofKind, bytes: Vec<u8>) -> Self {
match proof_kind {
ProofKind::Compressed => Self::Compressed(bytes),
ProofKind::Groth16 => Self::Groth16(bytes),
}
}
/// Returns [`ProofKind`].
pub fn kind(&self) -> ProofKind {
ProofKind::from(self)
}
/// Returns inner proof as bytes.
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Compressed(bytes) => bytes,
Self::Groth16(bytes) => bytes,
}
}
}

View File

@@ -22,3 +22,27 @@ impl NetworkProverConfig {
.collect()
}
}
/// ResourceType specifies what resource will be used to create the proofs.
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "clap", derive(clap::Subcommand))]
pub enum ProverResourceType {
#[default]
Cpu,
Gpu,
/// Use a remote prover network
Network(NetworkProverConfig),
}
#[cfg(feature = "clap")]
impl ProverResourceType {
pub fn to_args(&self) -> Vec<&str> {
match self {
Self::Cpu => vec!["cpu"],
Self::Gpu => vec!["gpu"],
Self::Network(config) => core::iter::once("network")
.chain(config.to_args())
.collect(),
}
}
}

View File

@@ -8,15 +8,16 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
serde.workspace = true
serde_json.workspace = true
tempfile.workspace = true
thiserror.workspace = true
# Airbender dependencies
airbender_execution_utils.workspace = true
airbender_execution_utils = { workspace = true, optional = true }
# Local dependencies
ere-compile-utils.workspace = true
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]
@@ -25,5 +26,10 @@ ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = ["dep:airbender_execution_utils"]
[lints]
workspace = true

View File

@@ -1,5 +1,5 @@
mod error;
mod rust_rv32ima;
pub use error::Error;
pub use rust_rv32ima::RustRv32ima;
pub type AirbenderProgram = Vec<u8>;

View File

@@ -0,0 +1,8 @@
use ere_compile_utils::CommonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
}

View File

@@ -1,6 +1,6 @@
use crate::{compiler::AirbenderProgram, error::CompileError};
use crate::{compiler::Error, program::AirbenderProgram};
use ere_compile_utils::{CargoBuildCmd, CommonError};
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use std::{
env,
io::Write,
@@ -35,7 +35,7 @@ const LINKER_SCRIPT: &str = concat!(
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = CompileError;
type Error = Error;
type Program = AirbenderProgram;
@@ -48,11 +48,11 @@ impl Compiler for RustRv32ima {
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
let bin = objcopy_binary(&elf)?;
Ok(bin)
Ok(AirbenderProgram { bin })
}
}
fn objcopy_binary(elf: &[u8]) -> Result<Vec<u8>, CompileError> {
fn objcopy_binary(elf: &[u8]) -> Result<Vec<u8>, Error> {
let mut cmd = Command::new("rust-objcopy");
let mut child = cmd
.args(["-O", "binary", "-", "-"])
@@ -88,12 +88,12 @@ fn objcopy_binary(elf: &[u8]) -> Result<Vec<u8>, CompileError> {
mod tests {
use crate::compiler::RustRv32ima;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("airbender", "basic");
let bin = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!bin.is_empty(), "Binary should not be empty.");
let program = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!program.bin.is_empty(), "Binary should not be empty.");
}
}

View File

@@ -1,173 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{
client::{AirbenderSdk, VkHashChain},
compiler::AirbenderProgram,
error::AirbenderError,
};
use airbender_execution_utils::ProgramProof;
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use std::time::Instant;
pub mod program;
mod client;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
#[cfg(feature = "zkvm")]
pub mod zkvm;
pub struct EreAirbender {
sdk: AirbenderSdk,
}
impl EreAirbender {
pub fn new(
bin: AirbenderProgram,
resource: ProverResourceType,
) -> Result<Self, AirbenderError> {
let gpu = matches!(resource, ProverResourceType::Gpu);
let sdk = AirbenderSdk::new(&bin, gpu);
Ok(Self { sdk })
}
}
impl zkVM for EreAirbender {
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();
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles: cycles,
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
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(|err| CommonError::serialize("proof", "bincode", err))?;
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
bail!(CommonError::unsupported_proof_kind(
proof.kind(),
[ProofKind::Compressed]
))
};
let (proof, _): (ProgramProof, _) =
bincode::serde::decode_from_slice(proof, bincode::config::legacy())
.map_err(|err| CommonError::deserialize("proof", "bincode", err))?;
let public_values = self.sdk.verify(&proof)?;
Ok(public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreAirbender {
type ProgramDigest = VkHashChain;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(*self.sdk.vk_chain_hash())
}
}
#[cfg(test)]
mod tests {
use crate::{
EreAirbender,
compiler::{AirbenderProgram, RustRv32ima},
};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
fn basic_program() -> AirbenderProgram {
static PROGRAM: OnceLock<AirbenderProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32ima
.compile(&testing_guest_directory("airbender", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
/// Airbender program that contains binary format of compiled guest.
#[derive(Clone, Serialize, Deserialize)]
pub struct AirbenderProgram {
pub(crate) bin: Vec<u8>,
}
impl AirbenderProgram {
pub fn bin(&self) -> &[u8] {
&self.bin
}
}

View File

@@ -0,0 +1,166 @@
use crate::{program::AirbenderProgram, zkvm::sdk::AirbenderSdk};
use airbender_execution_utils::ProgramProof;
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use std::time::Instant;
mod error;
mod sdk;
pub use error::Error;
pub use sdk::VkHashChain;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub struct EreAirbender {
sdk: AirbenderSdk,
}
impl EreAirbender {
pub fn new(program: AirbenderProgram, resource: ProverResourceType) -> Result<Self, Error> {
let gpu = matches!(resource, ProverResourceType::Gpu);
let sdk = AirbenderSdk::new(program.bin(), gpu);
Ok(Self { sdk })
}
}
impl zkVM for EreAirbender {
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();
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles: cycles,
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
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(|err| CommonError::serialize("proof", "bincode", err))?;
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
bail!(CommonError::unsupported_proof_kind(
proof.kind(),
[ProofKind::Compressed]
))
};
let (proof, _): (ProgramProof, _) =
bincode::serde::decode_from_slice(proof, bincode::config::legacy())
.map_err(|err| CommonError::deserialize("proof", "bincode", err))?;
let public_values = self.sdk.verify(&proof)?;
Ok(public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreAirbender {
type ProgramDigest = VkHashChain;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(*self.sdk.vk_chain_hash())
}
}
#[cfg(test)]
mod tests {
use crate::{compiler::RustRv32ima, program::AirbenderProgram, zkvm::EreAirbender};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::OnceLock;
fn basic_program() -> AirbenderProgram {
static PROGRAM: OnceLock<AirbenderProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32ima
.compile(&testing_guest_directory("airbender", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
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

@@ -1,17 +1,11 @@
use crate::client::VkHashChain;
use ere_compile_utils::CommonError;
use crate::zkvm::sdk::VkHashChain;
use ere_zkvm_interface::zkvm::CommonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompileError {
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
}
#[derive(Debug, Error)]
pub enum AirbenderError {
#[error(transparent)]
CommonError(#[from] ere_zkvm_interface::CommonError),
// Execution
#[error("Failed to parse public value from stdout: {0}")]

View File

@@ -1,9 +1,9 @@
use crate::error::AirbenderError;
use crate::zkvm::error::Error;
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::{CommonError, PublicValues};
use ere_zkvm_interface::zkvm::{CommonError, PublicValues};
use std::{array, fs, io::BufRead, iter, process::Command};
use tempfile::tempdir;
@@ -46,7 +46,7 @@ impl AirbenderSdk {
&self.vk_hash_chain
}
pub fn execute(&self, input: &[u8]) -> Result<(PublicValues, u64), AirbenderError> {
pub fn execute(&self, input: &[u8]) -> Result<(PublicValues, u64), Error> {
let tempdir = tempdir().map_err(CommonError::tempdir)?;
let bin_path = tempdir.path().join("guest.bin");
@@ -92,9 +92,7 @@ impl AirbenderSdk {
Some(bytes)
})
.ok_or_else(|| {
AirbenderError::ParsePublicValue(
String::from_utf8_lossy(&output.stdout).to_string(),
)
Error::ParsePublicValue(String::from_utf8_lossy(&output.stdout).to_string())
})?;
// Parse cycles from stdout in format of:
@@ -109,13 +107,13 @@ impl AirbenderSdk {
cycle.parse().ok()
})
.ok_or_else(|| {
AirbenderError::ParseCycles(String::from_utf8_lossy(&output.stdout).to_string())
Error::ParseCycles(String::from_utf8_lossy(&output.stdout).to_string())
})?;
Ok((public_values, cycles))
}
pub fn prove(&self, input: &[u8]) -> Result<(PublicValues, ProgramProof), AirbenderError> {
pub fn prove(&self, input: &[u8]) -> Result<(PublicValues, ProgramProof), Error> {
let tempdir = tempdir().map_err(CommonError::tempdir)?;
let bin_path = tempdir.path().join("guest.bin");
@@ -189,7 +187,7 @@ impl AirbenderSdk {
let (public_values, vk_hash_chain) = extract_public_values_and_vk_hash_chain(&proof)?;
if self.vk_hash_chain != vk_hash_chain {
return Err(AirbenderError::UnexpectedVkHashChain {
return Err(Error::UnexpectedVkHashChain {
preprocessed: self.vk_hash_chain,
proved: vk_hash_chain,
});
@@ -198,16 +196,16 @@ impl AirbenderSdk {
Ok((public_values, proof))
}
pub fn verify(&self, proof: &ProgramProof) -> Result<PublicValues, AirbenderError> {
pub fn verify(&self, proof: &ProgramProof) -> Result<PublicValues, Error> {
let is_valid = verify_recursion_log_23_layer(proof);
if !is_valid {
return Err(AirbenderError::ProofVerificationFailed);
return Err(Error::ProofVerificationFailed);
}
let (public_values, vk_hash_chain) = extract_public_values_and_vk_hash_chain(proof)?;
if self.vk_hash_chain != vk_hash_chain {
return Err(AirbenderError::UnexpectedVkHashChain {
return Err(Error::UnexpectedVkHashChain {
preprocessed: self.vk_hash_chain,
proved: vk_hash_chain,
});
@@ -232,9 +230,9 @@ fn encode_input(input: &[u8]) -> String {
// Extract public values and VK hash chain from register values.
fn extract_public_values_and_vk_hash_chain(
proof: &ProgramProof,
) -> Result<(PublicValues, VkHashChain), AirbenderError> {
) -> Result<(PublicValues, VkHashChain), Error> {
if proof.register_final_values.len() != 32 {
return Err(AirbenderError::InvalidRegisterCount(
return Err(Error::InvalidRegisterCount(
proof.register_final_values.len(),
));
}

View File

@@ -7,6 +7,7 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
serde.workspace = true
tempfile.workspace = true
thiserror.workspace = true
@@ -14,10 +15,10 @@ thiserror.workspace = true
jolt-ark-serialize = { workspace = true, features = ["derive"] }
jolt-common.workspace = true
jolt-core.workspace = true
jolt-sdk = { workspace = true, features = ["host"] }
jolt-sdk = { workspace = true, features = ["host"], optional = true }
# Local dependencies
ere-compile-utils.workspace = true
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]
@@ -26,5 +27,10 @@ ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = ["dep:jolt-sdk"]
[lints]
workspace = true

View File

@@ -1,7 +1,7 @@
mod error;
mod rust_rv64imac;
mod rust_rv64imac_customized;
pub use error::Error;
pub use rust_rv64imac::RustRv64imac;
pub use rust_rv64imac_customized::RustRv64imacCustomized;
pub type JoltProgram = Vec<u8>;

View File

@@ -1,10 +1,9 @@
use ere_compile_utils::CommonError;
use jolt_core::utils::errors::ProofVerifyError;
use std::{io, path::PathBuf};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompileError {
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
@@ -18,17 +17,3 @@ pub enum CompileError {
#[error("Failed to build guest")]
BuildFailed,
}
#[derive(Debug, Error)]
pub enum JoltError {
#[error(transparent)]
CommonError(#[from] ere_zkvm_interface::CommonError),
// Execute
#[error("Execution panics")]
ExecutionPanic,
// Verify
#[error("Failed to verify proof: {0}")]
VerifyProofFailed(#[from] ProofVerifyError),
}

View File

@@ -1,6 +1,6 @@
use crate::{compiler::JoltProgram, error::CompileError};
use crate::{compiler::Error, program::JoltProgram};
use ere_compile_utils::CargoBuildCmd;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use jolt_common::constants::{
DEFAULT_MEMORY_SIZE, DEFAULT_STACK_SIZE, EMULATOR_MEMORY_CAPACITY, STACK_CANARY_SIZE,
};
@@ -41,7 +41,7 @@ fn make_linker_script() -> String {
pub struct RustRv64imac;
impl Compiler for RustRv64imac {
type Error = CompileError;
type Error = Error;
type Program = JoltProgram;
@@ -53,21 +53,24 @@ impl Compiler for RustRv64imac {
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
Ok(JoltProgram { elf })
}
}
#[cfg(test)]
mod tests {
use crate::{EreJolt, compiler::RustRv64imac};
use crate::{compiler::RustRv64imac, zkvm::EreJolt};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProverResourceType, zkVM},
};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("jolt", "stock_nightly_no_std");
let elf = RustRv64imac.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
let program = RustRv64imac.compile(&guest_directory).unwrap();
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
#[test]

View File

@@ -1,7 +1,7 @@
use crate::{compiler::JoltProgram, error::CompileError};
use crate::{compiler::Error, program::JoltProgram};
use ere_compile_utils::{CommonError, cargo_metadata};
use ere_zkvm_interface::Compiler;
use jolt_sdk::host::Program;
use ere_zkvm_interface::compiler::Compiler;
use jolt_core::host::Program;
use std::{env::set_current_dir, fs, path::Path};
use tempfile::tempdir;
@@ -10,13 +10,13 @@ use tempfile::tempdir;
pub struct RustRv64imacCustomized;
impl Compiler for RustRv64imacCustomized {
type Error = CompileError;
type Error = Error;
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(|err| CompileError::SetCurrentDirFailed {
set_current_dir(guest_directory).map_err(|err| Error::SetCurrentDirFailed {
err,
path: guest_directory.to_path_buf(),
})?;
@@ -33,12 +33,12 @@ impl Compiler for RustRv64imacCustomized {
program.build(&tempdir.path().to_string_lossy());
program.elf.unwrap()
})
.map_err(|_| CompileError::BuildFailed)?;
.map_err(|_| Error::BuildFailed)?;
let elf =
fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?;
Ok(elf)
Ok(JoltProgram { elf })
}
}
@@ -46,12 +46,12 @@ impl Compiler for RustRv64imacCustomized {
mod tests {
use crate::compiler::RustRv64imacCustomized;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("jolt", "basic");
let elf = RustRv64imacCustomized.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
let program = RustRv64imacCustomized.compile(&guest_directory).unwrap();
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,178 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{
client::{JoltProof, JoltSdk},
compiler::JoltProgram,
error::JoltError,
};
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM,
};
use jolt_ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use std::{env, io::Cursor, time::Instant};
pub mod program;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod client;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
pub struct EreJolt {
sdk: JoltSdk,
_resource: ProverResourceType,
}
#[cfg(feature = "zkvm")]
pub mod zkvm;
impl EreJolt {
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 sdk = JoltSdk::new(&elf);
Ok(EreJolt {
sdk,
_resource: resource,
})
}
}
impl zkVM for EreJolt {
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();
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles,
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
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 mut proof_bytes = Vec::new();
proof
.serialize_compressed(&mut proof_bytes)
.map_err(|err| CommonError::serialize("proof", "jolt", err))?;
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
bail!(CommonError::unsupported_proof_kind(
proof.kind(),
[ProofKind::Compressed]
))
};
let proof = JoltProof::deserialize_compressed(&mut Cursor::new(proof))
.map_err(|err| CommonError::deserialize("proof", "jolt", err))?;
let public_values = self.sdk.verify(proof)?;
Ok(public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
#[cfg(test)]
mod tests {
use crate::{
EreJolt,
compiler::{JoltProgram, RustRv64imacCustomized},
};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::{Mutex, OnceLock};
/// While proving, Jolt uses global static variables to store some
/// parameters, that might cause panics if we prove concurrently, so we put
/// a lock here for the test to work without the need to set test threads.
static PROVE_LOCK: Mutex<()> = Mutex::new(());
fn basic_program() -> JoltProgram {
static PROGRAM: OnceLock<JoltProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv64imacCustomized
.compile(&testing_guest_directory("jolt", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
let _guard = PROVE_LOCK.lock().unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
let _guard = PROVE_LOCK.lock().unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
/// Jolt program that contains ELF of compiled guest.
#[derive(Clone, Serialize, Deserialize)]
pub struct JoltProgram {
pub(crate) elf: Vec<u8>,
}
impl JoltProgram {
pub fn elf(&self) -> &[u8] {
&self.elf
}
}

View File

@@ -0,0 +1,172 @@
use crate::{
program::JoltProgram,
zkvm::sdk::{JoltProof, JoltSdk},
};
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM,
};
use jolt_ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use std::{env, io::Cursor, time::Instant};
mod error;
mod sdk;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub struct EreJolt {
sdk: JoltSdk,
}
impl EreJolt {
pub fn new(program: JoltProgram, resource: ProverResourceType) -> Result<Self, Error> {
if !matches!(resource, ProverResourceType::Cpu) {
panic!("Network or GPU proving not yet implemented for Miden. Use CPU resource type.");
}
let sdk = JoltSdk::new(program.elf());
Ok(EreJolt { sdk })
}
}
impl zkVM for EreJolt {
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();
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles,
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
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 mut proof_bytes = Vec::new();
proof
.serialize_compressed(&mut proof_bytes)
.map_err(|err| CommonError::serialize("proof", "jolt", err))?;
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
bail!(CommonError::unsupported_proof_kind(
proof.kind(),
[ProofKind::Compressed]
))
};
let proof = JoltProof::deserialize_compressed(&mut Cursor::new(proof))
.map_err(|err| CommonError::deserialize("proof", "jolt", err))?;
let public_values = self.sdk.verify(proof)?;
Ok(public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
#[cfg(test)]
mod tests {
use crate::{compiler::RustRv64imacCustomized, program::JoltProgram, zkvm::EreJolt};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::{Mutex, OnceLock};
/// While proving, Jolt uses global static variables to store some
/// parameters, that might cause panics if we prove concurrently, so we put
/// a lock here for the test to work without the need to set test threads.
static PROVE_LOCK: Mutex<()> = Mutex::new(());
fn basic_program() -> JoltProgram {
static PROGRAM: OnceLock<JoltProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv64imacCustomized
.compile(&testing_guest_directory("jolt", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
let _guard = PROVE_LOCK.lock().unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
let _guard = PROVE_LOCK.lock().unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}

View File

@@ -0,0 +1,17 @@
use ere_zkvm_interface::zkvm::CommonError;
use jolt_core::utils::errors::ProofVerifyError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
// Execute
#[error("Execution panics")]
ExecutionPanic,
// Verify
#[error("Failed to verify proof: {0}")]
VerifyProofFailed(#[from] ProofVerifyError),
}

View File

@@ -1,5 +1,5 @@
use crate::error::JoltError;
use ere_zkvm_interface::{CommonError, PublicValues};
use crate::zkvm::Error;
use ere_zkvm_interface::zkvm::{CommonError, PublicValues};
use jolt_ark_serialize::{self as ark_serialize, CanonicalDeserialize, CanonicalSerialize};
use jolt_common::constants::{
DEFAULT_MAX_INPUT_SIZE, DEFAULT_MAX_OUTPUT_SIZE, DEFAULT_MAX_TRACE_LENGTH, DEFAULT_MEMORY_SIZE,
@@ -62,7 +62,7 @@ impl JoltSdk {
}
}
pub fn execute(&self, input: &[u8]) -> Result<(PublicValues, u64), JoltError> {
pub fn execute(&self, input: &[u8]) -> Result<(PublicValues, u64), Error> {
let (cycles, _, io) = trace(
&self.elf,
None,
@@ -70,16 +70,16 @@ impl JoltSdk {
&self.memory_config,
);
if io.panic {
return Err(JoltError::ExecutionPanic);
return Err(Error::ExecutionPanic);
}
let public_values = deserialize_output(&io.outputs)?;
Ok((public_values, cycles.len() as _))
}
pub fn prove(&self, input: &[u8]) -> Result<(PublicValues, JoltProof), JoltError> {
pub fn prove(&self, input: &[u8]) -> Result<(PublicValues, JoltProof), Error> {
let (proof, io, _) = JoltRV64IMAC::prove(&self.pk, &self.elf, &serialize_input(input)?);
if io.panic {
return Err(JoltError::ExecutionPanic);
return Err(Error::ExecutionPanic);
}
let public_values = deserialize_output(&io.outputs)?;
let proof = JoltProof {
@@ -90,7 +90,7 @@ impl JoltSdk {
Ok((public_values, proof))
}
pub fn verify(&self, proof: JoltProof) -> Result<PublicValues, JoltError> {
pub fn verify(&self, proof: JoltProof) -> Result<PublicValues, Error> {
JoltRV64IMAC::verify(
&self.vk,
proof.proof,
@@ -107,12 +107,12 @@ impl JoltSdk {
}
}
fn serialize_input(bytes: &[u8]) -> Result<Vec<u8>, JoltError> {
fn serialize_input(bytes: &[u8]) -> Result<Vec<u8>, Error> {
Ok(postcard::to_stdvec(bytes)
.map_err(|err| CommonError::serialize("input", "postcard", err))?)
}
fn deserialize_output(output: &[u8]) -> Result<Vec<u8>, JoltError> {
fn deserialize_output(output: &[u8]) -> Result<Vec<u8>, Error> {
Ok(if output.is_empty() {
Vec::new()
} else {

View File

@@ -13,10 +13,10 @@ thiserror.workspace = true
# Miden
miden-assembly = { workspace = true, features = ["std"] }
miden-core = { workspace = true, features = ["std"] }
miden-processor = { workspace = true, features = ["std"] }
miden-prover = { workspace = true, features = ["std"] }
miden-processor = { workspace = true, features = ["std"], optional = true }
miden-prover = { workspace = true, features = ["std"], optional = true }
miden-stdlib = { workspace = true, features = ["std"] }
miden-verifier.workspace = true
miden-verifier = { workspace = true, optional = true }
# Local dependencies
ere-zkvm-interface.workspace = true
@@ -27,5 +27,10 @@ ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = []
zkvm = ["dep:miden-processor", "dep:miden-prover", "dep:miden-verifier"]
[lints]
workspace = true

View File

@@ -1,46 +1,5 @@
use miden_core::{
Program, ProgramInfo,
utils::{Deserializable, Serializable},
};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
use std::ops::Deref;
mod error;
mod miden_asm;
pub use error::Error;
pub use miden_asm::MidenAsm;
pub type MidenProgram = MidenSerdeWrapper<Program>;
pub type MidenProgramInfo = MidenSerdeWrapper<ProgramInfo>;
/// Wrapper that implements `serde` for Miden structures.
#[derive(Clone)]
pub struct MidenSerdeWrapper<T>(pub T);
impl<T> Deref for MidenSerdeWrapper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Serializable> Serialize for MidenSerdeWrapper<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.0.to_bytes())
}
}
impl<'de, T: Deserializable> Deserialize<'de> for MidenSerdeWrapper<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = Vec::<u8>::deserialize(deserializer)?;
T::read_from_bytes(&bytes)
.map(Self)
.map_err(D::Error::custom)
}
}

View File

@@ -1,11 +1,9 @@
use miden_assembly::Report;
use miden_processor::ExecutionError;
use miden_verifier::VerificationError;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompileError {
pub enum Error {
#[error("Invalid program directory name")]
InvalidProgramPath,
@@ -28,21 +26,3 @@ pub enum CompileError {
#[error("Miden assembly compilation failed: {0}")]
AssemblyCompilation(Report),
}
#[derive(Debug, Error)]
pub enum MidenError {
#[error(transparent)]
CommonError(#[from] ere_zkvm_interface::CommonError),
// Execute
#[error("Miden execution failed")]
Execute(#[from] ExecutionError),
// Prove
#[error("Miden proving failed: {0}")]
Prove(#[source] ExecutionError),
// Verify
#[error("Miden verification failed")]
Verify(#[from] VerificationError),
}

View File

@@ -1,8 +1,8 @@
use crate::{
compiler::{MidenProgram, MidenSerdeWrapper},
error::CompileError,
compiler::Error,
program::{MidenProgram, MidenSerdeWrapper},
};
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use miden_assembly::Assembler;
use miden_stdlib::StdLibrary;
use std::{env, fs, path::Path};
@@ -11,39 +11,38 @@ use std::{env, fs, path::Path};
pub struct MidenAsm;
impl Compiler for MidenAsm {
type Error = CompileError;
type Error = Error;
type Program = MidenProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let dir_name = guest_directory
.file_name()
.and_then(|name| name.to_str())
.ok_or(CompileError::InvalidProgramPath)?;
.ok_or(Error::InvalidProgramPath)?;
let entrypoint = format!("{dir_name}.masm");
let entrypoint_path = guest_directory.join(&entrypoint);
if !entrypoint_path.exists() {
return Err(CompileError::MissingEntrypoint {
return Err(Error::MissingEntrypoint {
program_dir: guest_directory.display().to_string(),
entrypoint,
});
}
let source =
fs::read_to_string(&entrypoint_path).map_err(|err| CompileError::ReadEntrypoint {
entrypoint_path,
err,
})?;
let source = fs::read_to_string(&entrypoint_path).map_err(|err| Error::ReadEntrypoint {
entrypoint_path,
err,
})?;
// Compile using Miden assembler
let mut assembler =
Assembler::default().with_debug_mode(env::var_os("MIDEN_DEBUG").is_some());
assembler
.link_dynamic_library(StdLibrary::default())
.map_err(CompileError::LoadStdLibrary)?;
.map_err(Error::LoadStdLibrary)?;
let program = assembler
.assemble_program(&source)
.map_err(CompileError::AssemblyCompilation)?;
.map_err(Error::AssemblyCompilation)?;
Ok(MidenSerdeWrapper(program))
}
@@ -53,7 +52,7 @@ impl Compiler for MidenAsm {
mod tests {
use crate::compiler::MidenAsm;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {

View File

@@ -1,265 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{
compiler::{MidenProgram, MidenProgramInfo, MidenSerdeWrapper},
error::MidenError,
};
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use miden_core::{
Program,
utils::{Deserializable, Serializable},
};
use miden_processor::{
DefaultHost, ExecutionOptions, ProgramInfo, StackInputs, StackOutputs, execute as miden_execute,
};
use miden_prover::{AdviceInputs, ExecutionProof, ProvingOptions, prove as miden_prove};
use miden_stdlib::StdLibrary;
use miden_verifier::verify as miden_verify;
use std::{env, time::Instant};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod program;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
pub use miden_core::{Felt, FieldElement};
#[cfg(feature = "zkvm")]
pub mod zkvm;
/// [`zkVM`] implementation for Miden.
///
/// Miden VM takes list of field elements as input instead of bytes, so in
/// [`zkVM::execute`] and [`zkVM::prove`] we require the given `input` is built
/// from [`felts_to_bytes`].
/// Similarly, the output values of Miden is also list of field elements, to
/// be compatible with [`zkVM`], we convert it into [`PublicValues`] by
/// [`felts_to_bytes`] as well.
pub struct EreMiden {
program: Program,
_resource: ProverResourceType,
}
impl EreMiden {
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(MidenError::Execute)?;
Ok(host)
}
}
impl zkVM for EreMiden {
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)?);
let mut host = Self::setup_host()?;
let start = Instant::now();
let trace = miden_execute(
&self.program,
stack_inputs,
advice_inputs,
&mut host,
ExecutionOptions::default(),
)
.map_err(MidenError::Execute)?;
let public_values = felts_to_bytes(trace.stack_outputs().as_slice());
let report = ProgramExecutionReport {
total_num_cycles: trace.trace_len_summary().main_trace_len() as u64,
execution_duration: start.elapsed(),
..Default::default()
};
Ok((public_values, report))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
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)?);
let mut host = Self::setup_host()?;
let start = Instant::now();
let proving_options =
ProvingOptions::with_96_bit_security(env::var_os("MIDEN_DEBUG").is_some());
let (stack_outputs, proof) = miden_prove(
&self.program,
stack_inputs.clone(),
advice_inputs,
&mut host,
proving_options,
)
.map_err(MidenError::Prove)?;
let public_values = felts_to_bytes(stack_outputs.as_slice());
let proof_bytes = (stack_outputs, proof).to_bytes();
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(start.elapsed()),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
bail!(CommonError::unsupported_proof_kind(
proof.kind(),
[ProofKind::Compressed]
))
};
let program_info: ProgramInfo = self.program.clone().into();
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(), proof)
.map_err(MidenError::Verify)?;
Ok(felts_to_bytes(stack_outputs.as_slice()))
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreMiden {
type ProgramDigest = MidenProgramInfo;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(MidenSerdeWrapper(self.program.clone().into()))
}
}
/// Convert Miden field elements into bytes
pub fn felts_to_bytes(felts: &[Felt]) -> Vec<u8> {
felts
.iter()
.flat_map(|felt| felt.as_int().to_le_bytes())
.collect()
}
/// Convert bytes into Miden field elements.
pub fn bytes_to_felts(bytes: &[u8]) -> Result<Vec<Felt>, MidenError> {
if bytes.len() % 8 != 0 {
let err = anyhow::anyhow!(
"Invalid bytes length {}, expected multiple of 8",
bytes.len()
);
Err(CommonError::serialize("input", "miden", err))?;
}
Ok(bytes
.chunks(8)
.map(|bytes| Felt::try_from(u64::from_le_bytes(bytes.try_into().unwrap())))
.collect::<Result<Vec<Felt>, _>>()
.map_err(|err| CommonError::serialize("input", "miden", anyhow::anyhow!(err)))?)
}
#[cfg(test)]
mod tests {
use crate::{
EreMiden, Felt, FieldElement, bytes_to_felts,
compiler::{MidenAsm, MidenProgram},
felts_to_bytes,
};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
fn load_miden_program(guest_name: &str) -> MidenProgram {
MidenAsm
.compile(&testing_guest_directory("miden", guest_name))
.unwrap()
}
#[test]
fn test_prove_and_verify_add() {
let program = load_miden_program("add");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let const_a = -Felt::ONE;
let const_b = Felt::ONE / Felt::ONE.double();
let expected_sum = const_a + const_b;
let input = felts_to_bytes(&[const_a, const_b]);
// Prove
let (prover_public_values, proof, _) = zkvm.prove(&input, ProofKind::default()).unwrap();
// Verify
let verifier_public_values = zkvm.verify(&proof).unwrap();
assert_eq!(prover_public_values, verifier_public_values);
// Assert output
let output = bytes_to_felts(&verifier_public_values).unwrap();
assert_eq!(output[0], expected_sum);
}
#[test]
fn test_prove_and_verify_fib() {
let program = load_miden_program("fib");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let n_iterations = 50u32;
let expected_fib = Felt::try_from(12_586_269_025u64).unwrap();
let input = felts_to_bytes(&[Felt::from(0u32), Felt::from(1u32), Felt::from(n_iterations)]);
// Prove
let (prover_public_values, proof, _) = zkvm.prove(&input, ProofKind::default()).unwrap();
// Verify
let verifier_public_values = zkvm.verify(&proof).unwrap();
assert_eq!(prover_public_values, verifier_public_values);
// Assert output
let output = bytes_to_felts(&verifier_public_values).unwrap();
assert_eq!(output[0], expected_fib);
}
#[test]
fn test_invalid_input() {
let program = load_miden_program("add");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let empty_inputs = Vec::new();
assert!(zkvm.execute(&empty_inputs).is_err());
let insufficient_inputs = felts_to_bytes(&[Felt::from(5u32)]);
assert!(zkvm.execute(&insufficient_inputs).is_err());
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,42 @@
use miden_core::{
Program, ProgramInfo,
utils::{Deserializable, Serializable},
};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
use std::ops::Deref;
pub type MidenProgram = MidenSerdeWrapper<Program>;
pub type MidenProgramInfo = MidenSerdeWrapper<ProgramInfo>;
/// Wrapper that implements `serde` for Miden structures.
#[derive(Clone)]
pub struct MidenSerdeWrapper<T>(pub T);
impl<T> Deref for MidenSerdeWrapper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Serializable> Serialize for MidenSerdeWrapper<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.0.to_bytes())
}
}
impl<'de, T: Deserializable> Deserialize<'de> for MidenSerdeWrapper<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = Vec::<u8>::deserialize(deserializer)?;
T::read_from_bytes(&bytes)
.map(Self)
.map_err(D::Error::custom)
}
}

View File

@@ -0,0 +1,259 @@
use crate::program::{MidenProgram, MidenProgramInfo, MidenSerdeWrapper};
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use miden_core::{
Program,
utils::{Deserializable, Serializable},
};
use miden_processor::{
DefaultHost, ExecutionOptions, ProgramInfo, StackInputs, StackOutputs, execute as miden_execute,
};
use miden_prover::{AdviceInputs, ExecutionProof, ProvingOptions, prove as miden_prove};
use miden_stdlib::StdLibrary;
use miden_verifier::verify as miden_verify;
use std::{env, time::Instant};
mod error;
pub use error::Error;
pub use miden_core::{Felt, FieldElement};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
/// [`zkVM`] implementation for Miden.
///
/// Miden VM takes list of field elements as input instead of bytes, so in
/// [`zkVM::execute`] and [`zkVM::prove`] we require the given `input` is built
/// from [`felts_to_bytes`].
/// Similarly, the output values of Miden is also list of field elements, to
/// be compatible with [`zkVM`], we convert it into [`PublicValues`] by
/// [`felts_to_bytes`] as well.
pub struct EreMiden {
program: Program,
}
impl EreMiden {
pub fn new(program: MidenProgram, resource: ProverResourceType) -> Result<Self, Error> {
if !matches!(resource, ProverResourceType::Cpu) {
panic!("Network or GPU proving not yet implemented for Miden. Use CPU resource type.");
}
Ok(Self { program: program.0 })
}
fn setup_host() -> Result<DefaultHost, Error> {
let mut host = DefaultHost::default();
host.load_library(&StdLibrary::default())
.map_err(Error::Execute)?;
Ok(host)
}
}
impl zkVM for EreMiden {
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)?);
let mut host = Self::setup_host()?;
let start = Instant::now();
let trace = miden_execute(
&self.program,
stack_inputs,
advice_inputs,
&mut host,
ExecutionOptions::default(),
)
.map_err(Error::Execute)?;
let public_values = felts_to_bytes(trace.stack_outputs().as_slice());
let report = ProgramExecutionReport {
total_num_cycles: trace.trace_len_summary().main_trace_len() as u64,
execution_duration: start.elapsed(),
..Default::default()
};
Ok((public_values, report))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
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)?);
let mut host = Self::setup_host()?;
let start = Instant::now();
let proving_options =
ProvingOptions::with_96_bit_security(env::var_os("MIDEN_DEBUG").is_some());
let (stack_outputs, proof) = miden_prove(
&self.program,
stack_inputs.clone(),
advice_inputs,
&mut host,
proving_options,
)
.map_err(Error::Prove)?;
let public_values = felts_to_bytes(stack_outputs.as_slice());
let proof_bytes = (stack_outputs, proof).to_bytes();
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(start.elapsed()),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
bail!(CommonError::unsupported_proof_kind(
proof.kind(),
[ProofKind::Compressed]
))
};
let program_info: ProgramInfo = self.program.clone().into();
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(), proof)
.map_err(Error::Verify)?;
Ok(felts_to_bytes(stack_outputs.as_slice()))
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreMiden {
type ProgramDigest = MidenProgramInfo;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(MidenSerdeWrapper(self.program.clone().into()))
}
}
/// Convert Miden field elements into bytes
pub fn felts_to_bytes(felts: &[Felt]) -> Vec<u8> {
felts
.iter()
.flat_map(|felt| felt.as_int().to_le_bytes())
.collect()
}
/// Convert bytes into Miden field elements.
pub fn bytes_to_felts(bytes: &[u8]) -> Result<Vec<Felt>, Error> {
if bytes.len() % 8 != 0 {
let err = anyhow::anyhow!(
"Invalid bytes length {}, expected multiple of 8",
bytes.len()
);
Err(CommonError::serialize("input", "miden", err))?;
}
Ok(bytes
.chunks(8)
.map(|bytes| Felt::try_from(u64::from_le_bytes(bytes.try_into().unwrap())))
.collect::<Result<Vec<Felt>, _>>()
.map_err(|err| CommonError::serialize("input", "miden", anyhow::anyhow!(err)))?)
}
#[cfg(test)]
mod tests {
use crate::{
compiler::MidenAsm,
program::MidenProgram,
zkvm::{EreMiden, Felt, FieldElement, bytes_to_felts, felts_to_bytes},
};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
fn load_miden_program(guest_name: &str) -> MidenProgram {
MidenAsm
.compile(&testing_guest_directory("miden", guest_name))
.unwrap()
}
#[test]
fn test_prove_and_verify_add() {
let program = load_miden_program("add");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let const_a = -Felt::ONE;
let const_b = Felt::ONE / Felt::ONE.double();
let expected_sum = const_a + const_b;
let input = felts_to_bytes(&[const_a, const_b]);
// Prove
let (prover_public_values, proof, _) = zkvm.prove(&input, ProofKind::default()).unwrap();
// Verify
let verifier_public_values = zkvm.verify(&proof).unwrap();
assert_eq!(prover_public_values, verifier_public_values);
// Assert output
let output = bytes_to_felts(&verifier_public_values).unwrap();
assert_eq!(output[0], expected_sum);
}
#[test]
fn test_prove_and_verify_fib() {
let program = load_miden_program("fib");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let n_iterations = 50u32;
let expected_fib = Felt::try_from(12_586_269_025u64).unwrap();
let input = felts_to_bytes(&[Felt::from(0u32), Felt::from(1u32), Felt::from(n_iterations)]);
// Prove
let (prover_public_values, proof, _) = zkvm.prove(&input, ProofKind::default()).unwrap();
// Verify
let verifier_public_values = zkvm.verify(&proof).unwrap();
assert_eq!(prover_public_values, verifier_public_values);
// Assert output
let output = bytes_to_felts(&verifier_public_values).unwrap();
assert_eq!(output[0], expected_fib);
}
#[test]
fn test_invalid_input() {
let program = load_miden_program("add");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let empty_inputs = Vec::new();
assert!(zkvm.execute(&empty_inputs).is_err());
let insufficient_inputs = felts_to_bytes(&[Felt::from(5u32)]);
assert!(zkvm.execute(&insufficient_inputs).is_err());
}
}

View File

@@ -0,0 +1,22 @@
use ere_zkvm_interface::zkvm::CommonError;
use miden_processor::ExecutionError;
use miden_verifier::VerificationError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
// Execute
#[error("Miden execution failed")]
Execute(#[from] ExecutionError),
// Prove
#[error("Miden proving failed: {0}")]
Prove(#[source] ExecutionError),
// Verify
#[error("Miden verification failed")]
Verify(#[from] VerificationError),
}

View File

@@ -14,12 +14,12 @@ thiserror.workspace = true
tracing.workspace = true
# Nexus dependencies
nexus-core.workspace = true
nexus-sdk.workspace = true
nexus-vm.workspace = true
nexus-core = { workspace = true, optional = true }
nexus-sdk = { workspace = true, optional = true }
nexus-vm = { workspace = true, optional = true }
# Local dependencies
ere-compile-utils.workspace = true
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]
@@ -28,6 +28,10 @@ ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = ["dep:nexus-core", "dep:nexus-sdk", "dep:nexus-vm"]
[lints]
workspace = true

View File

@@ -1,5 +1,5 @@
mod error;
mod rust_rv32i;
pub use error::Error;
pub use rust_rv32i::RustRv32i;
pub type NexusProgram = Vec<u8>;

View File

@@ -0,0 +1,8 @@
use ere_compile_utils::CommonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
}

View File

@@ -1,6 +1,6 @@
use crate::{compiler::NexusProgram, error::CompileError};
use crate::{compiler::Error, program::NexusProgram};
use ere_compile_utils::CargoBuildCmd;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use std::path::Path;
const TARGET_TRIPLE: &str = "riscv32i-unknown-none-elf";
@@ -17,7 +17,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[
pub struct RustRv32i;
impl Compiler for RustRv32i {
type Error = CompileError;
type Error = Error;
type Program = NexusProgram;
@@ -30,7 +30,7 @@ impl Compiler for RustRv32i {
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
Ok(NexusProgram { elf })
}
}
@@ -38,12 +38,12 @@ impl Compiler for RustRv32i {
mod tests {
use crate::compiler::RustRv32i;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("nexus", "basic");
let elf = RustRv32i.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
let program = RustRv32i.compile(&guest_directory).unwrap();
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,219 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{compiler::NexusProgram, error::NexusError};
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM,
};
use nexus_core::nvm::{self, ElfFile};
use nexus_sdk::{
KnownExitCodes, Prover, Verifiable, Viewable,
stwo::seq::{Proof as NexusProof, Stwo},
};
use nexus_vm::trace::Trace;
use serde::{Deserialize, Serialize};
use std::time::Instant;
use tracing::info;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod program;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
#[derive(Serialize, Deserialize)]
pub struct NexusProofBundle {
proof: NexusProof,
public_values: Vec<u8>,
}
#[cfg(feature = "zkvm")]
pub mod zkvm;
pub struct EreNexus {
elf: NexusProgram,
}
impl EreNexus {
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.");
}
Ok(Self { elf })
}
}
impl zkVM for EreNexus {
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`
let mut private_encoded = if input.is_empty() {
Vec::new()
} else {
postcard::to_stdvec_cobs(&input)
.map_err(|err| CommonError::serialize("input", "postcard", err))?
};
if !private_encoded.is_empty() {
let private_padded_len = (private_encoded.len() + 3) & !3;
assert!(private_padded_len >= private_encoded.len());
private_encoded.resize(private_padded_len, 0x00);
}
let start = Instant::now();
let (view, trace) = nvm::k_trace(elf, &[], &[], private_encoded.as_slice(), 1)
.map_err(NexusError::Execute)?;
let execution_duration = start.elapsed();
let public_values = view
.public_output()
.map_err(|err| CommonError::deserialize("public_values", "postcard", err))?;
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles: trace.get_num_steps() as u64,
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
bail!(CommonError::unsupported_proof_kind(
proof_kind,
[ProofKind::Compressed]
))
}
let elf = ElfFile::from_bytes(&self.elf).map_err(NexusError::ParseElf)?;
let prover = Stwo::new(&elf).map_err(NexusError::Prove)?;
let start = Instant::now();
let (view, proof) = prover
.prove_with_input(&input, &())
.map_err(NexusError::Prove)?;
let proving_time = start.elapsed();
let public_values = view
.public_output()
.map_err(|err| CommonError::deserialize("public_values", "postcard", err))?;
let proof_bundle = NexusProofBundle {
proof,
public_values,
};
let proof_bytes = bincode::serde::encode_to_vec(&proof_bundle, bincode::config::legacy())
.map_err(|err| CommonError::serialize("proof", "bincode", err))?;
Ok((
proof_bundle.public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
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| CommonError::deserialize("proof", "bincode", err))?;
proof_bundle
.proof
.verify_expected_from_program_bytes::<(), Vec<u8>>(
&(),
KnownExitCodes::ExitSuccess as u32,
&proof_bundle.public_values,
&self.elf,
&[],
)
.map_err(NexusError::Verify)?;
info!("Verify Succeeded!");
Ok(proof_bundle.public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
#[cfg(test)]
mod tests {
use crate::{EreNexus, NexusProgram, compiler::RustRv32i};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
fn basic_program() -> NexusProgram {
static PROGRAM: OnceLock<NexusProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32i
.compile(&testing_guest_directory("nexus", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
/// Nexus program that contains ELF of compiled guest.
#[derive(Clone, Serialize, Deserialize)]
pub struct NexusProgram {
pub(crate) elf: Vec<u8>,
}
impl NexusProgram {
pub fn elf(&self) -> &[u8] {
&self.elf
}
}

View File

@@ -0,0 +1,219 @@
use crate::program::NexusProgram;
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM,
};
use nexus_core::nvm::{self, ElfFile};
use nexus_sdk::{
KnownExitCodes, Prover, Verifiable, Viewable,
stwo::seq::{Proof as NexusProof, Stwo},
};
use nexus_vm::trace::Trace;
use serde::{Deserialize, Serialize};
use std::time::Instant;
use tracing::info;
mod error;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
#[derive(Serialize, Deserialize)]
pub struct NexusProofBundle {
proof: NexusProof,
public_values: Vec<u8>,
}
pub struct EreNexus {
program: NexusProgram,
}
impl EreNexus {
pub fn new(program: NexusProgram, resource: ProverResourceType) -> Result<Self, Error> {
if !matches!(resource, ProverResourceType::Cpu) {
panic!("Network or GPU proving not yet implemented for Nexus. Use CPU resource type.");
}
Ok(Self { program })
}
}
impl zkVM for EreNexus {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let elf = ElfFile::from_bytes(self.program.elf()).map_err(Error::ParseElf)?;
// Nexus sdk does not provide a trace, so we need to use core `nvm`
// Encoding is copied directly from `prove_with_input`
let mut private_encoded = if input.is_empty() {
Vec::new()
} else {
postcard::to_stdvec_cobs(&input)
.map_err(|err| CommonError::serialize("input", "postcard", err))?
};
if !private_encoded.is_empty() {
let private_padded_len = (private_encoded.len() + 3) & !3;
assert!(private_padded_len >= private_encoded.len());
private_encoded.resize(private_padded_len, 0x00);
}
let start = Instant::now();
let (view, trace) =
nvm::k_trace(elf, &[], &[], private_encoded.as_slice(), 1).map_err(Error::Execute)?;
let execution_duration = start.elapsed();
let public_values = view
.public_output()
.map_err(|err| CommonError::deserialize("public_values", "postcard", err))?;
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles: trace.get_num_steps() as u64,
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
bail!(CommonError::unsupported_proof_kind(
proof_kind,
[ProofKind::Compressed]
))
}
let elf = ElfFile::from_bytes(self.program.elf()).map_err(Error::ParseElf)?;
let prover = Stwo::new(&elf).map_err(Error::Prove)?;
let start = Instant::now();
let (view, proof) = prover.prove_with_input(&input, &()).map_err(Error::Prove)?;
let proving_time = start.elapsed();
let public_values = view
.public_output()
.map_err(|err| CommonError::deserialize("public_values", "postcard", err))?;
let proof_bundle = NexusProofBundle {
proof,
public_values,
};
let proof_bytes = bincode::serde::encode_to_vec(&proof_bundle, bincode::config::legacy())
.map_err(|err| CommonError::serialize("proof", "bincode", err))?;
Ok((
proof_bundle.public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
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| CommonError::deserialize("proof", "bincode", err))?;
proof_bundle
.proof
.verify_expected_from_program_bytes::<(), Vec<u8>>(
&(),
KnownExitCodes::ExitSuccess as u32,
&proof_bundle.public_values,
self.program.elf(),
&[],
)
.map_err(Error::Verify)?;
info!("Verify Succeeded!");
Ok(proof_bundle.public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
#[cfg(test)]
mod tests {
use crate::{compiler::RustRv32i, program::NexusProgram, zkvm::EreNexus};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::OnceLock;
fn basic_program() -> NexusProgram {
static PROGRAM: OnceLock<NexusProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32i
.compile(&testing_guest_directory("nexus", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
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

@@ -1,17 +1,12 @@
use ere_zkvm_interface::zkvm::CommonError;
use nexus_sdk::stwo::seq::Error as StwoError;
use nexus_vm::error::VMError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompileError {
pub enum Error {
#[error(transparent)]
CommonError(#[from] ere_compile_utils::CommonError),
}
#[derive(Debug, Error)]
pub enum NexusError {
#[error(transparent)]
CommonError(#[from] ere_zkvm_interface::CommonError),
CommonError(#[from] CommonError),
#[error("Parse ELF failed: {0}")]
ParseElf(#[source] VMError),

View File

@@ -21,7 +21,7 @@ openvm-stark-sdk.workspace = true
openvm-transpiler.workspace = true
# Local dependencies
ere-compile-utils.workspace = true
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]
@@ -31,7 +31,9 @@ ere-test-utils = { workspace = true, features = ["host"] }
ere-build-utils.workspace = true
[features]
default = []
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = []
cuda = ["openvm-sdk/cuda"]
[lints]

View File

@@ -1,54 +1,40 @@
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};
use std::{fs, path::Path};
mod error;
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use error::Error;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
#[derive(Clone, Serialize, Deserialize)]
pub struct OpenVMProgram {
pub elf: Vec<u8>,
pub app_config: AppConfig<SdkVmConfig>,
}
impl OpenVMProgram {
fn from_elf_and_app_config_path(
elf: Vec<u8>,
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(|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 {
app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_APP_LOG_BLOWUP,
)
.into(),
// By default it supports RISCV32IM with IO but no precompiles.
app_vm_config: SdkVmConfig::builder()
.system(Default::default())
.rv32i(Default::default())
.rv32m(Default::default())
.io(Default::default())
.build(),
leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_LEAF_LOG_BLOWUP,
)
.into(),
compiler_options: Default::default(),
}
};
Ok(Self { elf, app_config })
}
fn read_app_config(app_config_path: impl AsRef<Path>) -> Result<AppConfig<SdkVmConfig>, Error> {
Ok(if app_config_path.as_ref().exists() {
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 {
app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_APP_LOG_BLOWUP,
)
.into(),
// By default it supports RISCV32IM with IO but no precompiles.
app_vm_config: SdkVmConfig::builder()
.system(Default::default())
.rv32i(Default::default())
.rv32m(Default::default())
.io(Default::default())
.build(),
leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_LEAF_LOG_BLOWUP,
)
.into(),
compiler_options: Default::default(),
}
})
}

View File

@@ -0,0 +1,17 @@
use ere_compile_utils::CommonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
#[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),
}

View File

@@ -1,6 +1,9 @@
use crate::{compiler::OpenVMProgram, error::CompileError};
use crate::{
compiler::{Error, read_app_config},
program::OpenVMProgram,
};
use ere_compile_utils::CargoBuildCmd;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use std::{env, path::Path};
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
@@ -35,7 +38,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = CompileError;
type Error = Error;
type Program = OpenVMProgram;
@@ -46,21 +49,27 @@ impl Compiler for RustRv32ima {
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
OpenVMProgram::from_elf_and_app_config_path(elf, guest_directory.join("openvm.toml"))
Ok(OpenVMProgram {
elf,
app_config: read_app_config(guest_directory.join("openvm.toml"))?,
})
}
}
#[cfg(test)]
mod tests {
use crate::{EreOpenVM, compiler::RustRv32ima};
use crate::{compiler::RustRv32ima, zkvm::EreOpenVM};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProverResourceType, zkVM},
};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("openvm", "stock_nightly_no_std");
let program = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
#[test]

View File

@@ -1,6 +1,9 @@
use crate::{compiler::OpenVMProgram, error::CompileError};
use crate::{
compiler::{Error, read_app_config},
program::OpenVMProgram,
};
use ere_compile_utils::CommonError;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use openvm_build::GuestOptions;
use std::{fs, path::Path};
@@ -9,7 +12,7 @@ use std::{fs, path::Path};
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = CompileError;
type Error = Error;
type Program = OpenVMProgram;
@@ -19,16 +22,19 @@ impl Compiler for RustRv32imaCustomized {
let guest_opts = GuestOptions::default().with_profile("release".to_string());
let target_dir = match openvm_build::build_guest_package(&pkg, &guest_opts, None, &None) {
Ok(target_dir) => target_dir,
Err(Some(code)) => return Err(CompileError::BuildFailed(code))?,
Err(None) => return Err(CompileError::BuildSkipped)?,
Err(Some(code)) => return Err(Error::BuildFailed(code))?,
Err(None) => return Err(Error::BuildSkipped)?,
};
let elf_path = openvm_build::find_unique_executable(guest_directory, target_dir, &None)
.map_err(CompileError::UniqueElfNotFound)?;
.map_err(Error::UniqueElfNotFound)?;
let elf =
fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?;
OpenVMProgram::from_elf_and_app_config_path(elf, guest_directory.join("openvm.toml"))
Ok(OpenVMProgram {
elf,
app_config: read_app_config(guest_directory.join("openvm.toml"))?,
})
}
}
@@ -36,12 +42,12 @@ impl Compiler for RustRv32imaCustomized {
mod tests {
use crate::compiler::RustRv32imaCustomized;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("openvm", "basic");
let program = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,286 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{compiler::OpenVMProgram, error::OpenVMError};
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use openvm_circuit::arch::instructions::exe::VmExe;
use openvm_continuations::verifier::internal::types::VmStarkProof;
use openvm_sdk::{
CpuSdk, F, SC, StdIn,
codec::{Decode, Encode},
commit::AppExecutionCommit,
config::{AppConfig, SdkVmConfig},
fs::read_object_from_file,
keygen::{AggProvingKey, AggVerifyingKey, AppProvingKey},
};
use openvm_stark_sdk::openvm_stark_backend::p3_field::PrimeField32;
use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE};
use std::{env, path::PathBuf, sync::Arc, time::Instant};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod program;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
pub struct EreOpenVM {
app_config: AppConfig<SdkVmConfig>,
app_exe: Arc<VmExe<F>>,
app_pk: AppProvingKey<SdkVmConfig>,
agg_pk: AggProvingKey,
agg_vk: AggVerifyingKey,
app_commit: AppExecutionCommit,
resource: ProverResourceType,
}
#[cfg(feature = "zkvm")]
pub mod zkvm;
impl EreOpenVM {
pub fn new(program: OpenVMProgram, resource: ProverResourceType) -> Result<Self, OpenVMError> {
match resource {
#[cfg(not(feature = "cuda"))]
ProverResourceType::Gpu => {
panic!("Feature `cuda` is disabled. Enable `cuda` to use GPU resource type")
}
ProverResourceType::Network(_) => {
panic!(
"Network proving not yet implemented for OpenVM. Use CPU or GPU resource type."
);
}
_ => {}
}
let sdk = CpuSdk::new(program.app_config.clone()).map_err(OpenVMError::SdkInit)?;
let elf = Elf::decode(&program.elf, MEM_SIZE as u32).map_err(OpenVMError::ElfDecode)?;
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(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(OpenVMError::ProverInit)?
.app_commit();
Ok(Self {
app_config: program.app_config,
app_exe,
app_pk,
agg_pk,
agg_vk,
app_commit,
resource,
})
}
fn cpu_sdk(&self) -> Result<CpuSdk, OpenVMError> {
let sdk = CpuSdk::new_without_transpiler(self.app_config.clone())
.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, OpenVMError> {
let sdk = openvm_sdk::GpuSdk::new_without_transpiler(self.app_config.clone())
.map_err(OpenVMError::SdkInit)?;
let _ = sdk.set_app_pk(self.app_pk.clone());
let _ = sdk.set_agg_pk(self.agg_pk.clone());
Ok(sdk)
}
}
impl zkVM for EreOpenVM {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let mut stdin = StdIn::default();
stdin.write_bytes(input);
let start = Instant::now();
let public_values = self
.cpu_sdk()?
.execute(self.app_exe.clone(), stdin)
.map_err(OpenVMError::Execute)?;
Ok((
public_values,
ProgramExecutionReport {
execution_duration: start.elapsed(),
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
bail!(CommonError::unsupported_proof_kind(
proof_kind,
[ProofKind::Compressed]
))
}
let mut stdin = StdIn::default();
stdin.write_bytes(input);
let now = std::time::Instant::now();
let (proof, app_commit) = match self.resource {
ProverResourceType::Cpu => self.cpu_sdk()?.prove(self.app_exe.clone(), stdin),
#[cfg(feature = "cuda")]
ProverResourceType::Gpu => self.gpu_sdk()?.prove(self.app_exe.clone(), stdin),
#[cfg(not(feature = "cuda"))]
ProverResourceType::Gpu => {
panic!("Feature `cuda` is disabled. Enable `cuda` to use GPU resource type")
}
ProverResourceType::Network(_) => {
panic!(
"Network proving not yet implemented for OpenVM. Use CPU or GPU resource type."
);
}
}
.map_err(OpenVMError::Prove)?;
let elapsed = now.elapsed();
if app_commit != self.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(|err| CommonError::serialize("proof", "openvm_sdk", err))?;
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(elapsed),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
bail!(CommonError::unsupported_proof_kind(
proof.kind(),
[ProofKind::Compressed]
))
};
let proof = VmStarkProof::<SC>::decode(&mut proof.as_slice())
.map_err(|err| CommonError::deserialize("proof", "openvm_sdk", err))?;
CpuSdk::verify_proof(&self.agg_vk, self.app_commit, &proof).map_err(OpenVMError::Verify)?;
let public_values = extract_public_values(&proof.user_public_values)?;
Ok(public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreOpenVM {
type ProgramDigest = AppExecutionCommit;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.app_commit)
}
}
/// Extract public values in bytes from field elements.
///
/// 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>, OpenVMError> {
user_public_values
.iter()
.map(|v| u8::try_from(v.as_canonical_u32()).ok())
.collect::<Option<_>>()
.ok_or(OpenVMError::InvalidPublicValue)
}
fn agg_pk_path() -> PathBuf {
PathBuf::from(std::env::var("HOME").expect("env `$HOME` should be set"))
.join(".openvm/agg_stark.pk")
}
#[cfg(test)]
mod tests {
use crate::{
EreOpenVM,
compiler::{OpenVMProgram, RustRv32imaCustomized},
};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
fn basic_program() -> OpenVMProgram {
static PROGRAM: OnceLock<OpenVMProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32imaCustomized
.compile(&testing_guest_directory("openvm", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,19 @@
use openvm_sdk::config::{AppConfig, SdkVmConfig};
use serde::{Deserialize, Serialize};
/// OpenVM program that contains ELF of compiled guest and app config.
#[derive(Clone, Serialize, Deserialize)]
pub struct OpenVMProgram {
pub(crate) elf: Vec<u8>,
pub(crate) app_config: AppConfig<SdkVmConfig>,
}
impl OpenVMProgram {
pub fn elf(&self) -> &[u8] {
&self.elf
}
pub fn app_config(&self) -> &AppConfig<SdkVmConfig> {
&self.app_config
}
}

View File

@@ -0,0 +1,285 @@
use crate::program::OpenVMProgram;
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use openvm_circuit::arch::instructions::exe::VmExe;
use openvm_continuations::verifier::internal::types::VmStarkProof;
use openvm_sdk::{
CpuSdk, F, SC, StdIn,
codec::{Decode, Encode},
commit::AppExecutionCommit,
config::{AppConfig, SdkVmConfig},
fs::read_object_from_file,
keygen::{AggProvingKey, AggVerifyingKey, AppProvingKey},
};
use openvm_stark_sdk::openvm_stark_backend::p3_field::PrimeField32;
use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE};
use std::{env, path::PathBuf, sync::Arc, time::Instant};
mod error;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub struct EreOpenVM {
app_config: AppConfig<SdkVmConfig>,
app_exe: Arc<VmExe<F>>,
app_pk: AppProvingKey<SdkVmConfig>,
agg_pk: AggProvingKey,
agg_vk: AggVerifyingKey,
app_commit: AppExecutionCommit,
resource: ProverResourceType,
}
impl EreOpenVM {
pub fn new(program: OpenVMProgram, resource: ProverResourceType) -> Result<Self, Error> {
match resource {
#[cfg(not(feature = "cuda"))]
ProverResourceType::Gpu => {
panic!("Feature `cuda` is disabled. Enable `cuda` to use GPU resource type")
}
ProverResourceType::Network(_) => {
panic!(
"Network proving not yet implemented for OpenVM. Use CPU or GPU resource type."
);
}
_ => {}
}
let sdk = CpuSdk::new(program.app_config().clone()).map_err(Error::SdkInit)?;
let elf = Elf::decode(program.elf(), MEM_SIZE as u32).map_err(Error::ElfDecode)?;
let app_exe = sdk.convert_to_exe(elf).map_err(Error::Transpile)?;
let (app_pk, _) = sdk.app_keygen();
let agg_pk = read_object_from_file::<AggProvingKey, _>(agg_pk_path())
.map_err(Error::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(Error::ProverInit)?
.app_commit();
Ok(Self {
app_config: program.app_config,
app_exe,
app_pk,
agg_pk,
agg_vk,
app_commit,
resource,
})
}
fn cpu_sdk(&self) -> Result<CpuSdk, Error> {
let sdk =
CpuSdk::new_without_transpiler(self.app_config.clone()).map_err(Error::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, Error> {
let sdk = openvm_sdk::GpuSdk::new_without_transpiler(self.app_config.clone())
.map_err(Error::SdkInit)?;
let _ = sdk.set_app_pk(self.app_pk.clone());
let _ = sdk.set_agg_pk(self.agg_pk.clone());
Ok(sdk)
}
}
impl zkVM for EreOpenVM {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let mut stdin = StdIn::default();
stdin.write_bytes(input);
let start = Instant::now();
let public_values = self
.cpu_sdk()?
.execute(self.app_exe.clone(), stdin)
.map_err(Error::Execute)?;
Ok((
public_values,
ProgramExecutionReport {
execution_duration: start.elapsed(),
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
bail!(CommonError::unsupported_proof_kind(
proof_kind,
[ProofKind::Compressed]
))
}
let mut stdin = StdIn::default();
stdin.write_bytes(input);
let now = std::time::Instant::now();
let (proof, app_commit) = match self.resource {
ProverResourceType::Cpu => self.cpu_sdk()?.prove(self.app_exe.clone(), stdin),
#[cfg(feature = "cuda")]
ProverResourceType::Gpu => self.gpu_sdk()?.prove(self.app_exe.clone(), stdin),
#[cfg(not(feature = "cuda"))]
ProverResourceType::Gpu => {
panic!("Feature `cuda` is disabled. Enable `cuda` to use GPU resource type")
}
ProverResourceType::Network(_) => {
panic!(
"Network proving not yet implemented for OpenVM. Use CPU or GPU resource type."
);
}
}
.map_err(Error::Prove)?;
let elapsed = now.elapsed();
if app_commit != self.app_commit {
bail!(Error::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(|err| CommonError::serialize("proof", "openvm_sdk", err))?;
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(elapsed),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
bail!(CommonError::unsupported_proof_kind(
proof.kind(),
[ProofKind::Compressed]
))
};
let proof = VmStarkProof::<SC>::decode(&mut proof.as_slice())
.map_err(|err| CommonError::deserialize("proof", "openvm_sdk", err))?;
CpuSdk::verify_proof(&self.agg_vk, self.app_commit, &proof).map_err(Error::Verify)?;
let public_values = extract_public_values(&proof.user_public_values)?;
Ok(public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreOpenVM {
type ProgramDigest = AppExecutionCommit;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.app_commit)
}
}
/// Extract public values in bytes from field elements.
///
/// 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>, Error> {
user_public_values
.iter()
.map(|v| u8::try_from(v.as_canonical_u32()).ok())
.collect::<Option<_>>()
.ok_or(Error::InvalidPublicValue)
}
fn agg_pk_path() -> PathBuf {
PathBuf::from(std::env::var("HOME").expect("env `$HOME` should be set"))
.join(".openvm/agg_stark.pk")
}
#[cfg(test)]
mod tests {
use crate::{compiler::RustRv32imaCustomized, program::OpenVMProgram, zkvm::EreOpenVM};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::OnceLock;
fn basic_program() -> OpenVMProgram {
static PROGRAM: OnceLock<OpenVMProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32imaCustomized
.compile(&testing_guest_directory("openvm", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}

View File

@@ -1,25 +1,11 @@
use ere_zkvm_interface::zkvm::CommonError;
use openvm_sdk::{SdkError, commit::AppExecutionCommit};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompileError {
pub enum Error {
#[error(transparent)]
CommonError(#[from] ere_compile_utils::CommonError),
#[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)]
CommonError(#[from] ere_zkvm_interface::CommonError),
CommonError(#[from] CommonError),
// Common
#[error("Initialize SDK failed: {0}")]

View File

@@ -14,11 +14,11 @@ tempfile.workspace = true
thiserror.workspace = true
# Pico dependencies
pico-p3-field.workspace = true
pico-vm.workspace = true
pico-p3-field = { workspace = true, optional = true }
pico-vm = { workspace = true, optional = true }
# Local dependencies
ere-compile-utils.workspace = true
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]
@@ -27,5 +27,10 @@ ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = ["dep:pico-p3-field", "dep:pico-vm"]
[lints]
workspace = true

View File

@@ -1,7 +1,7 @@
mod error;
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use error::Error;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
pub type PicoProgram = Vec<u8>;

View File

@@ -0,0 +1,8 @@
use ere_compile_utils::CommonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
}

View File

@@ -1,6 +1,6 @@
use crate::{compiler::PicoProgram, error::CompileError};
use crate::{compiler::Error, program::PicoProgram};
use ere_compile_utils::CargoBuildCmd;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use std::{env, path::Path};
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
@@ -32,7 +32,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = CompileError;
type Error = Error;
type Program = PicoProgram;
@@ -43,21 +43,24 @@ impl Compiler for RustRv32ima {
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
Ok(PicoProgram { elf })
}
}
#[cfg(test)]
mod tests {
use crate::{ErePico, compiler::RustRv32ima};
use crate::{compiler::RustRv32ima, zkvm::ErePico};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProverResourceType, zkVM},
};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std");
let elf = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
let program = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
#[test]

View File

@@ -1,6 +1,6 @@
use crate::error::CompileError;
use crate::{compiler::Error, program::PicoProgram};
use ere_compile_utils::{CommonError, cargo_metadata};
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use std::{fs, path::Path, process::Command};
use tempfile::tempdir;
@@ -9,9 +9,9 @@ use tempfile::tempdir;
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = CompileError;
type Error = Error;
type Program = Vec<u8>;
type Program = PicoProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let tempdir = tempdir().map_err(CommonError::tempdir)?;
@@ -32,10 +32,10 @@ impl Compiler for RustRv32imaCustomized {
}
let elf_path = tempdir.path().join("riscv32im-pico-zkvm-elf");
let elf_bytes =
let elf =
fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?;
Ok(elf_bytes)
Ok(PicoProgram { elf })
}
}
@@ -43,12 +43,12 @@ impl Compiler for RustRv32imaCustomized {
mod tests {
use crate::compiler::RustRv32imaCustomized;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("pico", "basic");
let elf = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
let program = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,244 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{
client::{BaseVerifyingKey, MetaProof, ProverClient},
compiler::PicoProgram,
error::PicoError,
};
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use pico_p3_field::PrimeField32;
use pico_vm::emulator::stdin::EmulatorStdinBuilder;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::{env, panic, time::Instant};
pub mod program;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod client;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
#[derive(Serialize, Deserialize)]
pub struct PicoProofWithPublicValues {
proof: MetaProof,
public_values: Vec<u8>,
}
#[cfg(feature = "zkvm")]
pub mod zkvm;
pub struct ErePico {
program: PicoProgram,
}
impl ErePico {
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.");
}
Ok(ErePico { program })
}
pub fn client(&self) -> ProverClient {
ProverClient::new(&self.program)
}
}
impl zkVM for ErePico {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let mut stdin = EmulatorStdinBuilder::default();
stdin.write_slice(input);
let ((total_num_cycles, public_values), execution_duration) = panic::catch_unwind(|| {
let client = self.client();
let start = Instant::now();
let result = client.execute(stdin);
(result, start.elapsed())
})
.map_err(|err| PicoError::ExecutePanic(panic_msg(err)))?;
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles,
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
bail!(CommonError::unsupported_proof_kind(
proof_kind,
[ProofKind::Compressed]
))
}
let mut stdin = EmulatorStdinBuilder::default();
stdin.write_slice(input);
let ((public_values, proof), proving_time) = panic::catch_unwind(|| {
let client = self.client();
let start = Instant::now();
let result = client.prove(stdin)?;
Ok((result, start.elapsed()))
})
.map_err(|err| PicoError::ProvePanic(panic_msg(err)))?
.map_err(PicoError::Prove)?;
let proof_bytes = bincode::serde::encode_to_vec(
&PicoProofWithPublicValues {
proof,
public_values: public_values.clone(),
},
bincode::config::legacy(),
)
.map_err(|err| CommonError::serialize("proof", "bincode", err))?;
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
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| CommonError::deserialize("proof", "bincode", err))?;
client.verify(&proof.proof).map_err(PicoError::Verify)?;
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)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for ErePico {
type ProgramDigest = BaseVerifyingKey;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.client().vk().clone())
}
}
/// 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], PicoError> {
if proof.proofs().len() != 1 {
return Err(PicoError::InvalidBaseProofLength(proof.proofs().len()));
}
if proof.proofs()[0].public_values.len() < 32 {
return Err(PicoError::InvalidPublicValuesLength(
proof.proofs()[0].public_values.len(),
));
}
Ok(proof.proofs()[0].public_values[..32]
.iter()
.map(|value| u8::try_from(value.as_canonical_u32()))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| PicoError::InvalidPublicValues)?
.try_into()
.unwrap())
}
fn panic_msg(err: Box<dyn std::any::Any + Send + 'static>) -> String {
None.or_else(|| err.downcast_ref::<String>().cloned())
.or_else(|| err.downcast_ref::<&'static str>().map(ToString::to_string))
.unwrap_or_else(|| "unknown panic msg".to_string())
}
#[cfg(test)]
mod tests {
use crate::{
ErePico,
compiler::{PicoProgram, RustRv32imaCustomized},
};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
static BASIC_PROGRAM: OnceLock<PicoProgram> = OnceLock::new();
fn basic_program() -> PicoProgram {
BASIC_PROGRAM
.get_or_init(|| {
RustRv32imaCustomized
.compile(&testing_guest_directory("pico", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
/// Pico program that contains ELF of compiled guest.
#[derive(Clone, Serialize, Deserialize)]
pub struct PicoProgram {
pub(crate) elf: Vec<u8>,
}
impl PicoProgram {
pub fn elf(&self) -> &[u8] {
&self.elf
}
}

View File

@@ -0,0 +1,241 @@
use crate::{
program::PicoProgram,
zkvm::sdk::{BaseVerifyingKey, MetaProof, ProverClient},
};
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use pico_p3_field::PrimeField32;
use pico_vm::emulator::stdin::EmulatorStdinBuilder;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::{env, panic, time::Instant};
mod error;
mod sdk;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
#[derive(Serialize, Deserialize)]
pub struct PicoProofWithPublicValues {
proof: MetaProof,
public_values: Vec<u8>,
}
pub struct ErePico {
program: PicoProgram,
}
impl ErePico {
pub fn new(program: PicoProgram, resource: ProverResourceType) -> Result<Self, Error> {
if !matches!(resource, ProverResourceType::Cpu) {
panic!("Network or GPU proving not yet implemented for Pico. Use CPU resource type.");
}
Ok(ErePico { program })
}
pub fn client(&self) -> ProverClient {
ProverClient::new(self.program.elf())
}
}
impl zkVM for ErePico {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let mut stdin = EmulatorStdinBuilder::default();
stdin.write_slice(input);
let ((total_num_cycles, public_values), execution_duration) = panic::catch_unwind(|| {
let client = self.client();
let start = Instant::now();
let result = client.execute(stdin);
(result, start.elapsed())
})
.map_err(|err| Error::ExecutePanic(panic_msg(err)))?;
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles,
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
if proof_kind != ProofKind::Compressed {
bail!(CommonError::unsupported_proof_kind(
proof_kind,
[ProofKind::Compressed]
))
}
let mut stdin = EmulatorStdinBuilder::default();
stdin.write_slice(input);
let ((public_values, proof), proving_time) = panic::catch_unwind(|| {
let client = self.client();
let start = Instant::now();
let result = client.prove(stdin)?;
Ok((result, start.elapsed()))
})
.map_err(|err| Error::ProvePanic(panic_msg(err)))?
.map_err(Error::Prove)?;
let proof_bytes = bincode::serde::encode_to_vec(
&PicoProofWithPublicValues {
proof,
public_values: public_values.clone(),
},
bincode::config::legacy(),
)
.map_err(|err| CommonError::serialize("proof", "bincode", err))?;
Ok((
public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let Proof::Compressed(proof) = proof else {
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| CommonError::deserialize("proof", "bincode", err))?;
client.verify(&proof.proof).map_err(Error::Verify)?;
let claimed = <[u8; 32]>::from(Sha256::digest(&proof.public_values));
let proved = extract_public_values_sha256_digest(&proof.proof)?;
if claimed != proved {
bail!(Error::UnexpectedPublicValuesDigest { claimed, proved });
}
Ok(proof.public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for ErePico {
type ProgramDigest = BaseVerifyingKey;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.client().vk().clone())
}
}
/// 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], Error> {
if proof.proofs().len() != 1 {
return Err(Error::InvalidBaseProofLength(proof.proofs().len()));
}
if proof.proofs()[0].public_values.len() < 32 {
return Err(Error::InvalidPublicValuesLength(
proof.proofs()[0].public_values.len(),
));
}
Ok(proof.proofs()[0].public_values[..32]
.iter()
.map(|value| u8::try_from(value.as_canonical_u32()))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| Error::InvalidPublicValues)?
.try_into()
.unwrap())
}
fn panic_msg(err: Box<dyn std::any::Any + Send + 'static>) -> String {
None.or_else(|| err.downcast_ref::<String>().cloned())
.or_else(|| err.downcast_ref::<&'static str>().map(ToString::to_string))
.unwrap_or_else(|| "unknown panic msg".to_string())
}
#[cfg(test)]
mod tests {
use crate::{compiler::RustRv32imaCustomized, program::PicoProgram, zkvm::ErePico};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::OnceLock;
fn basic_program() -> PicoProgram {
static PROGRAM: OnceLock<PicoProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32imaCustomized
.compile(&testing_guest_directory("pico", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
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

@@ -1,15 +1,10 @@
use ere_zkvm_interface::zkvm::CommonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompileError {
pub enum Error {
#[error(transparent)]
CommonError(#[from] ere_compile_utils::CommonError),
}
#[derive(Debug, Error)]
pub enum PicoError {
#[error(transparent)]
CommonError(#[from] ere_zkvm_interface::CommonError),
CommonError(#[from] CommonError),
// Execute
#[error("Pico execution panicked: {0}")]

View File

@@ -3,7 +3,7 @@
// on chain. Issue for tracking: https://github.com/eth-act/ere/issues/140.
use anyhow::{Error, Ok, Result};
use ere_zkvm_interface::PublicValues;
use ere_zkvm_interface::zkvm::PublicValues;
use pico_vm::{
compiler::riscv::program::Program,
configs::{config::StarkGenericConfig, stark_config::KoalaBearPoseidon2},

View File

@@ -15,11 +15,11 @@ tracing.workspace = true
# Risc0 dependencies
risc0-build = { workspace = true, features = ["unstable"] }
risc0-zkp.workspace = true
risc0-zkvm = { workspace = true, features = ["client", "unstable"] }
risc0-zkvm = { workspace = true, features = ["client", "unstable"], optional = true }
risc0-binfmt.workspace = true
# Local dependencies
ere-compile-utils.workspace = true
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]
@@ -30,6 +30,9 @@ ere-test-utils = { workspace = true, features = ["host"] }
ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = ["dep:risc0-zkvm"]
metal = ["risc0-zkvm/metal"]
[lints]

View File

@@ -1,14 +1,7 @@
use risc0_zkvm::Digest;
use serde::{Deserialize, Serialize};
mod error;
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use error::Error;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Risc0Program {
pub(crate) elf: Vec<u8>,
pub(crate) image_id: Digest,
}

View File

@@ -0,0 +1,22 @@
use ere_compile_utils::CommonError;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] 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),
}

View File

@@ -1,6 +1,6 @@
use crate::{compiler::Risc0Program, error::CompileError};
use crate::{compiler::Error, program::Risc0Program};
use ere_compile_utils::CargoBuildCmd;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use risc0_binfmt::ProgramBinary;
use std::{env, path::Path};
use tracing::info;
@@ -32,7 +32,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = CompileError;
type Error = Error;
type Program = Risc0Program;
@@ -47,7 +47,7 @@ impl Compiler for RustRv32ima {
let program = ProgramBinary::new(elf.as_slice(), V1COMPAT_ELF);
let image_id = program
.compute_image_id()
.map_err(CompileError::ImageIDCalculationFailure)?;
.map_err(Error::ImageIDCalculationFailure)?;
info!("Risc0 program compiled OK - {} bytes", elf.len());
info!("Image ID - {image_id}");
@@ -61,9 +61,12 @@ impl Compiler for RustRv32ima {
#[cfg(test)]
mod tests {
use crate::{EreRisc0, compiler::RustRv32ima};
use crate::{compiler::RustRv32ima, zkvm::EreRisc0};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProverResourceType, zkVM},
};
#[test]
fn test_compile() {

View File

@@ -1,6 +1,6 @@
use crate::{compiler::Risc0Program, error::CompileError};
use crate::{compiler::Error, program::Risc0Program};
use ere_compile_utils::cargo_metadata;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use risc0_build::GuestOptions;
use std::path::Path;
use tracing::info;
@@ -10,7 +10,7 @@ use tracing::info;
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = CompileError;
type Error = Error;
type Program = Risc0Program;
@@ -27,13 +27,13 @@ impl Compiler for RustRv32imaCustomized {
&metadata.target_directory,
GuestOptions::default(),
)
.map_err(|err| CompileError::BuildFailure {
.map_err(|err| Error::BuildFailure {
err,
guest_path: guest_directory.to_path_buf(),
})?
.into_iter()
.next()
.ok_or(CompileError::Risc0BuildMissingGuest)?;
.ok_or(Error::Risc0BuildMissingGuest)?;
let elf = guest.elf.to_vec();
let image_id = guest.image_id;
@@ -49,7 +49,7 @@ impl Compiler for RustRv32imaCustomized {
mod tests {
use crate::compiler::RustRv32imaCustomized;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {

View File

@@ -1,303 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{compiler::Risc0Program, error::Risc0Error};
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use risc0_zkvm::{
DEFAULT_MAX_PO2, DefaultProver, Digest, ExecutorEnv, ExternalProver, InnerReceipt, ProverOpts,
Receipt, default_executor, default_prover,
};
use std::{env, ops::RangeInclusive, rc::Rc, time::Instant};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod program;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
/// Default logarithmic segment size from [`DEFAULT_SEGMENT_LIMIT_PO2`].
///
/// [`DEFAULT_SEGMENT_LIMIT_PO2`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/rv32im/src/execute/mod.rs#L39.
const DEFAULT_SEGMENT_PO2: usize = 20;
#[cfg(feature = "zkvm")]
pub mod zkvm;
/// Supported range of logarithmic segment size.
///
/// The minimum is by [`MIN_LIFT_PO2`] to be lifted.
///
/// The maximum is by [`DEFAULT_MAX_PO2`], although the real maximum is `24`,
/// but it requires us to set the `control_ids` manually in the `ProverOpts`.
///
/// [`MIN_LIFT_PO2`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/recursion/src/control_id.rs#L19
/// [`DEFAULT_MAX_PO2`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/zkvm/src/receipt.rs#L884
const SEGMENT_PO2_RANGE: RangeInclusive<usize> = 14..=DEFAULT_MAX_PO2;
/// Default logarithmic keccak size from [`KECCAK_DEFAULT_PO2`].
///
/// [`KECCAK_DEFAULT_PO2`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/keccak/src/lib.rs#L27.
const DEFAULT_KECCAK_PO2: usize = 17;
/// Supported range of logarithmic keccak size from [`KECCAK_PO2_RANGE`].
///
/// [`KECCAK_PO2_RANGE`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/keccak/src/lib.rs#L29.
const KECCAK_PO2_RANGE: RangeInclusive<usize> = 14..=18;
pub struct EreRisc0 {
program: Risc0Program,
resource: ProverResourceType,
segment_po2: usize,
keccak_po2: usize,
}
impl EreRisc0 {
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."
);
}
let [segment_po2, keccak_po2] = [
("RISC0_SEGMENT_PO2", DEFAULT_SEGMENT_PO2, SEGMENT_PO2_RANGE),
("RISC0_KECCAK_PO2", DEFAULT_KECCAK_PO2, KECCAK_PO2_RANGE),
]
.map(|(key, default, range)| {
let val = env::var(key)
.ok()
.and_then(|po2| po2.parse::<usize>().ok())
.unwrap_or(default);
if !range.contains(&val) {
panic!("Unsupported po2 value {val} of {key}, expected in range {range:?}")
}
val
});
Ok(Self {
program,
resource,
segment_po2,
keccak_po2,
})
}
}
impl zkVM for EreRisc0 {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let executor = default_executor();
let env = ExecutorEnv::builder()
.write_slice(input)
.build()
.map_err(Risc0Error::BuildExecutorEnv)?;
let start = Instant::now();
let session_info = executor
.execute(env, &self.program.elf)
.map_err(Risc0Error::Execute)?;
let public_values = session_info.journal.bytes.clone();
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles: session_info.cycles() as u64,
execution_duration: start.elapsed(),
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
let prover = match self.resource {
ProverResourceType::Cpu => Rc::new(ExternalProver::new("ipc", "r0vm")),
ProverResourceType::Gpu => {
if cfg!(feature = "metal") {
// When `metal` is enabled, we use the `LocalProver` to do
// proving. but it's not public so we use `default_prover`
// to instantiate it.
default_prover()
} else {
// The `DefaultProver` uses `r0vm-cuda` to spawn multiple
// 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(Risc0Error::InitializeCudaProver)?,
)
}
}
ProverResourceType::Network(_) => {
panic!(
"Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type."
);
}
};
let env = ExecutorEnv::builder()
.write_slice(input)
.segment_limit_po2(self.segment_po2 as _)
.keccak_max_po2(self.keccak_po2 as _)
.and_then(|builder| builder.build())
.map_err(Risc0Error::BuildExecutorEnv)?;
let opts = match proof_kind {
ProofKind::Compressed => ProverOpts::succinct(),
ProofKind::Groth16 => ProverOpts::groth16(),
};
let now = Instant::now();
let prove_info = prover
.prove_with_opts(env, &self.program.elf, &opts)
.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(|err| CommonError::serialize("proof", "borsh", err))?,
);
Ok((
public_values,
proof,
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let proof_kind = proof.kind();
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(_))
) {
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(Risc0Error::Verify)?;
let public_values = receipt.journal.bytes.clone();
Ok(public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreRisc0 {
type ProgramDigest = Digest;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.program.image_id)
}
}
#[cfg(test)]
mod tests {
use crate::{
EreRisc0,
compiler::{Risc0Program, RustRv32imaCustomized},
};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
static BASIC_PROGRAM: OnceLock<Risc0Program> = OnceLock::new();
fn basic_program() -> Risc0Program {
BASIC_PROGRAM
.get_or_init(|| {
RustRv32imaCustomized
.compile(&testing_guest_directory("risc0", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
#[test]
fn test_aligned_allocs() {
let program = RustRv32imaCustomized
.compile(&testing_guest_directory("risc0", "allocs_alignment"))
.unwrap();
for i in 1..=16_u32 {
let zkvm = EreRisc0::new(program.clone(), ProverResourceType::Cpu).unwrap();
let input = i.to_le_bytes();
if i.is_power_of_two() {
zkvm.execute(&input)
.expect("Power of two alignment should execute successfully");
} else {
zkvm.execute(&input)
.expect_err("Non-power of two aligment is expected to fail");
}
}
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,19 @@
use risc0_zkp::core::digest::Digest;
use serde::{Deserialize, Serialize};
/// Risc0 program that contains ELF of compiled guest and image ID.
#[derive(Clone, Serialize, Deserialize)]
pub struct Risc0Program {
pub(crate) elf: Vec<u8>,
pub(crate) image_id: Digest,
}
impl Risc0Program {
pub fn elf(&self) -> &[u8] {
&self.elf
}
pub fn image_id(&self) -> &Digest {
&self.image_id
}
}

View File

@@ -0,0 +1,298 @@
use crate::program::Risc0Program;
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use risc0_zkvm::{
DEFAULT_MAX_PO2, DefaultProver, Digest, ExecutorEnv, ExternalProver, InnerReceipt, ProverOpts,
Receipt, default_executor, default_prover,
};
use std::{env, ops::RangeInclusive, rc::Rc, time::Instant};
mod error;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
/// Default logarithmic segment size from [`DEFAULT_SEGMENT_LIMIT_PO2`].
///
/// [`DEFAULT_SEGMENT_LIMIT_PO2`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/rv32im/src/execute/mod.rs#L39.
const DEFAULT_SEGMENT_PO2: usize = 20;
/// Supported range of logarithmic segment size.
///
/// The minimum is by [`MIN_LIFT_PO2`] to be lifted.
///
/// The maximum is by [`DEFAULT_MAX_PO2`], although the real maximum is `24`,
/// but it requires us to set the `control_ids` manually in the `ProverOpts`.
///
/// [`MIN_LIFT_PO2`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/recursion/src/control_id.rs#L19
/// [`DEFAULT_MAX_PO2`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/zkvm/src/receipt.rs#L884
const SEGMENT_PO2_RANGE: RangeInclusive<usize> = 14..=DEFAULT_MAX_PO2;
/// Default logarithmic keccak size from [`KECCAK_DEFAULT_PO2`].
///
/// [`KECCAK_DEFAULT_PO2`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/keccak/src/lib.rs#L27.
const DEFAULT_KECCAK_PO2: usize = 17;
/// Supported range of logarithmic keccak size from [`KECCAK_PO2_RANGE`].
///
/// [`KECCAK_PO2_RANGE`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/keccak/src/lib.rs#L29.
const KECCAK_PO2_RANGE: RangeInclusive<usize> = 14..=18;
pub struct EreRisc0 {
program: Risc0Program,
resource: ProverResourceType,
segment_po2: usize,
keccak_po2: usize,
}
impl EreRisc0 {
pub fn new(program: Risc0Program, resource: ProverResourceType) -> Result<Self, Error> {
if matches!(resource, ProverResourceType::Network(_)) {
panic!(
"Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type."
);
}
let [segment_po2, keccak_po2] = [
("RISC0_SEGMENT_PO2", DEFAULT_SEGMENT_PO2, SEGMENT_PO2_RANGE),
("RISC0_KECCAK_PO2", DEFAULT_KECCAK_PO2, KECCAK_PO2_RANGE),
]
.map(|(key, default, range)| {
let val = env::var(key)
.ok()
.and_then(|po2| po2.parse::<usize>().ok())
.unwrap_or(default);
if !range.contains(&val) {
panic!("Unsupported po2 value {val} of {key}, expected in range {range:?}")
}
val
});
Ok(Self {
program,
resource,
segment_po2,
keccak_po2,
})
}
}
impl zkVM for EreRisc0 {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let executor = default_executor();
let env = ExecutorEnv::builder()
.write_slice(input)
.build()
.map_err(Error::BuildExecutorEnv)?;
let start = Instant::now();
let session_info = executor
.execute(env, &self.program.elf)
.map_err(Error::Execute)?;
let public_values = session_info.journal.bytes.clone();
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles: session_info.cycles() as u64,
execution_duration: start.elapsed(),
..Default::default()
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
let prover = match self.resource {
ProverResourceType::Cpu => Rc::new(ExternalProver::new("ipc", "r0vm")),
ProverResourceType::Gpu => {
if cfg!(feature = "metal") {
// When `metal` is enabled, we use the `LocalProver` to do
// proving. but it's not public so we use `default_prover`
// to instantiate it.
default_prover()
} else {
// The `DefaultProver` uses `r0vm-cuda` to spawn multiple
// 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(Error::InitializeCudaProver)?)
}
}
ProverResourceType::Network(_) => {
panic!(
"Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type."
);
}
};
let env = ExecutorEnv::builder()
.write_slice(input)
.segment_limit_po2(self.segment_po2 as _)
.keccak_max_po2(self.keccak_po2 as _)
.and_then(|builder| builder.build())
.map_err(Error::BuildExecutorEnv)?;
let opts = match proof_kind {
ProofKind::Compressed => ProverOpts::succinct(),
ProofKind::Groth16 => ProverOpts::groth16(),
};
let now = Instant::now();
let prove_info = prover
.prove_with_opts(env, &self.program.elf, &opts)
.map_err(Error::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(|err| CommonError::serialize("proof", "borsh", err))?,
);
Ok((
public_values,
proof,
ProgramProvingReport::new(proving_time),
))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let proof_kind = proof.kind();
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(_))
) {
let got = match &receipt.inner {
InnerReceipt::Composite(_) => "Composite",
InnerReceipt::Succinct(_) => "Succinct",
InnerReceipt::Groth16(_) => "Groth16",
InnerReceipt::Fake(_) => "Fake",
_ => "Unknown",
};
bail!(Error::InvalidProofKind(proof_kind, got.to_string()));
}
receipt
.verify(self.program.image_id)
.map_err(Error::Verify)?;
let public_values = receipt.journal.bytes.clone();
Ok(public_values)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreRisc0 {
type ProgramDigest = Digest;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.program.image_id)
}
}
#[cfg(test)]
mod tests {
use crate::{compiler::RustRv32imaCustomized, program::Risc0Program, zkvm::EreRisc0};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::OnceLock;
fn basic_program() -> Risc0Program {
static PROGRAM: OnceLock<Risc0Program> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32imaCustomized
.compile(&testing_guest_directory("risc0", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
#[test]
fn test_aligned_allocs() {
let program = RustRv32imaCustomized
.compile(&testing_guest_directory("risc0", "allocs_alignment"))
.unwrap();
for i in 1..=16_u32 {
let zkvm = EreRisc0::new(program.clone(), ProverResourceType::Cpu).unwrap();
let input = i.to_le_bytes();
if i.is_power_of_two() {
zkvm.execute(&input)
.expect("Power of two alignment should execute successfully");
} else {
zkvm.execute(&input)
.expect_err("Non-power of two aligment is expected to fail");
}
}
}
}

View File

@@ -1,29 +1,9 @@
use ere_zkvm_interface::ProofKind;
use ere_zkvm_interface::zkvm::ProofKind;
use risc0_zkp::verify::VerificationError;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompileError {
#[error(transparent)]
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 Risc0Error {
pub enum Error {
// Execute
#[error("Failed to build `ExecutorEnv`: {0}")]
BuildExecutorEnv(anyhow::Error),

View File

@@ -8,15 +8,16 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
serde.workspace = true
tempfile.workspace = true
thiserror.workspace = true
tracing.workspace = true
# SP1 dependencies
sp1-sdk.workspace = true
sp1-sdk = { workspace = true, optional = true }
# Local dependencies
ere-compile-utils.workspace = true
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]
@@ -24,3 +25,11 @@ ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = ["dep:sp1-sdk"]
[lints]
workspace = true

View File

@@ -1,7 +1,7 @@
mod error;
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use error::Error;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
pub type SP1Program = Vec<u8>;

View File

@@ -0,0 +1,8 @@
use ere_compile_utils::CommonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
}

View File

@@ -1,6 +1,6 @@
use crate::{compiler::SP1Program, error::CompileError};
use crate::{compiler::Error, program::SP1Program};
use ere_compile_utils::CargoBuildCmd;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use std::{env, path::Path};
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
@@ -32,7 +32,7 @@ const CARGO_BUILD_OPTIONS: &[&str] = &[
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = CompileError;
type Error = Error;
type Program = SP1Program;
@@ -43,21 +43,24 @@ impl Compiler for RustRv32ima {
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
Ok(SP1Program { elf })
}
}
#[cfg(test)]
mod tests {
use crate::{EreSP1, compiler::RustRv32ima};
use crate::{compiler::RustRv32ima, zkvm::EreSP1};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProverResourceType, zkVM},
};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("sp1", "stock_nightly_no_std");
let elf = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
let program = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
#[test]

View File

@@ -1,6 +1,6 @@
use crate::{compiler::SP1Program, error::CompileError};
use crate::{compiler::Error, program::SP1Program};
use ere_compile_utils::{CommonError, cargo_metadata};
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use std::{fs, path::Path, process::Command};
use tempfile::tempdir;
use tracing::info;
@@ -10,7 +10,7 @@ use tracing::info;
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = CompileError;
type Error = Error;
type Program = SP1Program;
@@ -46,11 +46,11 @@ impl Compiler for RustRv32imaCustomized {
}
let elf_path = output_dir.path().join("guest.elf");
let elf_bytes =
let elf =
fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?;
info!("SP1 program compiled OK - {} bytes", elf_bytes.len());
info!("SP1 program compiled OK - {} bytes", elf.len());
Ok(elf_bytes)
Ok(SP1Program { elf })
}
}
@@ -58,12 +58,12 @@ impl Compiler for RustRv32imaCustomized {
mod tests {
use crate::compiler::RustRv32imaCustomized;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("sp1", "basic");
let elf_bytes = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
let program = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,326 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{compiler::SP1Program, error::SP1Error};
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, NetworkProverConfig, ProgramExecutionReport, ProgramProvingReport, Proof,
ProofKind, ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use sp1_sdk::{
CpuProver, CudaProver, NetworkProver, Prover, ProverClient, SP1ProofMode,
SP1ProofWithPublicValues, SP1ProvingKey, SP1Stdin, SP1VerifyingKey,
};
use std::{panic, time::Instant};
use tracing::info;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod program;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
#[allow(clippy::large_enum_variant)]
enum ProverType {
Cpu(CpuProver),
Gpu(CudaProver),
Network(NetworkProver),
}
#[cfg(feature = "zkvm")]
pub mod zkvm;
impl ProverType {
fn setup(&self, program: &SP1Program) -> (SP1ProvingKey, SP1VerifyingKey) {
match self {
ProverType::Cpu(cpu_prover) => cpu_prover.setup(program),
ProverType::Gpu(cuda_prover) => cuda_prover.setup(program),
ProverType::Network(network_prover) => network_prover.setup(program),
}
}
fn execute(
&self,
program: &SP1Program,
input: &SP1Stdin,
) -> Result<(sp1_sdk::SP1PublicValues, sp1_sdk::ExecutionReport), SP1Error> {
let cpu_executor_builder = match self {
ProverType::Cpu(cpu_prover) => cpu_prover.execute(program, input),
ProverType::Gpu(cuda_prover) => cuda_prover.execute(program, input),
ProverType::Network(network_prover) => network_prover.execute(program, input),
};
cpu_executor_builder.run().map_err(SP1Error::Execute)
}
fn prove(
&self,
pk: &SP1ProvingKey,
input: &SP1Stdin,
mode: SP1ProofMode,
) -> Result<SP1ProofWithPublicValues, SP1Error> {
match self {
ProverType::Cpu(cpu_prover) => cpu_prover.prove(pk, input).mode(mode).run(),
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(SP1Error::Prove)
}
fn verify(
&self,
proof: &SP1ProofWithPublicValues,
vk: &SP1VerifyingKey,
) -> Result<(), SP1Error> {
match self {
ProverType::Cpu(cpu_prover) => cpu_prover.verify(proof, vk),
ProverType::Gpu(cuda_prover) => cuda_prover.verify(proof, vk),
ProverType::Network(network_prover) => network_prover.verify(proof, vk),
}
.map_err(SP1Error::Verify)
}
}
pub struct EreSP1 {
program: SP1Program,
/// Proving key
pk: SP1ProvingKey,
/// Verification key
vk: SP1VerifyingKey,
/// Prover resource configuration for creating clients
resource: ProverResourceType,
// FIXME: The current version of SP1 (v5.0.5) has a problem where if proving the program crashes in the
// Moongate container, it leaves an internal mutex poisoned, which prevents further proving attempts.
// This is a workaround to avoid the poisoned mutex issue by creating a new client for each prove call.
// We still use the `setup(...)` method to create the proving and verification keys only once, such that when
// later calling `prove(...)` in a fresh client, we can reuse the keys and avoiding extra work.
//
// Eventually, this should be fixed in the SP1 SDK and we can create the `client` in the `new(...)` method.
// For more context see: https://github.com/eth-act/zkevm-benchmark-workload/issues/54
}
impl EreSP1 {
fn create_network_prover(config: &NetworkProverConfig) -> NetworkProver {
let mut builder = ProverClient::builder().network();
// Check if we have a private key in the config or environment
if let Some(api_key) = &config.api_key {
builder = builder.private_key(api_key);
} else if let Ok(private_key) = std::env::var("NETWORK_PRIVATE_KEY") {
builder = builder.private_key(&private_key);
} else {
panic!(
"Network proving requires a private key. Set NETWORK_PRIVATE_KEY environment variable or provide api_key in NetworkProverConfig"
);
}
// Set the RPC URL if provided
if !config.endpoint.is_empty() {
builder = builder.rpc_url(&config.endpoint);
} else if let Ok(rpc_url) = std::env::var("NETWORK_RPC_URL") {
builder = builder.rpc_url(&rpc_url);
}
// Otherwise SP1 SDK will use its default RPC URL
builder.build()
}
fn create_client(resource: &ProverResourceType) -> ProverType {
match resource {
ProverResourceType::Cpu => ProverType::Cpu(ProverClient::builder().cpu().build()),
ProverResourceType::Gpu => ProverType::Gpu(ProverClient::builder().cuda().build()),
ProverResourceType::Network(config) => {
ProverType::Network(Self::create_network_prover(config))
}
}
}
pub fn new(program: SP1Program, resource: ProverResourceType) -> Result<Self, SP1Error> {
let (pk, vk) = Self::create_client(&resource).setup(&program);
Ok(Self {
program,
pk,
vk,
resource,
})
}
}
impl zkVM for EreSP1 {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let mut stdin = SP1Stdin::new();
stdin.write_slice(input);
let client = Self::create_client(&self.resource);
let start = Instant::now();
let (public_values, exec_report) = client.execute(&self.program, &stdin)?;
Ok((
public_values.to_vec(),
ProgramExecutionReport {
total_num_cycles: exec_report.total_instruction_count(),
region_cycles: exec_report.cycle_tracker.into_iter().collect(),
execution_duration: start.elapsed(),
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
info!("Generating proof…");
let mut stdin = SP1Stdin::new();
stdin.write_slice(input);
let mode = match proof_kind {
ProofKind::Compressed => SP1ProofMode::Compressed,
ProofKind::Groth16 => SP1ProofMode::Groth16,
};
let (proof, proving_time) = panic::catch_unwind(|| {
let client = Self::create_client(&self.resource);
let start = Instant::now();
let proof = client.prove(&self.pk, &stdin, mode)?;
Ok::<_, SP1Error>((proof, start.elapsed()))
})
.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| CommonError::serialize("proof", "bincode", err))?,
);
Ok((
public_values,
proof,
ProgramProvingReport::new(proving_time),
))
}
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| CommonError::deserialize("proof", "bincode", err))?;
let inner_proof_kind = SP1ProofMode::from(&proof.proof);
if !matches!(
(proof_kind, inner_proof_kind),
(ProofKind::Compressed, SP1ProofMode::Compressed)
| (ProofKind::Groth16, SP1ProofMode::Groth16)
) {
bail!(SP1Error::InvalidProofKind(proof_kind, inner_proof_kind));
}
let client = Self::create_client(&self.resource);
client.verify(&proof, &self.vk)?;
let public_values_bytes = proof.public_values.as_slice().to_vec();
Ok(public_values_bytes)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreSP1 {
type ProgramDigest = SP1VerifyingKey;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.vk.clone())
}
}
fn panic_msg(err: Box<dyn std::any::Any + Send + 'static>) -> String {
None.or_else(|| err.downcast_ref::<String>().cloned())
.or_else(|| err.downcast_ref::<&'static str>().map(ToString::to_string))
.unwrap_or_else(|| "unknown panic msg".to_string())
}
#[cfg(test)]
mod tests {
use crate::{EreSP1, compiler::RustRv32imaCustomized};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, NetworkProverConfig, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
fn basic_program() -> Vec<u8> {
static PROGRAM: OnceLock<Vec<u8>> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32imaCustomized
.compile(&testing_guest_directory("sp1", "basic"))
.unwrap()
})
.to_vec()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
#[test]
#[ignore = "Requires NETWORK_PRIVATE_KEY environment variable to be set"]
fn test_prove_sp1_network() {
// Check if we have the required environment variable
if std::env::var("NETWORK_PRIVATE_KEY").is_err() {
eprintln!("Skipping network test: NETWORK_PRIVATE_KEY not set");
return;
}
// Create a network prover configuration
let network_config = NetworkProverConfig {
endpoint: std::env::var("NETWORK_RPC_URL").unwrap_or_default(),
api_key: std::env::var("NETWORK_PRIVATE_KEY").ok(),
};
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Network(network_config)).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
/// SP1 program that contains ELF of compiled guest.
#[derive(Clone, Serialize, Deserialize)]
pub struct SP1Program {
pub(crate) elf: Vec<u8>,
}
impl SP1Program {
pub fn elf(&self) -> &[u8] {
&self.elf
}
}

260
crates/zkvm/sp1/src/zkvm.rs Normal file
View File

@@ -0,0 +1,260 @@
use crate::{program::SP1Program, zkvm::sdk::Prover};
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use sp1_sdk::{SP1ProofMode, SP1ProofWithPublicValues, SP1ProvingKey, SP1Stdin, SP1VerifyingKey};
use std::{
mem::take,
panic,
sync::{RwLock, RwLockReadGuard, RwLockWriteGuard},
time::Instant,
};
use tracing::info;
mod error;
mod sdk;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub struct EreSP1 {
program: SP1Program,
/// Prover resource configuration for creating clients
resource: ProverResourceType,
/// Proving key
pk: SP1ProvingKey,
/// Verification key
vk: SP1VerifyingKey,
// The current version of SP1 (v5.2.1) has a problem where if GPU proving
// the program crashes in the Moongate container, it leaves an internal
// mutex poisoned, which prevents further proving attempts.
// This is a workaround to avoid the poisoned mutex issue by creating a new
// prover if the proving panics.
// Eventually, this should be fixed in the SP1 SDK.
// For more context see: https://github.com/eth-act/zkevm-benchmark-workload/issues/54
prover: RwLock<Prover>,
}
impl EreSP1 {
pub fn new(program: SP1Program, resource: ProverResourceType) -> Result<Self, Error> {
let prover = Prover::new(&resource);
let (pk, vk) = prover.setup(&program.elf);
Ok(Self {
program,
resource,
pk,
vk,
prover: RwLock::new(prover),
})
}
fn prover(&'_ self) -> Result<RwLockReadGuard<'_, Prover>, Error> {
self.prover.read().map_err(|_| Error::RwLockPosioned)
}
fn prover_mut(&'_ self) -> Result<RwLockWriteGuard<'_, Prover>, Error> {
self.prover.write().map_err(|_| Error::RwLockPosioned)
}
}
impl zkVM for EreSP1 {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let mut stdin = SP1Stdin::new();
stdin.write_slice(input);
let prover = self.prover()?;
let start = Instant::now();
let (public_values, exec_report) = prover.execute(self.program.elf(), &stdin)?;
let execution_duration = start.elapsed();
Ok((
public_values.to_vec(),
ProgramExecutionReport {
total_num_cycles: exec_report.total_instruction_count(),
region_cycles: exec_report.cycle_tracker.into_iter().collect(),
execution_duration,
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
info!("Generating proof…");
let mut stdin = SP1Stdin::new();
stdin.write_slice(input);
let mode = match proof_kind {
ProofKind::Compressed => SP1ProofMode::Compressed,
ProofKind::Groth16 => SP1ProofMode::Groth16,
};
let mut prover = self.prover_mut()?;
let start = Instant::now();
let proof =
panic::catch_unwind(|| prover.prove(&self.pk, &stdin, mode)).map_err(|err| {
if matches!(self.resource, ProverResourceType::Gpu) {
// Drop the panicked prover and create a new one.
// Note that `take` has to be done explicitly first so the
// Moongate container could be removed properly.
take(&mut *prover);
*prover = Prover::new(&self.resource);
}
Error::Panic(panic_msg(err))
})??;
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| CommonError::serialize("proof", "bincode", err))?,
);
Ok((
public_values,
proof,
ProgramProvingReport::new(proving_time),
))
}
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| CommonError::deserialize("proof", "bincode", err))?;
let inner_proof_kind = SP1ProofMode::from(&proof.proof);
if !matches!(
(proof_kind, inner_proof_kind),
(ProofKind::Compressed, SP1ProofMode::Compressed)
| (ProofKind::Groth16, SP1ProofMode::Groth16)
) {
bail!(Error::InvalidProofKind(proof_kind, inner_proof_kind));
}
self.prover()?.verify(&proof, &self.vk)?;
let public_values_bytes = proof.public_values.as_slice().to_vec();
Ok(public_values_bytes)
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreSP1 {
type ProgramDigest = SP1VerifyingKey;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.vk.clone())
}
}
fn panic_msg(err: Box<dyn std::any::Any + Send + 'static>) -> String {
None.or_else(|| err.downcast_ref::<String>().cloned())
.or_else(|| err.downcast_ref::<&'static str>().map(ToString::to_string))
.unwrap_or_else(|| "unknown panic msg".to_string())
}
#[cfg(test)]
mod tests {
use crate::{compiler::RustRv32imaCustomized, program::SP1Program, zkvm::EreSP1};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{NetworkProverConfig, ProofKind, ProverResourceType, zkVM},
};
use std::sync::OnceLock;
fn basic_program() -> SP1Program {
static PROGRAM: OnceLock<SP1Program> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32imaCustomized
.compile(&testing_guest_directory("sp1", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
#[test]
#[ignore = "Requires NETWORK_PRIVATE_KEY environment variable to be set"]
fn test_prove_sp1_network() {
// Check if we have the required environment variable
if std::env::var("NETWORK_PRIVATE_KEY").is_err() {
eprintln!("Skipping network test: NETWORK_PRIVATE_KEY not set");
return;
}
// Create a network prover configuration
let network_config = NetworkProverConfig {
endpoint: std::env::var("NETWORK_RPC_URL").unwrap_or_default(),
api_key: std::env::var("NETWORK_PRIVATE_KEY").ok(),
};
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Network(network_config)).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
}

View File

@@ -1,17 +1,14 @@
use ere_zkvm_interface::ProofKind;
use ere_zkvm_interface::zkvm::{CommonError, ProofKind};
use sp1_sdk::{SP1ProofMode, SP1VerificationError};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompileError {
pub enum Error {
#[error(transparent)]
CommonError(#[from] ere_compile_utils::CommonError),
}
CommonError(#[from] CommonError),
#[derive(Debug, Error)]
pub enum SP1Error {
#[error(transparent)]
CommonError(#[from] ere_zkvm_interface::CommonError),
#[error("Prover RwLock posioned, panic not catched properly")]
RwLockPosioned,
// Execute
#[error("SP1 execution failed: {0}")]

View File

@@ -0,0 +1,99 @@
use crate::zkvm::Error;
use ere_zkvm_interface::zkvm::{NetworkProverConfig, ProverResourceType};
use sp1_sdk::{
CpuProver, CudaProver, NetworkProver, Prover as _, ProverClient, SP1ProofMode,
SP1ProofWithPublicValues, SP1ProvingKey, SP1Stdin, SP1VerifyingKey,
};
#[allow(clippy::large_enum_variant)]
pub enum Prover {
Cpu(CpuProver),
Gpu(CudaProver),
Network(NetworkProver),
}
impl Default for Prover {
fn default() -> Self {
Self::new(&ProverResourceType::Cpu)
}
}
impl Prover {
pub fn new(resource: &ProverResourceType) -> Self {
match resource {
ProverResourceType::Cpu => Self::Cpu(ProverClient::builder().cpu().build()),
ProverResourceType::Gpu => Self::Gpu(ProverClient::builder().cuda().build()),
ProverResourceType::Network(config) => Self::Network(build_network_prover(config)),
}
}
pub fn setup(&self, elf: &[u8]) -> (SP1ProvingKey, SP1VerifyingKey) {
match self {
Self::Cpu(cpu_prover) => cpu_prover.setup(elf),
Self::Gpu(cuda_prover) => cuda_prover.setup(elf),
Self::Network(network_prover) => network_prover.setup(elf),
}
}
pub fn execute(
&self,
elf: &[u8],
input: &SP1Stdin,
) -> Result<(sp1_sdk::SP1PublicValues, sp1_sdk::ExecutionReport), Error> {
match self {
Self::Cpu(cpu_prover) => cpu_prover.execute(elf, input).run(),
Self::Gpu(cuda_prover) => cuda_prover.execute(elf, input).run(),
Self::Network(network_prover) => network_prover.execute(elf, input).run(),
}
.map_err(Error::Execute)
}
pub fn prove(
&self,
pk: &SP1ProvingKey,
input: &SP1Stdin,
mode: SP1ProofMode,
) -> Result<SP1ProofWithPublicValues, Error> {
match self {
Self::Cpu(cpu_prover) => cpu_prover.prove(pk, input).mode(mode).run(),
Self::Gpu(cuda_prover) => cuda_prover.prove(pk, input).mode(mode).run(),
Self::Network(network_prover) => network_prover.prove(pk, input).mode(mode).run(),
}
.map_err(Error::Prove)
}
pub fn verify(
&self,
proof: &SP1ProofWithPublicValues,
vk: &SP1VerifyingKey,
) -> Result<(), Error> {
match self {
Self::Cpu(cpu_prover) => cpu_prover.verify(proof, vk),
Self::Gpu(cuda_prover) => cuda_prover.verify(proof, vk),
Self::Network(network_prover) => network_prover.verify(proof, vk),
}
.map_err(Error::Verify)
}
}
fn build_network_prover(config: &NetworkProverConfig) -> NetworkProver {
let mut builder = ProverClient::builder().network();
// Check if we have a private key in the config or environment
if let Some(api_key) = &config.api_key {
builder = builder.private_key(api_key);
} else if let Ok(private_key) = std::env::var("NETWORK_PRIVATE_KEY") {
builder = builder.private_key(&private_key);
} else {
panic!(
"Network proving requires a private key. Set NETWORK_PRIVATE_KEY environment variable or provide api_key in NetworkProverConfig"
);
}
// Set the RPC URL if provided
if !config.endpoint.is_empty() {
builder = builder.rpc_url(&config.endpoint);
} else if let Ok(rpc_url) = std::env::var("NETWORK_RPC_URL") {
builder = builder.rpc_url(&rpc_url);
}
// Otherwise SP1 SDK will use its default RPC URL
builder.build()
}

View File

@@ -8,14 +8,15 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
bincode = { workspace = true, features = ["std", "serde"] }
serde.workspace = true
thiserror.workspace = true
tracing.workspace = true
# Ziren dependencies
zkm-sdk.workspace = true
zkm-sdk = { workspace = true, optional = true }
# Local dependencies
ere-compile-utils.workspace = true
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
[dev-dependencies]
@@ -24,5 +25,10 @@ ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = ["dep:zkm-sdk"]
[lints]
workspace = true

View File

@@ -1,5 +1,5 @@
mod error;
mod rust_mips32r2_customized;
pub use error::Error;
pub use rust_mips32r2_customized::RustMips32r2Customized;
pub type ZirenProgram = Vec<u8>;

View File

@@ -0,0 +1,8 @@
use ere_compile_utils::CommonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
}

View File

@@ -1,6 +1,6 @@
use crate::{compiler::ZirenProgram, error::CompileError};
use crate::{compiler::Error, program::ZirenProgram};
use ere_compile_utils::{CommonError, cargo_metadata, rustc_path};
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
use std::{fs, path::Path, process::Command};
const ZKM_TOOLCHAIN: &str = "zkm";
@@ -10,7 +10,7 @@ const ZKM_TOOLCHAIN: &str = "zkm";
pub struct RustMips32r2Customized;
impl Compiler for RustMips32r2Customized {
type Error = CompileError;
type Error = Error;
type Program = ZirenProgram;
@@ -47,7 +47,7 @@ impl Compiler for RustMips32r2Customized {
let elf =
fs::read(&elf_path).map_err(|err| CommonError::read_file("elf", &elf_path, err))?;
Ok(elf)
Ok(ZirenProgram { elf })
}
}
@@ -55,12 +55,12 @@ impl Compiler for RustMips32r2Customized {
mod tests {
use crate::compiler::RustMips32r2Customized;
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::Compiler;
use ere_zkvm_interface::compiler::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("ziren", "basic");
let elf_bytes = RustMips32r2Customized.compile(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
let program = RustMips32r2Customized.compile(&guest_directory).unwrap();
assert!(!program.elf().is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,203 +1,15 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(
all(not(test), feature = "compiler", feature = "zkvm"),
warn(unused_crate_dependencies)
)]
use crate::{compiler::ZirenProgram, error::ZirenError};
use anyhow::bail;
use ere_zkvm_interface::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use std::{panic, time::Instant};
use tracing::info;
use zkm_sdk::{
CpuProver, Prover, ZKMProofKind, ZKMProofWithPublicValues, ZKMProvingKey, ZKMStdin,
ZKMVerifyingKey,
};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod program;
#[cfg(feature = "compiler")]
pub mod compiler;
pub mod error;
pub struct EreZiren {
program: ZirenProgram,
pk: ZKMProvingKey,
vk: ZKMVerifyingKey,
}
#[cfg(feature = "zkvm")]
pub mod zkvm;
impl EreZiren {
pub fn new(program: ZirenProgram, resource: ProverResourceType) -> Result<Self, ZirenError> {
if matches!(
resource,
ProverResourceType::Gpu | ProverResourceType::Network(_)
) {
panic!("Network or Gpu proving not yet implemented for ZKM. Use CPU resource type.");
}
let (pk, vk) = CpuProver::new().setup(&program);
Ok(Self { program, pk, vk })
}
}
impl zkVM for EreZiren {
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(ZirenError::Execute)?;
let execution_duration = start.elapsed();
Ok((
public_inputs.to_vec(),
ProgramExecutionReport {
total_num_cycles: exec_report.total_instruction_count(),
region_cycles: exec_report.cycle_tracker.into_iter().collect(),
execution_duration,
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
info!("Generating proof…");
let mut stdin = ZKMStdin::new();
stdin.write_slice(input);
let inner_proof_kind = match proof_kind {
ProofKind::Compressed => ZKMProofKind::Compressed,
ProofKind::Groth16 => ZKMProofKind::Groth16,
};
let start = std::time::Instant::now();
let proof =
panic::catch_unwind(|| CpuProver::new().prove(&self.pk, stdin, inner_proof_kind))
.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| CommonError::serialize("proof", "bincode", err))?,
);
Ok((
public_values,
proof,
ProgramProvingReport::new(proving_time),
))
}
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| CommonError::deserialize("proof", "bincode", err))?;
let inner_proof_kind = ZKMProofKind::from(&proof.proof);
if !matches!(
(proof_kind, inner_proof_kind),
(ProofKind::Compressed, ZKMProofKind::Compressed)
| (ProofKind::Groth16, ZKMProofKind::Groth16)
) {
bail!(ZirenError::InvalidProofKind(proof_kind, inner_proof_kind));
}
CpuProver::new()
.verify(&proof, &self.vk)
.map_err(ZirenError::Verify)?;
Ok(proof.public_values.to_vec())
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreZiren {
type ProgramDigest = ZKMVerifyingKey;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.vk.clone())
}
}
fn panic_msg(err: Box<dyn std::any::Any + Send + 'static>) -> String {
None.or_else(|| err.downcast_ref::<String>().cloned())
.or_else(|| err.downcast_ref::<&'static str>().map(ToString::to_string))
.unwrap_or_else(|| "unknown panic msg".to_string())
}
#[cfg(test)]
mod tests {
use crate::{EreZiren, compiler::RustMips32r2Customized};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
fn basic_program() -> Vec<u8> {
static PROGRAM: OnceLock<Vec<u8>> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustMips32r2Customized
.compile(&testing_guest_directory("ziren", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}
#[cfg(feature = "zkvm")]
pub use zkvm::*;

View File

@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
/// Ziren program that contains ELF of compiled guest.
#[derive(Clone, Serialize, Deserialize)]
pub struct ZirenProgram {
pub(crate) elf: Vec<u8>,
}
impl ZirenProgram {
pub fn elf(&self) -> &[u8] {
&self.elf
}
}

View File

@@ -0,0 +1,205 @@
use crate::program::ZirenProgram;
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMProgramDigest,
};
use std::{panic, time::Instant};
use tracing::info;
use zkm_sdk::{
CpuProver, Prover, ZKMProofKind, ZKMProofWithPublicValues, ZKMProvingKey, ZKMStdin,
ZKMVerifyingKey,
};
mod error;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub struct EreZiren {
program: ZirenProgram,
pk: ZKMProvingKey,
vk: ZKMVerifyingKey,
}
impl EreZiren {
pub fn new(program: ZirenProgram, resource: ProverResourceType) -> Result<Self, Error> {
if matches!(
resource,
ProverResourceType::Gpu | ProverResourceType::Network(_)
) {
panic!("Network or Gpu proving not yet implemented for ZKM. Use CPU resource type.");
}
let (pk, vk) = CpuProver::new().setup(program.elf());
Ok(Self { program, pk, vk })
}
}
impl zkVM for EreZiren {
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.elf(), &stdin)
.map_err(Error::Execute)?;
let execution_duration = start.elapsed();
Ok((
public_inputs.to_vec(),
ProgramExecutionReport {
total_num_cycles: exec_report.total_instruction_count(),
region_cycles: exec_report.cycle_tracker.into_iter().collect(),
execution_duration,
},
))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
info!("Generating proof…");
let mut stdin = ZKMStdin::new();
stdin.write_slice(input);
let inner_proof_kind = match proof_kind {
ProofKind::Compressed => ZKMProofKind::Compressed,
ProofKind::Groth16 => ZKMProofKind::Groth16,
};
let start = std::time::Instant::now();
let proof =
panic::catch_unwind(|| CpuProver::new().prove(&self.pk, stdin, inner_proof_kind))
.map_err(|err| Error::ProvePanic(panic_msg(err)))?
.map_err(Error::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| CommonError::serialize("proof", "bincode", err))?,
);
Ok((
public_values,
proof,
ProgramProvingReport::new(proving_time),
))
}
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| CommonError::deserialize("proof", "bincode", err))?;
let inner_proof_kind = ZKMProofKind::from(&proof.proof);
if !matches!(
(proof_kind, inner_proof_kind),
(ProofKind::Compressed, ZKMProofKind::Compressed)
| (ProofKind::Groth16, ZKMProofKind::Groth16)
) {
bail!(Error::InvalidProofKind(proof_kind, inner_proof_kind));
}
CpuProver::new()
.verify(&proof, &self.vk)
.map_err(Error::Verify)?;
Ok(proof.public_values.to_vec())
}
fn name(&self) -> &'static str {
NAME
}
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
}
impl zkVMProgramDigest for EreZiren {
type ProgramDigest = ZKMVerifyingKey;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.vk.clone())
}
}
fn panic_msg(err: Box<dyn std::any::Any + Send + 'static>) -> String {
None.or_else(|| err.downcast_ref::<String>().cloned())
.or_else(|| err.downcast_ref::<&'static str>().map(ToString::to_string))
.unwrap_or_else(|| "unknown panic msg".to_string())
}
#[cfg(test)]
mod tests {
use crate::{compiler::RustMips32r2Customized, program::ZirenProgram, zkvm::EreZiren};
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::OnceLock;
fn basic_program() -> ZirenProgram {
static PROGRAM: OnceLock<ZirenProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustMips32r2Customized
.compile(&testing_guest_directory("ziren", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
#[test]
fn test_prove() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap();
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_input() {
let program = basic_program();
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

@@ -1,15 +1,9 @@
use ere_zkvm_interface::ProofKind;
use ere_zkvm_interface::zkvm::ProofKind;
use thiserror::Error;
use zkm_sdk::{ZKMProofKind, ZKMVerificationError};
#[derive(Debug, Error)]
pub enum CompileError {
#[error(transparent)]
CommonError(#[from] ere_compile_utils::CommonError),
}
#[derive(Debug, Error)]
pub enum ZirenError {
pub enum Error {
// Execute
#[error("Ziren execution failed: {0}")]
Execute(#[source] anyhow::Error),

Some files were not shown because too many files have changed in this diff Show More