mirror of
https://github.com/eth-act/ere.git
synced 2026-02-19 11:54:42 -05:00
Refactor mods to separate compiler and zkvm (#184)
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -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",
|
||||
|
||||
10
README.md
10
README.md
@@ -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");
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
14
crates/zkvm-interface/src/compiler.rs
Normal file
14
crates/zkvm-interface/src/compiler.rs
Normal 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>;
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
59
crates/zkvm-interface/src/zkvm.rs
Normal file
59
crates/zkvm-interface/src/zkvm.rs
Normal 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>;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::ProofKind;
|
||||
use crate::zkvm::ProofKind;
|
||||
use std::{
|
||||
io,
|
||||
path::Path,
|
||||
40
crates/zkvm-interface/src/zkvm/proof.rs
Normal file
40
crates/zkvm-interface/src/zkvm/proof.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod error;
|
||||
mod rust_rv32ima;
|
||||
|
||||
pub use error::Error;
|
||||
pub use rust_rv32ima::RustRv32ima;
|
||||
|
||||
pub type AirbenderProgram = Vec<u8>;
|
||||
|
||||
8
crates/zkvm/airbender/src/compiler/error.rs
Normal file
8
crates/zkvm/airbender/src/compiler/error.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use ere_compile_utils::CommonError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
CommonError(#[from] CommonError),
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
13
crates/zkvm/airbender/src/program.rs
Normal file
13
crates/zkvm/airbender/src/program.rs
Normal 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
|
||||
}
|
||||
}
|
||||
166
crates/zkvm/airbender/src/zkvm.rs
Normal file
166
crates/zkvm/airbender/src/zkvm.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}")]
|
||||
@@ -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(),
|
||||
));
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
13
crates/zkvm/jolt/src/program.rs
Normal file
13
crates/zkvm/jolt/src/program.rs
Normal 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
|
||||
}
|
||||
}
|
||||
172
crates/zkvm/jolt/src/zkvm.rs
Normal file
172
crates/zkvm/jolt/src/zkvm.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
crates/zkvm/jolt/src/zkvm/error.rs
Normal file
17
crates/zkvm/jolt/src/zkvm/error.rs
Normal 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),
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
42
crates/zkvm/miden/src/program.rs
Normal file
42
crates/zkvm/miden/src/program.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
259
crates/zkvm/miden/src/zkvm.rs
Normal file
259
crates/zkvm/miden/src/zkvm.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
22
crates/zkvm/miden/src/zkvm/error.rs
Normal file
22
crates/zkvm/miden/src/zkvm/error.rs
Normal 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),
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod error;
|
||||
mod rust_rv32i;
|
||||
|
||||
pub use error::Error;
|
||||
pub use rust_rv32i::RustRv32i;
|
||||
|
||||
pub type NexusProgram = Vec<u8>;
|
||||
|
||||
8
crates/zkvm/nexus/src/compiler/error.rs
Normal file
8
crates/zkvm/nexus/src/compiler/error.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use ere_compile_utils::CommonError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
CommonError(#[from] CommonError),
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
13
crates/zkvm/nexus/src/program.rs
Normal file
13
crates/zkvm/nexus/src/program.rs
Normal 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
|
||||
}
|
||||
}
|
||||
219
crates/zkvm/nexus/src/zkvm.rs
Normal file
219
crates/zkvm/nexus/src/zkvm.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
@@ -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]
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
17
crates/zkvm/openvm/src/compiler/error.rs
Normal file
17
crates/zkvm/openvm/src/compiler/error.rs
Normal 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),
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
19
crates/zkvm/openvm/src/program.rs
Normal file
19
crates/zkvm/openvm/src/program.rs
Normal 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
|
||||
}
|
||||
}
|
||||
285
crates/zkvm/openvm/src/zkvm.rs
Normal file
285
crates/zkvm/openvm/src/zkvm.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}")]
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
8
crates/zkvm/pico/src/compiler/error.rs
Normal file
8
crates/zkvm/pico/src/compiler/error.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use ere_compile_utils::CommonError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
CommonError(#[from] CommonError),
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
13
crates/zkvm/pico/src/program.rs
Normal file
13
crates/zkvm/pico/src/program.rs
Normal 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
|
||||
}
|
||||
}
|
||||
241
crates/zkvm/pico/src/zkvm.rs
Normal file
241
crates/zkvm/pico/src/zkvm.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}")]
|
||||
@@ -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},
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
22
crates/zkvm/risc0/src/compiler/error.rs
Normal file
22
crates/zkvm/risc0/src/compiler/error.rs
Normal 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),
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
19
crates/zkvm/risc0/src/program.rs
Normal file
19
crates/zkvm/risc0/src/program.rs
Normal 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
|
||||
}
|
||||
}
|
||||
298
crates/zkvm/risc0/src/zkvm.rs
Normal file
298
crates/zkvm/risc0/src/zkvm.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
8
crates/zkvm/sp1/src/compiler/error.rs
Normal file
8
crates/zkvm/sp1/src/compiler/error.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use ere_compile_utils::CommonError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
CommonError(#[from] CommonError),
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
13
crates/zkvm/sp1/src/program.rs
Normal file
13
crates/zkvm/sp1/src/program.rs
Normal 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
260
crates/zkvm/sp1/src/zkvm.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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}")]
|
||||
99
crates/zkvm/sp1/src/zkvm/sdk.rs
Normal file
99
crates/zkvm/sp1/src/zkvm/sdk.rs
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
8
crates/zkvm/ziren/src/compiler/error.rs
Normal file
8
crates/zkvm/ziren/src/compiler/error.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use ere_compile_utils::CommonError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
CommonError(#[from] CommonError),
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
13
crates/zkvm/ziren/src/program.rs
Normal file
13
crates/zkvm/ziren/src/program.rs
Normal 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
|
||||
}
|
||||
}
|
||||
205
crates/zkvm/ziren/src/zkvm.rs
Normal file
205
crates/zkvm/ziren/src/zkvm.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user