zkVM takes opaque input (#173)

This commit is contained in:
Han
2025-10-18 11:04:35 +08:00
committed by GitHub
parent 7bd1789a31
commit 577f97165e
76 changed files with 851 additions and 1528 deletions

View File

@@ -21,6 +21,8 @@ jobs:
run_test: true
- crate: ere-dockerized
run_test: false # They are run in per-zkVM workflows
- crate: ere-io-serde
run_test: true
- crate: ere-build-utils
run_test: true
- crate: ere-compile-utils

70
Cargo.lock generated
View File

@@ -3621,7 +3621,7 @@ dependencies = [
[[package]]
name = "ere-build-utils"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"cargo_metadata 0.19.2",
"thiserror 2.0.12",
@@ -3630,7 +3630,7 @@ dependencies = [
[[package]]
name = "ere-compile-utils"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"cargo_metadata 0.19.2",
"tempfile",
@@ -3639,10 +3639,10 @@ dependencies = [
[[package]]
name = "ere-compiler"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"anyhow",
"bincode 1.3.3",
"bincode 2.0.1",
"clap",
"ere-jolt",
"ere-miden",
@@ -3660,16 +3660,13 @@ dependencies = [
[[package]]
name = "ere-dockerized"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"anyhow",
"bincode 1.3.3",
"bytemuck",
"ere-build-utils",
"ere-server",
"ere-test-utils",
"ere-zkvm-interface",
"risc0-zkvm",
"serde",
"tempfile",
"thiserror 2.0.12",
@@ -3677,9 +3674,17 @@ dependencies = [
"tracing",
]
[[package]]
name = "ere-io-serde"
version = "0.0.14"
dependencies = [
"bincode 2.0.1",
"serde",
]
[[package]]
name = "ere-jolt"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"ark-serialize 0.5.0",
"common",
@@ -3688,16 +3693,15 @@ dependencies = [
"ere-test-utils",
"ere-zkvm-interface",
"jolt",
"serde",
"tempfile",
"thiserror 2.0.12",
]
[[package]]
name = "ere-miden"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"bincode 1.3.3",
"bincode 2.0.1",
"ere-build-utils",
"ere-test-utils",
"ere-zkvm-interface",
@@ -3713,9 +3717,9 @@ dependencies = [
[[package]]
name = "ere-nexus"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"bincode 1.3.3",
"bincode 2.0.1",
"ere-build-utils",
"ere-compile-utils",
"ere-test-utils",
@@ -3731,7 +3735,7 @@ dependencies = [
[[package]]
name = "ere-openvm"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"ere-build-utils",
"ere-compile-utils",
@@ -3750,10 +3754,10 @@ dependencies = [
[[package]]
name = "ere-pico"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"anyhow",
"bincode 1.3.3",
"bincode 2.0.1",
"ere-build-utils",
"ere-compile-utils",
"ere-test-utils",
@@ -3768,13 +3772,13 @@ dependencies = [
[[package]]
name = "ere-risc0"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"anyhow",
"borsh",
"bytemuck",
"ere-build-utils",
"ere-compile-utils",
"ere-io-serde",
"ere-test-utils",
"ere-zkvm-interface",
"risc0-binfmt",
@@ -3787,10 +3791,10 @@ dependencies = [
[[package]]
name = "ere-server"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"anyhow",
"bincode 1.3.3",
"bincode 2.0.1",
"clap",
"ere-jolt",
"ere-miden",
@@ -3815,14 +3819,13 @@ dependencies = [
[[package]]
name = "ere-sp1"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"bincode 1.3.3",
"bincode 2.0.1",
"ere-build-utils",
"ere-compile-utils",
"ere-test-utils",
"ere-zkvm-interface",
"serde",
"sp1-sdk",
"tempfile",
"thiserror 2.0.12",
@@ -3831,9 +3834,9 @@ dependencies = [
[[package]]
name = "ere-test-utils"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"bincode 1.3.3",
"ere-io-serde",
"ere-zkvm-interface",
"rand 0.9.2",
"serde",
@@ -3842,14 +3845,13 @@ dependencies = [
[[package]]
name = "ere-ziren"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"bincode 1.3.3",
"bincode 2.0.1",
"ere-build-utils",
"ere-compile-utils",
"ere-test-utils",
"ere-zkvm-interface",
"serde",
"thiserror 2.0.12",
"tracing",
"zkm-sdk",
@@ -3857,16 +3859,15 @@ dependencies = [
[[package]]
name = "ere-zisk"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"bincode 1.3.3",
"bincode 2.0.1",
"blake3",
"bytemuck",
"ere-build-utils",
"ere-compile-utils",
"ere-test-utils",
"ere-zkvm-interface",
"serde",
"strum 0.27.2",
"tempfile",
"thiserror 2.0.12",
@@ -3875,12 +3876,11 @@ dependencies = [
[[package]]
name = "ere-zkvm-interface"
version = "0.0.13"
version = "0.0.14"
dependencies = [
"auto_impl",
"bincode 1.3.3",
"bincode 2.0.1",
"clap",
"erased-serde",
"indexmap 2.10.0",
"serde",
"serde_json",

View File

@@ -17,6 +17,7 @@ members = [
"crates/dockerized/dockerized",
"crates/dockerized/server",
# Utils
"crates/io-serde",
"crates/build-utils",
"crates/compile-utils",
"crates/test-utils",
@@ -24,7 +25,7 @@ members = [
resolver = "2"
[workspace.package]
version = "0.0.13"
version = "0.0.14"
edition = "2024"
rust-version = "1.85"
license = "MIT OR Apache-2.0"
@@ -34,23 +35,22 @@ license = "MIT OR Apache-2.0"
[workspace.dependencies]
anyhow = "1.0.98"
auto_impl = "1.3.0"
bincode = "1.3.3"
bincode = { version = "2.0.1", default-features = false }
blake3 = "1.8.2"
borsh = "1.5.7"
bytemuck = "1.23.1"
cargo_metadata = "0.19.0"
clap = "4.5.42"
dashmap = "6.1.0"
erased-serde = "0.4.6"
indexmap = "2.10.0"
postcard = "1.0.8"
prost = "0.13"
prost-build = "0.13"
rand = "0.9.2"
serde = "1.0.219"
serde = { version = "1.0.219", default-features = false }
serde_json = "1.0.142"
serde_yaml = "0.9.34"
sha2 = "0.10.9"
sha2 = { version = "0.10.9", default-features = false }
strum = "0.27.2"
tempfile = "3.20.0"
thiserror = "2.0.12"
@@ -120,6 +120,7 @@ ere-compiler = { path = "crates/dockerized/compiler" }
ere-dockerized = { path = "crates/dockerized/dockerized" }
ere-server = { path = "crates/dockerized/server" }
ere-io-serde = { path = "crates/io-serde" }
ere-build-utils = { path = "crates/build-utils" }
ere-compile-utils = { path = "crates/compile-utils" }
ere-test-utils = { path = "crates/test-utils" }

View File

@@ -82,7 +82,7 @@ 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, Input, ProofKind, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let guest_directory = std::path::Path::new("workspace/guest");
@@ -94,15 +94,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create zkVM instance
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
// Prepare inputs
let mut io = Input::new();
io.write(42u32);
// Serialize input
let input = 42u32.to_le_bytes();
// Execute
let (public_values, report) = zkvm.execute(&io)?;
let (public_values, report) = zkvm.execute(&input)?;
// Prove
let (public_values, proof, report) = zkvm.prove(&io, ProofKind::Compressed)?;
let (public_values, proof, report) = zkvm.prove(&input, ProofKind::Compressed)?;
// Verify
let public_values = zkvm.verify(&proof)?;
@@ -129,7 +128,7 @@ 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, Input, ProofKind, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let guest_directory = std::path::Path::new("workspace/guest");
@@ -141,15 +140,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create zkVM instance
let zkvm = EreDockerizedzkVM::new(ErezkVM::SP1, program, ProverResourceType::Cpu)?;
// Prepare inputs
let mut io = Input::new();
io.write(42u32);
// Serialize input
let input = 42u32.to_le_bytes();
// Execute
let (public_values, report) = zkvm.execute(&io)?;
let (public_values, report) = zkvm.execute(&input)?;
// Prove
let (public_values, proof, report) = zkvm.prove(&io, ProofKind::Compressed)?;
let (public_values, proof, report) = zkvm.prove(&input, ProofKind::Compressed)?;
// Verify
let public_values = zkvm.verify(&proof)?;
@@ -184,7 +182,7 @@ Each `ere-{backend}` crate implements the above traits for its zkVM.
### Input Handling
The `Input` type supports both chunked (`Vec<Vec<u8>>`) and contiguous (`Vec<u8>`) modes to satisfy differing backend APIs.
The input is opaque to `zkVM` and will be passed as is, de/serialization needed to be handled by guest/host themselves.
## Contributing

View File

@@ -7,7 +7,7 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
bincode.workspace = true
bincode = { workspace = true, features = ["std", "serde"] }
clap = { workspace = true, features = ["derive"] }
serde.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }

View File

@@ -42,8 +42,9 @@ fn main() -> Result<(), Error> {
let program = compile(args.guest_path)?;
let output = File::create(args.output_path).with_context(|| "Failed to create output")?;
bincode::serialize_into(output, &program).with_context(|| "Failed to serialize program")?;
let mut output = File::create(args.output_path).with_context(|| "Failed to create output")?;
bincode::serde::encode_into_std_write(&program, &mut output, bincode::config::legacy())
.with_context(|| "Failed to serialize program")?;
Ok(())
}

View File

@@ -7,17 +7,12 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
bincode.workspace = true
serde = { workspace = true, features = ["derive"] }
tempfile.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread"] }
tracing.workspace = true
# Needed for Risc0 and OpenVM input serialization.
bytemuck.workspace = true
risc0-zkvm.workspace = true
# Local dependencies
ere-zkvm-interface = { workspace = true, features = ["clap"] }
ere-server.workspace = true

View File

@@ -1,73 +0,0 @@
use crate::{ErezkVM, error::CommonError};
use ere_server::input::{SerializedInput, SerializedInputItem};
use ere_zkvm_interface::{Input, InputItem};
use serde::Serialize;
impl ErezkVM {
pub fn serialize_object(
&self,
obj: &(impl Serialize + ?Sized),
) -> Result<Vec<u8>, CommonError> {
match self {
// Issue for tracking: https://github.com/eth-act/ere/issues/4.
Self::Jolt => todo!(),
Self::Miden => bincode::serialize(obj).map_err(|err| {
CommonError::serilization(err, "Failed to serialize object with `bincode`")
}),
// Issue for tracking: https://github.com/eth-act/ere/issues/63.
Self::Nexus => todo!(),
// FIXME: Instead of using `openvm::serde::to_vec`, we use Risc0's
// serializer, because OpenVM uses the same one, to avoid the
// duplicated extern symbol they export.
// It'd be better to have each zkvm provides their
// lightweight serde crate.
// The issue for tracking https://github.com/eth-act/ere/issues/76.
Self::OpenVM => risc0_zkvm::serde::to_vec(obj)
.map(|words| words.into_iter().flat_map(|w| w.to_le_bytes()).collect())
.map_err(|err| {
CommonError::serilization(
err,
"Failed to serialize object with `risc0_zkvm::serde::to_vec`",
)
}),
Self::Pico => bincode::serialize(obj).map_err(|err| {
CommonError::serilization(err, "Failed to serialize object with `bincode`")
}),
Self::Risc0 => risc0_zkvm::serde::to_vec(obj)
.map(|vec| bytemuck::cast_slice(&vec).to_vec())
.map_err(|err| {
CommonError::serilization(
err,
"Failed to serialize object with `risc0_zkvm::serde::to_vec`",
)
}),
Self::SP1 => bincode::serialize(obj).map_err(|err| {
CommonError::serilization(err, "Failed to serialize object with `bincode`")
}),
Self::Ziren => bincode::serialize(obj).map_err(|err| {
CommonError::serilization(err, "Failed to serialize object with `bincode`")
}),
Self::Zisk => bincode::serialize(obj).map_err(|err| {
CommonError::serilization(err, "Failed to serialize object with `bincode`")
}),
}
}
pub fn serialize_inputs(&self, inputs: &Input) -> Result<SerializedInput, CommonError> {
inputs
.iter()
.map(|input| {
Ok(match input {
InputItem::Object(obj) => {
SerializedInputItem::SerializedObject(self.serialize_object(&**obj)?)
}
InputItem::SerializedObject(bytes) => {
SerializedInputItem::SerializedObject(bytes.clone())
}
InputItem::Bytes(bytes) => SerializedInputItem::Bytes(bytes.clone()),
})
})
.collect::<Result<_, _>>()
.map(SerializedInput)
}
}

View File

@@ -27,7 +27,7 @@
//! ```rust,no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ere_dockerized::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM};
//! use ere_zkvm_interface::{Compiler, Input, ProofKind, ProverResourceType, zkVM};
//! use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
//! use std::path::Path;
//!
//! // The zkVM we plan to use
@@ -42,17 +42,15 @@
//! let resource = ProverResourceType::Cpu;
//! let zkvm = EreDockerizedzkVM::new(zkvm, program, resource)?;
//!
//! // Prepare inputs
//! let mut inputs = Input::new();
//! inputs.write(42u32);
//! inputs.write(100u16);
//! // Serialize input
//! let input = 42u32.to_le_bytes();
//!
//! // Execute program
//! let (public_values, execution_report) = zkvm.execute(&inputs)?;
//! let (public_values, execution_report) = zkvm.execute(&input)?;
//! println!("Execution cycles: {}", execution_report.total_num_cycles);
//!
//! // Generate proof
//! let (public_values, proof, proving_report) = zkvm.prove(&inputs, ProofKind::Compressed)?;
//! let (public_values, proof, proving_report) = zkvm.prove(&input, ProofKind::Compressed)?;
//! println!("Proof generated in: {:?}", proving_report.proving_time);
//!
//! // Verify proof
@@ -71,16 +69,14 @@ use crate::{
};
use ere_server::client::{Url, zkVMClient};
use ere_zkvm_interface::{
Compiler, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMError,
Compiler, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde::{Deserialize, Serialize};
use std::{
env,
fmt::{self, Display, Formatter},
fs,
io::Read,
iter,
fs, iter,
path::{Path, PathBuf},
str::FromStr,
};
@@ -93,8 +89,6 @@ include!(concat!(env!("OUT_DIR"), "/zkvm_sdk_version_impl.rs"));
pub mod cuda;
pub mod docker;
pub mod error;
pub mod input;
pub mod output;
/// Offset of port used for `ere-server` for [`ErezkVM`]s.
const ERE_SERVER_PORT_OFFSET: u16 = 4174;
@@ -201,13 +195,7 @@ impl ErezkVM {
let cuda_arch = cuda_arch();
match self {
ErezkVM::OpenVM => {
if let Some(cuda_arch) = cuda_arch {
// OpenVM takes only the numeric part.
cmd = cmd.build_arg("CUDA_ARCH", cuda_arch.replace("sm_", ""))
}
}
ErezkVM::Risc0 | ErezkVM::Zisk => {
ErezkVM::OpenVM | ErezkVM::Risc0 | ErezkVM::Zisk => {
if let Some(cuda_arch) = cuda_arch {
cmd = cmd.build_arg("CUDA_ARCH", cuda_arch)
}
@@ -502,13 +490,8 @@ impl EreDockerizedzkVM {
}
impl zkVM for EreDockerizedzkVM {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let serialized_input = self
.zkvm
.serialize_inputs(inputs)
.map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?;
let (public_values, report) = block_on(self.client.execute(serialized_input))
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let (public_values, report) = block_on(self.client.execute(input.to_vec()))
.map_err(|err| DockerizedError::Execute(ExecuteError::Client(err)))?;
Ok((public_values, report))
@@ -516,16 +499,11 @@ impl zkVM for EreDockerizedzkVM {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
let serialized_input = self
.zkvm
.serialize_inputs(inputs)
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
let (public_values, proof, report) =
block_on(self.client.prove(serialized_input, proof_kind))
block_on(self.client.prove(input.to_vec(), proof_kind))
.map_err(|err| DockerizedError::Prove(ProveError::Client(err)))?;
Ok((public_values, proof, report))
@@ -545,10 +523,6 @@ impl zkVM for EreDockerizedzkVM {
fn sdk_version(&self) -> &'static str {
self.zkvm.sdk_version()
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, reader: R) -> Result<T, zkVMError> {
self.zkvm.deserialize_from(reader)
}
}
fn block_on<T>(future: impl Future<Output = T>) -> T {
@@ -572,14 +546,16 @@ fn home_dir() -> PathBuf {
#[cfg(test)]
mod test {
use crate::{
EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM, SerializedProgram, workspace_dir,
};
use ere_test_utils::{host::*, program::basic::BasicProgramInput};
use ere_zkvm_interface::{Compiler, ProverResourceType};
use std::sync::{Mutex, MutexGuard, OnceLock};
macro_rules! test_compile {
($zkvm:ident, $program:literal) => {
use crate::{
EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM, SerializedProgram, workspace_dir,
};
use ere_test_utils::host::*;
use ere_zkvm_interface::{Compiler, ProverResourceType};
use std::sync::{Mutex, MutexGuard, OnceLock};
use super::*;
fn program() -> &'static SerializedProgram {
static PROGRAM: OnceLock<SerializedProgram> = OnceLock::new();
@@ -644,41 +620,43 @@ mod test {
mod nexus {
test_compile!(Nexus, "basic");
test_execute!(Nexus, BasicProgramInput::valid());
test_prove!(Nexus, BasicProgramInput::valid());
}
mod openvm {
test_compile!(OpenVM, "basic");
test_execute!(OpenVM, BasicProgramIo::valid().into_output_hashed_io());
test_prove!(OpenVM, BasicProgramIo::valid().into_output_hashed_io());
test_execute!(OpenVM, BasicProgramInput::valid().into_output_sha256());
test_prove!(OpenVM, BasicProgramInput::valid().into_output_sha256());
}
mod pico {
test_compile!(Pico, "basic");
test_execute!(Pico, BasicProgramIo::valid());
test_prove!(Pico, BasicProgramIo::valid());
test_execute!(Pico, BasicProgramInput::valid());
test_prove!(Pico, BasicProgramInput::valid());
}
mod risc0 {
test_compile!(Risc0, "basic");
test_execute!(Risc0, BasicProgramIo::valid());
test_prove!(Risc0, BasicProgramIo::valid());
test_execute!(Risc0, BasicProgramInput::valid());
test_prove!(Risc0, BasicProgramInput::valid());
}
mod sp1 {
test_compile!(SP1, "basic");
test_execute!(SP1, BasicProgramIo::valid());
test_prove!(SP1, BasicProgramIo::valid());
test_execute!(SP1, BasicProgramInput::valid());
test_prove!(SP1, BasicProgramInput::valid());
}
mod ziren {
test_compile!(Ziren, "basic");
test_execute!(Ziren, BasicProgramIo::valid());
test_prove!(Ziren, BasicProgramIo::valid());
test_execute!(Ziren, BasicProgramInput::valid());
test_prove!(Ziren, BasicProgramInput::valid());
}
mod zisk {
test_compile!(Zisk, "basic");
test_execute!(Zisk, BasicProgramIo::valid().into_output_hashed_io());
test_prove!(Zisk, BasicProgramIo::valid().into_output_hashed_io());
test_execute!(Zisk, BasicProgramInput::valid().into_output_sha256());
test_prove!(Zisk, BasicProgramInput::valid().into_output_sha256());
}
}

View File

@@ -1,28 +0,0 @@
use crate::ErezkVM;
use ere_zkvm_interface::zkVMError;
use serde::de::DeserializeOwned;
use std::io::Read;
#[path = "../../../zkvm/risc0/src/output.rs"]
mod ere_risc0_output;
impl ErezkVM {
pub fn deserialize_from<R: Read, T: DeserializeOwned>(
&self,
reader: R,
) -> Result<T, zkVMError> {
match self {
// Issue for tracking: https://github.com/eth-act/ere/issues/4.
Self::Jolt => todo!(),
Self::Miden => bincode::deserialize_from(reader).map_err(zkVMError::other),
// Issue for tracking: https://github.com/eth-act/ere/issues/63.
Self::Nexus => todo!(),
Self::OpenVM => unimplemented!("no native serialization in this platform"),
Self::Pico => bincode::deserialize_from(reader).map_err(zkVMError::other),
Self::Risc0 => ere_risc0_output::deserialize_from(reader),
Self::SP1 => bincode::deserialize_from(reader).map_err(zkVMError::other),
Self::Ziren => bincode::deserialize_from(reader).map_err(zkVMError::other),
Self::Zisk => unimplemented!("no native serialization in this platform"),
}
}
}

View File

@@ -7,7 +7,7 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
bincode.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
prost.workspace = true
serde = { workspace = true, features = ["derive"] }
tokio.workspace = true

View File

@@ -1,9 +1,6 @@
use crate::{
api::{
ExecuteRequest, ExecuteResponse, ProveRequest, ProveResponse, VerifyRequest,
VerifyResponse, ZkvmService,
},
input::SerializedInput,
use crate::api::{
ExecuteRequest, ExecuteResponse, ProveRequest, ProveResponse, VerifyRequest, VerifyResponse,
ZkvmService,
};
use anyhow::{Context, Error, bail};
use ere_zkvm_interface::{
@@ -47,10 +44,8 @@ impl zkVMClient {
pub async fn execute(
&self,
input: SerializedInput,
input: Vec<u8>,
) -> Result<(PublicValues, ProgramExecutionReport), Error> {
let input = bincode::serialize(&input).with_context(|| "Failed to serialize input")?;
let request = Request::new(ExecuteRequest { input });
let response = self
@@ -64,19 +59,18 @@ impl zkVMClient {
report,
} = response.into_body();
let report: ProgramExecutionReport = bincode::deserialize(&report)
.with_context(|| "Failed to deserialize execution report")?;
let (report, _): (ProgramExecutionReport, _) =
bincode::serde::decode_from_slice(&report, bincode::config::legacy())
.with_context(|| "Failed to deserialize execution report")?;
Ok((public_values, report))
}
pub async fn prove(
&self,
input: SerializedInput,
input: Vec<u8>,
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), Error> {
let input = bincode::serialize(&input).with_context(|| "Failed to serialize input")?;
let request = Request::new(ProveRequest {
input,
proof_kind: proof_kind as i32,
@@ -94,8 +88,9 @@ impl zkVMClient {
report,
} = response.into_body();
let report: ProgramProvingReport = bincode::deserialize(&report)
.with_context(|| "Failed to deserialize proving report")?;
let (report, _): (ProgramProvingReport, _) =
bincode::serde::decode_from_slice(&report, bincode::config::legacy())
.with_context(|| "Failed to deserialize proving report")?;
Ok((public_values, Proof::new(proof_kind, proof), report))
}

View File

@@ -1,29 +0,0 @@
use ere_zkvm_interface::{Input, InputItem};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct SerializedInput(pub Vec<SerializedInputItem>);
impl From<SerializedInput> for Input {
fn from(value: SerializedInput) -> Self {
Self::from(value.0.into_iter().map(Into::into).collect::<Vec<_>>())
}
}
/// `InputItem` but only `SerializedObject` and `Byte` variants remain.
///
/// The user must serialize the `InputItem::Object` in the way the zkVM expects.
#[derive(Serialize, Deserialize)]
pub enum SerializedInputItem {
SerializedObject(Vec<u8>),
Bytes(Vec<u8>),
}
impl From<SerializedInputItem> for InputItem {
fn from(value: SerializedInputItem) -> Self {
match value {
SerializedInputItem::SerializedObject(bytes) => Self::SerializedObject(bytes),
SerializedInputItem::Bytes(bytes) => Self::Bytes(bytes),
}
}
}

View File

@@ -1,5 +1,4 @@
pub mod client;
pub mod input;
#[allow(dead_code)]
pub(crate) mod api {

View File

@@ -108,8 +108,8 @@ async fn shutdown_signal() {
}
fn construct_zkvm(program: Vec<u8>, resource: ProverResourceType) -> Result<impl zkVM, Error> {
let program =
bincode::deserialize(&program).with_context(|| "Failed to deserialize program")?;
let (program, _) = bincode::serde::decode_from_slice(&program, bincode::config::legacy())
.with_context(|| "Failed to deserialize program")?;
#[cfg(feature = "jolt")]
let zkvm = ere_jolt::EreJolt::new(program, resource);

View File

@@ -1,9 +1,6 @@
use crate::{
api::{
self, ExecuteRequest, ExecuteResponse, ProveRequest, ProveResponse, VerifyRequest,
VerifyResponse, ZkvmService,
},
input::SerializedInput,
use crate::api::{
self, ExecuteRequest, ExecuteResponse, ProveRequest, ProveResponse, VerifyRequest,
VerifyResponse, ZkvmService,
};
use ere_zkvm_interface::{Proof, ProofKind, zkVM};
use twirp::{Request, Response, async_trait::async_trait, invalid_argument};
@@ -31,9 +28,7 @@ impl<T: 'static + zkVM + Send + Sync> ZkvmService for zkVMServer<T> {
) -> twirp::Result<Response<ExecuteResponse>> {
let request = request.into_body();
let input = bincode::deserialize::<SerializedInput>(&request.input)
.map_err(|_| invalid_argument("failed to deserialize input"))?
.into();
let input = request.input;
let (public_values, report) = self
.zkvm
@@ -42,7 +37,7 @@ impl<T: 'static + zkVM + Send + Sync> ZkvmService for zkVMServer<T> {
Ok(Response::new(ExecuteResponse {
public_values,
report: bincode::serialize(&report).unwrap(),
report: bincode::serde::encode_to_vec(&report, bincode::config::legacy()).unwrap(),
}))
}
@@ -52,9 +47,7 @@ impl<T: 'static + zkVM + Send + Sync> ZkvmService for zkVMServer<T> {
) -> twirp::Result<Response<ProveResponse>> {
let request = request.into_body();
let input = bincode::deserialize::<SerializedInput>(&request.input)
.map_err(|_| invalid_argument("failed to deserialize input"))?
.into();
let input = request.input;
let proof_kind = ProofKind::from_repr(request.proof_kind as usize).ok_or_else(|| {
invalid_argument(format!("invalid proof kind: {}", request.proof_kind))
})?;
@@ -67,7 +60,7 @@ impl<T: 'static + zkVM + Send + Sync> ZkvmService for zkVMServer<T> {
Ok(Response::new(ProveResponse {
public_values,
proof: proof.as_bytes().to_vec(),
report: bincode::serialize(&report).unwrap(),
report: bincode::serde::encode_to_vec(&report, bincode::config::legacy()).unwrap(),
}))
}

View File

@@ -0,0 +1,15 @@
[package]
name = "ere-io-serde"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
bincode = { workspace = true, features = ["alloc", "serde"] }
serde.workspace = true
[dev-dependencies]
[lints]
workspace = true

View File

@@ -0,0 +1,62 @@
use crate::IoSerde;
use alloc::vec::Vec;
use bincode::config::{Config, Configuration, Fixint, LittleEndian, NoLimit, Varint};
use core::{
error::Error,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize};
pub use bincode::{
config,
error::{DecodeError, EncodeError},
};
#[derive(Debug)]
pub enum BincodeError {
Encode(EncodeError),
Decode(DecodeError),
}
impl Display for BincodeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Encode(err) => write!(f, "{err:?}"),
Self::Decode(err) => write!(f, "{err:?}"),
}
}
}
impl Error for BincodeError {}
/// IO de/serialization implementation with [`bincode`].
#[derive(Clone, Copy, Debug)]
pub struct Bincode<O>(pub O);
impl Bincode<Configuration<LittleEndian, Fixint, NoLimit>> {
/// `Bincode` with legacy configuration, same as the default of `bincode@1`.
pub fn legacy() -> Self {
Self(bincode::config::legacy())
}
}
impl Bincode<Configuration<LittleEndian, Varint, NoLimit>> {
/// `Bincode` with standard configuration.
pub fn standard() -> Self {
Self(bincode::config::standard())
}
}
impl<O: Config> IoSerde for Bincode<O> {
type Error = BincodeError;
fn serialize<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, Self::Error> {
bincode::serde::encode_to_vec(value, self.0).map_err(BincodeError::Encode)
}
fn deserialize<'a, T: Deserialize<'a>>(&self, bytes: &'a [u8]) -> Result<T, Self::Error> {
let (value, _) = bincode::serde::borrow_decode_from_slice(bytes, self.0)
.map_err(BincodeError::Decode)?;
Ok(value)
}
}

View File

@@ -0,0 +1,22 @@
//! This crate provides IO de/serialization implementation to be shared between
//! host and guest, if the guest is also written in Rust.
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![no_std]
extern crate alloc;
use alloc::vec::Vec;
use core::error::Error;
use serde::{Deserialize, Serialize};
pub mod bincode;
/// IO de/serialization to be shared between host and guest.
pub trait IoSerde {
type Error: Error;
fn serialize<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, Self::Error>;
fn deserialize<'a, T: Deserialize<'a>>(&self, bytes: &'a [u8]) -> Result<T, Self::Error>;
}

View File

@@ -6,13 +6,13 @@ rust-version.workspace = true
license.workspace = true
[dependencies]
bincode.workspace = true
rand = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
sha2.workspace = true
# Local dependencies
ere-zkvm-interface = { workspace = true, optional = true }
ere-io-serde = { workspace = true }
[lints]
workspace = true

View File

@@ -1,60 +1,12 @@
use alloc::vec::Vec;
use core::iter;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
pub struct BasicProgramCore;
pub use sha2::{Digest, Sha256};
impl BasicProgramCore {
pub const BYTES_LENGTH: usize = 32;
/// Platform dependent methods.
pub trait Platform {
/// Read the whole input at once from host.
fn read_input() -> Vec<u8>;
pub fn outputs(inputs: (Vec<u8>, BasicStruct)) -> (Vec<u8>, BasicStruct) {
let (bytes, basic_struct) = inputs;
(bytes.iter().rev().copied().collect(), basic_struct.output())
}
pub fn sha256_outputs(outputs: (Vec<u8>, BasicStruct)) -> [u8; 32] {
let (rev_bytes, basic_struct) = outputs;
Sha256::digest(
iter::empty()
.chain(rev_bytes)
.chain(bincode::serialize(&basic_struct).unwrap())
.collect::<Vec<_>>(),
)
.into()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct BasicStruct {
pub a: u8,
pub b: u16,
pub c: u32,
pub d: u64,
pub e: Vec<u8>,
}
impl BasicStruct {
#[cfg(feature = "host")]
pub fn random(mut rng: impl rand::Rng) -> Self {
let n = rng.random_range(16..32);
BasicStruct {
a: rng.random(),
b: rng.random(),
c: rng.random(),
d: rng.random(),
e: rng.random_iter().take(n).collect(),
}
}
/// Performs some computation (Wrapping add all fields by 1).
pub fn output(&self) -> Self {
Self {
a: self.a.wrapping_add(1),
b: self.b.wrapping_add(1),
c: self.c.wrapping_add(1),
d: self.d.wrapping_add(1),
e: self.e.iter().map(|byte| byte.wrapping_add(1)).collect(),
}
}
/// Write the whole output at once to host.
fn write_output(output: &[u8]);
}

View File

@@ -1,7 +1,8 @@
use crate::guest::{BasicProgramCore, BasicStruct};
use ere_zkvm_interface::{Input, ProofKind, PublicValues, zkVM};
use rand::{Rng, rng};
use std::{fmt::Debug, io::Read, marker::PhantomData, path::PathBuf};
use crate::program::{Program, ProgramInput};
use ere_io_serde::IoSerde;
use ere_zkvm_interface::{ProofKind, PublicValues, zkVM};
use sha2::Digest;
use std::{marker::PhantomData, path::PathBuf};
fn workspace() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@@ -14,29 +15,19 @@ pub fn testing_guest_directory(zkvm_name: &str, program: &str) -> PathBuf {
workspace().join("tests").join(zkvm_name).join(program)
}
pub trait Io {
type Output: Debug + PartialEq;
fn inputs(&self) -> Input;
fn outputs(&self) -> Self::Output;
fn deserialize_outputs(&self, zkvm: &impl zkVM, bytes: &[u8]) -> Self::Output;
}
pub fn run_zkvm_execute(zkvm: &impl zkVM, io: &impl Io) -> PublicValues {
pub fn run_zkvm_execute(zkvm: &impl zkVM, test_case: &impl TestCase) -> PublicValues {
let (public_values, _report) = zkvm
.execute(&io.inputs())
.execute(&test_case.serialized_input())
.expect("execute should not fail with valid input");
assert_eq!(io.deserialize_outputs(&zkvm, &public_values), io.outputs());
test_case.assert_output(&public_values);
public_values
}
pub fn run_zkvm_prove(zkvm: &impl zkVM, io: &impl Io) -> PublicValues {
pub fn run_zkvm_prove(zkvm: &impl zkVM, test_case: &impl TestCase) -> PublicValues {
let (prover_public_values, proof, _report) = zkvm
.prove(&io.inputs(), ProofKind::default())
.prove(&test_case.serialized_input(), ProofKind::default())
.expect("prove should not fail with valid input");
let verifier_public_values = zkvm
@@ -45,126 +36,62 @@ pub fn run_zkvm_prove(zkvm: &impl zkVM, io: &impl Io) -> PublicValues {
assert_eq!(prover_public_values, verifier_public_values);
assert_eq!(
io.deserialize_outputs(&zkvm, &verifier_public_values),
io.outputs()
);
test_case.assert_output(&verifier_public_values);
verifier_public_values
}
/// The basic program takes 2 inputs:
/// - `Vec<u8>` - random bytes
/// - [`BasicStruct`] - structure filled with random values
///
/// Commit 2 outputs:
/// - `Vec<u8>` that should be reverse of the input random bytes.
/// - [`BasicStruct`] that should be computed by [`BasicStruct::output`].
#[derive(Clone)]
pub struct BasicProgramIo {
bytes: Vec<u8>,
basic_struct: BasicStruct,
/// Test case for specific [`Program`] that provides serialized
/// [`Program::Input`], and is able to assert if the [`PublicValues`] returned
/// by [`zkVM`] methods is correct or not.
pub trait TestCase {
fn serialized_input(&self) -> Vec<u8>;
fn assert_output(&self, public_values: &[u8]);
}
impl Io for BasicProgramIo {
type Output = (Vec<u8>, BasicStruct);
fn inputs(&self) -> Input {
let mut inputs = Input::new();
inputs.write_bytes(self.bytes.clone());
inputs.write(self.basic_struct.clone());
inputs
/// Auto-implementation [`TestCase`] for [`ProgramInput`] that can be shared
/// between host and guest, if the guest is also written in Rust.
impl<T: ProgramInput> TestCase for T {
fn serialized_input(&self) -> Vec<u8> {
T::Program::io_serde().serialize(&self.clone()).unwrap()
}
fn outputs(&self) -> Self::Output {
BasicProgramCore::outputs((self.bytes.clone(), self.basic_struct.clone()))
}
fn deserialize_outputs(&self, zkvm: &impl zkVM, mut bytes: &[u8]) -> Self::Output {
let mut rev_bytes = vec![0; self.bytes.len()];
bytes.read_exact(&mut rev_bytes).unwrap();
let basic_struct_output = zkvm.deserialize_from(bytes).unwrap();
(rev_bytes, basic_struct_output)
fn assert_output(&self, public_values: &[u8]) {
assert_eq!(
T::Program::compute(self.clone()),
T::Program::io_serde().deserialize(public_values).unwrap()
)
}
}
impl BasicProgramIo {
pub fn valid() -> Self {
let rng = &mut rng();
Self {
bytes: rng
.random_iter()
.take(BasicProgramCore::BYTES_LENGTH)
.collect(),
basic_struct: BasicStruct::random(rng),
}
}
pub fn into_output_hashed_io(self) -> impl Io {
OutputHashedIo::new(self, BasicProgramCore::sha256_outputs)
}
/// Empty input that should trigger deserialization failure in guest
/// program.
pub fn empty() -> Input {
Input::new()
}
/// Input with invalid type that should trigger deserialization
/// failure in guest program.
pub fn invalid_type() -> Input {
let mut inputs = Input::new();
inputs.write(0u64);
inputs.write_bytes(vec![0, 1, 2, 3]);
inputs
}
/// Input with invalid data that should trigger assertion failure in guest
/// program.
pub fn invalid_data() -> Input {
let mut inputs = Input::new();
inputs.write_bytes(vec![0; BasicProgramCore::BYTES_LENGTH + 1]);
inputs.write(BasicStruct::default());
inputs
}
}
pub struct OutputHashedIo<T, H, D> {
/// Wrapper for [`TestCase`] that asserts output to be hashed.
pub struct OutputHashedTestCase<T, D> {
inner: T,
hasher: H,
_marker: PhantomData<D>,
}
impl<T, H, D> OutputHashedIo<T, H, D> {
pub fn new(inner: T, hasher: H) -> Self {
impl<T, D> OutputHashedTestCase<T, D> {
pub fn new(inner: T) -> Self {
Self {
inner,
hasher,
_marker: PhantomData,
}
}
}
impl<T, H, D> Io for OutputHashedIo<T, H, D>
impl<T, D> TestCase for OutputHashedTestCase<T, D>
where
T: Io,
H: Fn(T::Output) -> D,
D: Clone + Debug + Default + PartialEq + AsMut<[u8]>,
T: ProgramInput,
D: Digest,
{
type Output = D;
fn inputs(&self) -> Input {
self.inner.inputs()
fn serialized_input(&self) -> Vec<u8> {
self.inner.serialized_input()
}
fn outputs(&self) -> Self::Output {
(self.hasher)(self.inner.outputs())
}
fn deserialize_outputs(&self, _: &impl zkVM, bytes: &[u8]) -> Self::Output {
let mut digest = D::default();
assert_eq!(digest.as_mut().len(), bytes.len());
digest.as_mut().copy_from_slice(bytes);
digest
fn assert_output(&self, public_values: &[u8]) {
let output = T::Program::compute(self.inner.clone());
let digest = D::digest(T::Program::io_serde().serialize(&output).unwrap());
assert_eq!(digest.as_slice(), public_values)
}
}

View File

@@ -3,6 +3,7 @@
extern crate alloc;
pub mod guest;
pub mod program;
#[cfg(feature = "host")]
pub mod host;

View File

@@ -0,0 +1,28 @@
use crate::guest::Platform;
use core::fmt::Debug;
use ere_io_serde::IoSerde;
use serde::{Serialize, de::DeserializeOwned};
pub mod basic;
/// Program that can be ran given [`Platform`] implementation.
pub trait Program {
type Input: Serialize + DeserializeOwned;
type Output: Debug + PartialEq + Serialize + DeserializeOwned;
fn io_serde() -> impl IoSerde;
fn compute(input: Self::Input) -> Self::Output;
fn run<P: Platform>() {
let io_serde = Self::io_serde();
let input = io_serde.deserialize(&P::read_input()).unwrap();
let output = io_serde.serialize(&Self::compute(input)).unwrap();
P::write_output(&output);
}
}
/// [`Program::Input`] that has [`TestCase`] auto-implemented.
pub trait ProgramInput: Clone + Serialize {
type Program: Program<Input = Self>;
}

View File

@@ -0,0 +1,95 @@
use crate::program::Program;
use alloc::vec::Vec;
use core::panic;
use ere_io_serde::{IoSerde, bincode::Bincode};
use serde::{Deserialize, Serialize};
/// The basic program takes `BasicProgramInput` as input, and computes
/// `BasicProgramOutput` as output.
pub struct BasicProgram;
impl Program for BasicProgram {
type Input = BasicProgramInput;
type Output = BasicProgramOutput;
fn io_serde() -> impl IoSerde {
Bincode::legacy()
}
fn compute(input: BasicProgramInput) -> BasicProgramOutput {
if input.should_panic {
panic!("invalid data");
}
BasicProgramOutput {
a: input.a.wrapping_add(1),
b: input.b.wrapping_add(1),
c: input.c.wrapping_add(1),
d: input.d.wrapping_add(1),
e: input.e.iter().map(|byte| byte.wrapping_add(1)).collect(),
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct BasicProgramInput {
pub should_panic: bool,
pub a: u8,
pub b: u16,
pub c: u32,
pub d: u64,
pub e: Vec<u8>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BasicProgramOutput {
pub e: Vec<u8>,
pub d: u64,
pub c: u32,
pub b: u16,
pub a: u8,
}
#[cfg(feature = "host")]
mod host {
use crate::{
host::{OutputHashedTestCase, TestCase},
program::{
ProgramInput,
basic::{BasicProgram, BasicProgramInput},
},
};
use rand::{Rng, rng};
use sha2::Sha256;
impl ProgramInput for BasicProgramInput {
type Program = BasicProgram;
}
impl BasicProgramInput {
pub fn valid() -> Self {
let mut rng = rng();
let n = rng.random_range(16..32);
Self {
should_panic: false,
a: rng.random(),
b: rng.random(),
c: rng.random(),
d: rng.random(),
e: rng.random_iter().take(n).collect(),
}
}
/// Invalid input that causes panic in guest program.
pub fn invalid() -> Self {
Self {
should_panic: true,
..Default::default()
}
}
/// Wrap into [`OutputHashedTestCase`] with [`Sha256`].
pub fn into_output_sha256(self) -> impl TestCase {
OutputHashedTestCase::<_, Sha256>::new(self)
}
}
}

View File

@@ -7,7 +7,6 @@ license.workspace = true
[dependencies]
auto_impl.workspace = true
erased-serde.workspace = true
indexmap = { workspace = true, features = ["serde"] }
serde = { workspace = true, features = ["derive"] }
strum = { workspace = true, features = ["derive"] }
@@ -17,7 +16,7 @@ thiserror.workspace = true
clap = { workspace = true, features = ["derive"], optional = true }
[dev-dependencies]
bincode.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
serde_json.workspace = true
[lints]

View File

@@ -1,178 +0,0 @@
use erased_serde::Serialize as ErasedSerialize;
use serde::Serialize;
use std::{fmt::Debug, sync::Arc};
#[derive(Clone)]
pub enum InputItem {
/// A serializable object stored as a trait object
Object(Arc<dyn ErasedSerialize + Send + Sync>),
/// A serialized object with zkvm specific serializer.
///
/// This is only for `ere-dockerized` to serialize the inputs to be able to
/// pass to `ere-server` to do the actual action, in normal case this should
/// be avoided, instead [`InputItem::Object`] should be used.
SerializedObject(Vec<u8>),
/// Serialized bytes with opaque serializer (e.g. bincode)
Bytes(Vec<u8>),
}
impl Debug for InputItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InputItem::Object(_) => f.write_str("Object(<erased>)"),
InputItem::SerializedObject(bytes) => {
f.debug_tuple("SerializedObject").field(bytes).finish()
}
InputItem::Bytes(bytes) => f.debug_tuple("Bytes").field(bytes).finish(),
}
}
}
/// Represents a builder for input data to be passed to a ZKVM guest program.
#[derive(Debug, Clone)]
pub struct Input {
items: Vec<InputItem>,
}
impl Default for Input {
fn default() -> Self {
Self::new()
}
}
impl Input {
/// Create an empty input buffer.
pub fn new() -> Self {
Self {
items: Default::default(),
}
}
/// Write a serializable value as a trait object
pub fn write<T: Serialize + Send + Sync + 'static>(&mut self, value: T) {
self.items.push(InputItem::Object(Arc::new(value)));
}
/// Write pre-serialized bytes directly
pub fn write_bytes(&mut self, bytes: Vec<u8>) {
self.items.push(InputItem::Bytes(bytes));
}
/// Get the number of items stored
pub fn len(&self) -> usize {
self.items.len()
}
/// Check if the buffer is empty
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
/// Iterate over the items
pub fn iter(&self) -> std::slice::Iter<'_, InputItem> {
self.items.iter()
}
}
impl From<Vec<InputItem>> for Input {
fn from(items: Vec<InputItem>) -> Self {
Self { items }
}
}
#[cfg(test)]
mod input_erased_tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Person {
name: String,
age: u32,
}
#[test]
fn test_write_object() {
let mut input = Input::new();
let person = Person {
name: "Alice".to_string(),
age: 30,
};
input.write(person);
assert_eq!(input.len(), 1);
match &input.items[0] {
InputItem::Object(_) => (), // Success
InputItem::SerializedObject(_) | InputItem::Bytes(_) => {
panic!("Expected Object, got Bytes")
}
}
}
#[test]
fn test_write_bytes() {
let mut input = Input::new();
let bytes = vec![1, 2, 3, 4, 5];
input.write_bytes(bytes.clone());
assert_eq!(input.len(), 1);
match &input.items[0] {
InputItem::Bytes(stored_bytes) => assert_eq!(stored_bytes.to_vec(), bytes),
InputItem::Object(_) | InputItem::SerializedObject(_) => {
panic!("Expected Bytes, got Object")
}
}
}
#[test]
fn test_mixed_usage() {
let mut input = Input::new();
let person = Person {
name: "Charlie".to_string(),
age: 35,
};
// Mix different write methods
input.write(42i32); // Object
let serialized = bincode::serialize(&person).unwrap();
input.write_bytes(serialized); // Bytes (serialized)
input.write_bytes(vec![10, 20, 30]); // Bytes (raw)
input.write("hello".to_string()); // Object
assert_eq!(input.len(), 4);
// Verify types
match &input.items[0] {
InputItem::Object(_) => (),
_ => panic!(),
}
match &input.items[1] {
InputItem::Bytes(_) => (),
_ => panic!(),
}
match &input.items[2] {
InputItem::Bytes(_) => (),
_ => panic!(),
}
match &input.items[3] {
InputItem::Object(_) => (),
_ => panic!(),
}
}
#[test]
fn test_iteration() {
let mut input = Input::new();
input.write(1);
input.write(2);
input.write_bytes(vec![3, 4, 5]);
let count = input.iter().count();
assert_eq!(count, 3);
}
}

View File

@@ -2,13 +2,10 @@
#![allow(clippy::double_parens)]
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{io::Read, path::Path};
use std::path::Path;
use strum::{EnumDiscriminants, EnumIs, EnumTryAs, FromRepr};
use thiserror::Error;
mod input;
pub use input::{Input, InputItem};
mod reports;
pub use reports::{ProgramExecutionReport, ProgramProvingReport};
@@ -144,13 +141,13 @@ impl Proof {
/// 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 provided inputs.
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError>;
/// Executes the program with the given input.
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError>;
/// Creates a proof of the program execution with given inputs.
/// Creates a proof of the program execution with given input.
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError>;
@@ -164,17 +161,4 @@ pub trait zkVM {
/// Returns the version of the zkVM SDK (e.g. 0.1.0)
fn sdk_version(&self) -> &'static str;
/// Deserializes an object from a [`Read`]er.
///
/// If a guest program has multiple objects committed/revealed, one can do
/// the following to extract them in sequence:
///
/// ```ignore
/// let public_values = zkvm.verify(&proof)?;
/// let mut reader = public_values.as_slice();
/// let v0: T = zkvm.deserialize_from(&mut reader)?;
/// let v1: U = zkvm.deserialize_from(&mut reader)?;
/// ```
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, reader: R) -> Result<T, zkVMError>;
}

View File

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

View File

@@ -60,7 +60,7 @@ impl Compiler for RustRv32ima {
mod tests {
use crate::{EreJolt, compiler::RustRv32ima};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
#[test]
fn test_compile() {
@@ -75,6 +75,6 @@ mod tests {
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&Input::new()).unwrap();
zkvm.execute(&[]).unwrap();
}
}

View File

@@ -48,7 +48,7 @@ impl Compiler for RustRv32imaCustomized {
mod tests {
use crate::{EreJolt, compiler::RustRv32imaCustomized};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
#[test]
fn test_compile() {
@@ -63,6 +63,6 @@ mod tests {
let program = RustRv32imaCustomized.compile(&guest_directory).unwrap();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&Input::new()).unwrap();
zkvm.execute(&[]).unwrap();
}
}

View File

@@ -1,6 +1,5 @@
use crate::{EreJoltProof, error::VerifyError};
use common::constants::{DEFAULT_MAX_BYTECODE_SIZE, DEFAULT_MAX_TRACE_LENGTH, DEFAULT_MEMORY_SIZE};
use ere_zkvm_interface::Input;
use jolt::{
Jolt, JoltHyperKZGProof, JoltProverPreprocessing, JoltVerifierPreprocessing, MemoryConfig,
MemoryLayout, RV32IJoltVM, tracer::JoltDevice,
@@ -43,7 +42,7 @@ pub fn preprocess_verifier(
pub fn prove_generic(
program: &jolt::host::Program,
preprocessing: JoltProverPreprocessing<4, jolt::F, jolt::PCS, jolt::ProofTranscript>,
_inputs: &Input,
_input: &[u8],
) -> EreJoltProof {
let mut program = program.clone();

View File

@@ -7,15 +7,11 @@ use crate::{
};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ere_zkvm_interface::{
Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use jolt::{JoltHyperKZGProof, JoltProverPreprocessing, JoltVerifierPreprocessing};
use serde::de::DeserializeOwned;
use std::{
env, fs,
io::{Cursor, Read},
};
use std::{env, fs, io::Cursor};
use tempfile::TempDir;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
@@ -52,10 +48,7 @@ impl EreJolt {
}
impl zkVM for EreJolt {
fn execute(
&self,
_inputs: &Input,
) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
fn execute(&self, _input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let (_tempdir, program) = program(&self.elf)?;
// TODO: Check how to pass private input to jolt, issue for tracking:
@@ -71,7 +64,7 @@ impl zkVM for EreJolt {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
if proof_kind != ProofKind::Compressed {
@@ -81,7 +74,7 @@ impl zkVM for EreJolt {
let (_tempdir, program) = program(&self.elf)?;
let now = std::time::Instant::now();
let proof = prove_generic(&program, self.prover_preprocessing.clone(), inputs);
let proof = prove_generic(&program, self.prover_preprocessing.clone(), input);
let elapsed = now.elapsed();
let mut proof_bytes = Vec::new();
@@ -122,11 +115,6 @@ impl zkVM for EreJolt {
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, _reader: R) -> Result<T, zkVMError> {
// Issue for tracking: https://github.com/eth-act/ere/issues/4.
todo!()
}
}
/// Create `jolt::host::Program` by storing the compiled `elf` to a temporary

View File

@@ -6,7 +6,7 @@ rust-version.workspace = true
license.workspace = true
[dependencies]
bincode.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true

View File

@@ -51,7 +51,7 @@ pub enum ExecuteError {
#[error("Invalid input format: {0}")]
InvalidInput(String),
#[error("Serialization failed")]
Serialization(#[from] bincode::Error),
Serialization(#[from] bincode::error::EncodeError),
#[error("Failed to deserialize Miden program")]
ProgramDeserialization(#[from] DeserializationError),
}
@@ -62,8 +62,8 @@ pub enum ProveError {
Proving(#[from] ExecutionError),
#[error("Invalid input format: {0}")]
InvalidInput(String),
#[error("Serialization failed")]
Serialization(#[from] bincode::Error),
#[error("Output serialization failed")]
OutputSerialization(#[from] bincode::error::EncodeError),
}
#[derive(Debug, Error)]
@@ -73,5 +73,7 @@ pub enum VerifyError {
#[error("Proof or associated data deserialization failed")]
MidenDeserialization(#[from] DeserializationError),
#[error("Proof bundle deserialization failed")]
BundleDeserialization(#[from] bincode::Error),
BundleDeserialization(#[from] bincode::error::DecodeError),
#[error("Output serialization failed")]
OutputSerialization(#[from] bincode::error::EncodeError),
}

View File

@@ -1,52 +0,0 @@
use crate::error::{ExecuteError, MidenError};
use ere_zkvm_interface::{Input, InputItem, PublicValues};
use miden_processor::{AdviceInputs, StackInputs, StackOutputs};
/// Returns Miden compatible inputs from `ere_zkvm_interface::Input`.
///
/// All inputs are serialized and concatenated, then placed onto the advice tape.
/// The stack is left empty.
pub fn generate_miden_inputs(inputs: &Input) -> Result<(StackInputs, AdviceInputs), MidenError> {
let mut all_bytes = Vec::new();
for item in inputs.iter() {
match item {
InputItem::Object(obj) => {
bincode::serialize_into(&mut all_bytes, &**obj)
.map_err(ExecuteError::Serialization)?;
}
InputItem::SerializedObject(bytes) | InputItem::Bytes(bytes) => {
all_bytes.extend_from_slice(bytes);
}
}
}
// Convert the byte stream into u64 words for the Miden VM.
let advice_words: Vec<u64> = {
let mut words: Vec<u64> = all_bytes
.chunks_exact(8)
.map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap()))
.collect();
let remainder = all_bytes.chunks_exact(8).remainder();
if !remainder.is_empty() {
let mut last_chunk = [0u8; 8];
last_chunk[..remainder.len()].copy_from_slice(remainder);
words.push(u64::from_le_bytes(last_chunk));
}
words
};
let advice_inputs = AdviceInputs::default()
.with_stack_values(advice_words)
.map_err(|e| ExecuteError::InvalidInput(e.to_string()))?;
Ok((StackInputs::default(), advice_inputs))
}
// Convert Miden stack outputs to public values
pub fn outputs_to_public_values(outputs: &StackOutputs) -> Result<PublicValues, bincode::Error> {
let output_ints: Vec<u64> = outputs.iter().map(|f| f.as_int()).collect();
bincode::serialize(&output_ints)
}

View File

@@ -1,10 +1,9 @@
use crate::{
compiler::MidenProgram,
error::{ExecuteError, MidenError, VerifyError},
io::{generate_miden_inputs, outputs_to_public_values},
error::{ExecuteError, MidenError, ProveError, VerifyError},
};
use ere_zkvm_interface::{
Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use miden_core::{
@@ -14,17 +13,18 @@ use miden_core::{
use miden_processor::{
DefaultHost, ExecutionOptions, ProgramInfo, StackInputs, StackOutputs, execute as miden_execute,
};
use miden_prover::{ExecutionProof, ProvingOptions, prove as miden_prove};
use miden_prover::{AdviceInputs, ExecutionProof, ProvingOptions, prove as miden_prove};
use miden_stdlib::StdLibrary;
use miden_verifier::verify as miden_verify;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{env, io::Read, time::Instant};
use serde::{Deserialize, Serialize};
use std::{env, time::Instant};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod compiler;
pub mod error;
mod io;
pub use miden_core::{Felt, FieldElement};
#[derive(Serialize, Deserialize)]
struct MidenProofBundle {
@@ -33,6 +33,14 @@ struct MidenProofBundle {
proof: Vec<u8>,
}
/// [`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,
}
@@ -54,8 +62,12 @@ impl EreMiden {
}
impl zkVM for EreMiden {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let (stack_inputs, advice_inputs) = generate_miden_inputs(inputs)?;
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let stack_inputs = StackInputs::default();
let advice_inputs = AdviceInputs::default().with_stack(
bytes_to_felts(input)
.map_err(|err| MidenError::Execute(ExecuteError::InvalidInput(err)))?,
);
let mut host = Self::setup_host()?;
let start = Instant::now();
@@ -68,8 +80,7 @@ impl zkVM for EreMiden {
)
.map_err(|e| MidenError::Execute(e.into()))?;
let public_values = outputs_to_public_values(trace.stack_outputs())
.map_err(|e| MidenError::Execute(e.into()))?;
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,
@@ -82,14 +93,18 @@ impl zkVM for EreMiden {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
if proof_kind != ProofKind::Compressed {
panic!("Only Compressed proof kind is supported.");
}
let (stack_inputs, advice_inputs) = generate_miden_inputs(inputs)?;
let stack_inputs = StackInputs::default();
let advice_inputs = AdviceInputs::default().with_stack(
bytes_to_felts(input)
.map_err(|err| MidenError::Prove(ProveError::InvalidInput(err)))?,
);
let mut host = Self::setup_host()?;
let start = Instant::now();
@@ -104,8 +119,7 @@ impl zkVM for EreMiden {
)
.map_err(|e| MidenError::Prove(e.into()))?;
let public_values =
outputs_to_public_values(&stack_outputs).map_err(|e| MidenError::Prove(e.into()))?;
let public_values = felts_to_bytes(stack_outputs.as_slice());
let bundle = MidenProofBundle {
stack_inputs: stack_inputs.to_bytes(),
@@ -113,7 +127,8 @@ impl zkVM for EreMiden {
proof: proof.to_bytes(),
};
let proof_bytes = bincode::serialize(&bundle).map_err(|e| MidenError::Prove(e.into()))?;
let proof_bytes = bincode::serde::encode_to_vec(&bundle, bincode::config::legacy())
.map_err(|e| MidenError::Prove(e.into()))?;
Ok((
public_values,
@@ -127,8 +142,9 @@ impl zkVM for EreMiden {
return Err(zkVMError::other("Only Compressed proof kind is supported."));
};
let bundle: MidenProofBundle = bincode::deserialize(proof)
.map_err(|e| MidenError::Verify(VerifyError::BundleDeserialization(e)))?;
let (bundle, _): (MidenProofBundle, _) =
bincode::serde::decode_from_slice(proof, bincode::config::legacy())
.map_err(|e| MidenError::Verify(VerifyError::BundleDeserialization(e)))?;
let program_info: ProgramInfo = self.program.clone().into();
@@ -147,12 +163,7 @@ impl zkVM for EreMiden {
)
.map_err(|e| MidenError::Verify(e.into()))?;
Ok(outputs_to_public_values(&stack_outputs)
.map_err(|e| MidenError::Verify(VerifyError::BundleDeserialization(e)))?)
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, reader: R) -> Result<T, zkVMError> {
bincode::deserialize_from(reader).map_err(|e| MidenError::Execute(e.into()).into())
Ok(felts_to_bytes(stack_outputs.as_slice()))
}
fn name(&self) -> &'static str {
@@ -164,14 +175,38 @@ impl zkVM for EreMiden {
}
}
/// 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>, String> {
if bytes.len() % 8 != 0 {
return Err(format!(
"Invalid bytes length {}, expected multiple of 8",
bytes.len()
));
}
bytes
.chunks(8)
.map(|bytes| Felt::try_from(u64::from_le_bytes(bytes.try_into().unwrap())))
.collect::<Result<Vec<Felt>, _>>()
.map_err(|err| err.to_string())
}
#[cfg(test)]
mod tests {
use crate::{
EreMiden,
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, Input, ProofKind, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
fn load_miden_program(guest_name: &str) -> MidenProgram {
MidenAsm
@@ -184,25 +219,21 @@ mod tests {
let program = load_miden_program("add");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let const_a = 2518446814u64;
let const_b = 1949327098u64;
let const_a = -Felt::ONE;
let const_b = Felt::ONE / Felt::ONE.double();
let expected_sum = const_a + const_b;
let mut inputs = Input::new();
inputs.write(const_a);
inputs.write(const_b);
let input = felts_to_bytes(&[const_a, const_b]);
// Prove
let (prover_public_values, proof, _) = zkvm.prove(&inputs, ProofKind::default()).unwrap();
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_eq!(prover_public_values, verifier_public_values);
// Assert output
let output: Vec<u64> = zkvm
.deserialize_from(verifier_public_values.as_slice())
.unwrap();
let output = bytes_to_felts(&verifier_public_values).unwrap();
assert_eq!(output[0], expected_sum);
}
@@ -211,38 +242,32 @@ mod tests {
let program = load_miden_program("fib");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let n_iterations = 50u64;
let expected_fib = 12_586_269_025u64;
let n_iterations = 50u32;
let expected_fib = Felt::try_from(12_586_269_025u64).unwrap();
let mut inputs = Input::new();
inputs.write(0u64);
inputs.write(1u64);
inputs.write(n_iterations);
let input = felts_to_bytes(&[Felt::from(0u32), Felt::from(1u32), Felt::from(n_iterations)]);
// Prove
let (prover_public_values, proof, _) = zkvm.prove(&inputs, ProofKind::default()).unwrap();
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_eq!(prover_public_values, verifier_public_values);
// Assert output
let output: Vec<u64> = zkvm
.deserialize_from(verifier_public_values.as_slice())
.unwrap();
let output = bytes_to_felts(&verifier_public_values).unwrap();
assert_eq!(output[0], expected_fib);
}
#[test]
fn test_invalid_inputs() {
fn test_invalid_input() {
let program = load_miden_program("add");
let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap();
let empty_inputs = Input::new();
let empty_inputs = Vec::new();
assert!(zkvm.execute(&empty_inputs).is_err());
let mut insufficient_inputs = Input::new();
insufficient_inputs.write(5u64);
let insufficient_inputs = felts_to_bytes(&[Felt::from(5u32)]);
assert!(zkvm.execute(&insufficient_inputs).is_err());
}
}

View File

@@ -6,15 +6,15 @@ rust-version.workspace = true
license.workspace = true
[dependencies]
bincode.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
postcard.workspace = true
serde.workspace = true
thiserror.workspace = true
tracing.workspace = true
# Nexus dependencies
nexus-sdk.workspace = true
nexus-core.workspace = true
nexus-sdk.workspace = true
nexus-vm.workspace = true
# Local dependencies

View File

@@ -39,7 +39,7 @@ pub enum ProveError {
#[error("nexus execution failed: {0}")]
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Serialising proof with `bincode` failed: {0}")]
Bincode(#[from] bincode::Error),
Bincode(#[from] bincode::error::EncodeError),
#[error("Serialising input with `postcard` failed: {0}")]
Postcard(String),
}
@@ -49,5 +49,5 @@ pub enum VerifyError {
#[error("nexus verification failed: {0}")]
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Deserialising proof failed: {0}")]
Bincode(#[from] bincode::Error),
Bincode(#[from] bincode::error::DecodeError),
}

View File

@@ -5,8 +5,8 @@ use crate::{
error::{NexusError, ProveError, VerifyError},
};
use ere_zkvm_interface::{
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMError,
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use nexus_core::nvm::{self, ElfFile};
use nexus_sdk::{
@@ -14,8 +14,8 @@ use nexus_sdk::{
stwo::seq::{Proof as NexusProof, Stwo},
};
use nexus_vm::trace::Trace;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{io::Read, time::Instant};
use serde::{Deserialize, Serialize};
use std::time::Instant;
use tracing::info;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
@@ -34,24 +34,25 @@ pub struct EreNexus {
}
impl EreNexus {
pub fn new(elf: NexusProgram, _resource_type: ProverResourceType) -> Self {
pub fn new(elf: NexusProgram, resource: ProverResourceType) -> Self {
if !matches!(resource, ProverResourceType::Cpu) {
panic!("Network or GPU proving not yet implemented for Nexus. Use CPU resource type.");
}
Self { elf }
}
}
impl zkVM for EreNexus {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let elf = ElfFile::from_bytes(&self.elf)
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?;
let input_bytes = serialize_inputs(inputs)?;
// 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_bytes.is_empty() {
let mut private_encoded = if input.is_empty() {
Vec::new()
} else {
postcard::to_stdvec_cobs(&input_bytes)
postcard::to_stdvec_cobs(&input)
.map_err(|e| NexusError::Prove(ProveError::Postcard(e.to_string())))?
};
@@ -64,24 +65,25 @@ impl zkVM for EreNexus {
let start = Instant::now();
let (view, trace) = nvm::k_trace(elf, &[], &[], private_encoded.as_slice(), 1)
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?;
let execution_duration = start.elapsed();
let public_values = view
.public_output::<Vec<u8>>()
.public_output()
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?;
Ok((
public_values,
ProgramExecutionReport {
total_num_cycles: trace.get_num_steps() as u64,
region_cycles: Default::default(), // not available
execution_duration: start.elapsed(),
execution_duration,
..Default::default()
},
))
}
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
if proof_kind != ProofKind::Compressed {
@@ -94,29 +96,28 @@ impl zkVM for EreNexus {
let prover =
Stwo::new(&elf).map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?;
let input_bytes = serialize_inputs(inputs)?;
let start = Instant::now();
let (view, proof) = prover
.prove_with_input::<Vec<u8>, ()>(&input_bytes, &())
.prove_with_input(&input, &())
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?;
let proving_time = start.elapsed();
let public_values = view
.public_output::<Vec<u8>>()
.public_output()
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))?;
let proof_bundle = NexusProofBundle {
proof,
public_values: public_values.clone(),
public_values,
};
let proof_bytes = bincode::serialize(&proof_bundle)
let proof_bytes = bincode::serde::encode_to_vec(&proof_bundle, bincode::config::legacy())
.map_err(|err| NexusError::Prove(ProveError::Bincode(err)))?;
Ok((
public_values,
proof_bundle.public_values,
Proof::Compressed(proof_bytes),
ProgramProvingReport::new(start.elapsed()),
ProgramProvingReport::new(proving_time),
))
}
@@ -127,8 +128,9 @@ impl zkVM for EreNexus {
info!("Verifying proof...");
let proof_bundle = bincode::deserialize::<NexusProofBundle>(proof)
.map_err(|err| NexusError::Verify(VerifyError::Bincode(err)))?;
let (proof_bundle, _): (NexusProofBundle, _) =
bincode::serde::decode_from_slice(proof, bincode::config::legacy())
.map_err(|err| NexusError::Verify(VerifyError::Bincode(err)))?;
proof_bundle
.proof
@@ -153,55 +155,21 @@ impl zkVM for EreNexus {
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, reader: R) -> Result<T, zkVMError> {
let mut buf = vec![0; 1 << 20]; // allocate 1MiB as buffer.
let (value, _) = postcard::from_io((reader, &mut buf)).map_err(zkVMError::other)?;
Ok(value)
}
}
/// Serializes nexus program inputs
pub fn serialize_inputs(inputs: &Input) -> Result<Vec<u8>, NexusError> {
inputs
.iter()
.try_fold(Vec::new(), |mut acc, item| -> Result<Vec<u8>, NexusError> {
match item {
InputItem::Object(obj) => {
let buffer = postcard::to_allocvec(obj.as_ref())
.map_err(|e| NexusError::Prove(ProveError::Postcard(e.to_string())))?;
acc.extend_from_slice(&buffer);
Ok(acc)
}
InputItem::SerializedObject(bytes) => {
acc.extend_from_slice(bytes);
Ok(acc)
}
InputItem::Bytes(bytes) => {
let buffer = postcard::to_allocvec(bytes)
.map_err(|e| NexusError::Prove(ProveError::Postcard(e.to_string())))?;
acc.extend_from_slice(&buffer);
Ok(acc)
}
}
})
}
#[cfg(test)]
mod tests {
use crate::{EreNexus, compiler::RustRv32i};
use ere_test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
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, Input, ProofKind, ProverResourceType, zkVM};
use serde::{Deserialize, Serialize};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
static BASIC_PROGRAM: OnceLock<Vec<u8>> = OnceLock::new();
static FIB_PROGRAM: OnceLock<Vec<u8>> = OnceLock::new();
fn basic_program() -> Vec<u8> {
BASIC_PROGRAM
fn basic_program() -> NexusProgram {
static PROGRAM: OnceLock<NexusProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
RustRv32i
.compile(&testing_guest_directory("nexus", "basic"))
@@ -210,36 +178,22 @@ mod tests {
.clone()
}
fn fib_program() -> Vec<u8> {
FIB_PROGRAM
.get_or_init(|| {
RustRv32i
.compile(&testing_guest_directory("nexus", "fib"))
.unwrap()
})
.clone()
}
#[test]
fn test_execute() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu);
let io = BasicProgramIo::valid();
run_zkvm_execute(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_inputs() {
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu);
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.execute(&inputs).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
@@ -248,60 +202,17 @@ mod tests {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu);
let io = BasicProgramIo::valid();
run_zkvm_prove(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_inputs() {
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu);
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.prove(&inputs, ProofKind::default()).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
#[test]
fn test_fibonacci() {
#[derive(Serialize, Deserialize)]
struct FibInput {
n: u32,
}
let program = fib_program();
let zkvm = EreNexus::new(program, ProverResourceType::Cpu);
let mut input = Input::new();
input.write(FibInput { n: 10 });
let (public_values, _report) = zkvm.execute(&input).expect("Execution failed");
let result: u32 = zkvm
.deserialize_from(&public_values[..])
.expect("Failed to deserialize output");
assert_eq!(result, 55, "fib(10) should be 55");
let mut input = Input::new();
input.write(FibInput { n: 0 });
let (public_values, _report) = zkvm.execute(&input).expect("Execution failed");
let result: u32 = zkvm
.deserialize_from(&public_values[..])
.expect("Failed to deserialize output");
assert_eq!(result, 0, "fib(0) should be 0");
let mut input = Input::new();
input.write(FibInput { n: 1 });
let (public_values, _report) = zkvm.execute(&input).expect("Execution failed");
let result: u32 = zkvm
.deserialize_from(&public_values[..])
.expect("Failed to deserialize output");
assert_eq!(result, 1, "fib(1) should be 1");
}
}

View File

@@ -1,5 +1,5 @@
use crate::{
OpenVMProgram,
compiler::OpenVMProgram,
error::{CompileError, OpenVMError},
};
use ere_compile_utils::CargoBuildCmd;
@@ -61,7 +61,7 @@ impl Compiler for RustRv32ima {
mod tests {
use crate::{EreOpenVM, compiler::RustRv32ima};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
#[test]
fn test_compile() {
@@ -76,6 +76,6 @@ mod tests {
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&Input::new()).unwrap();
zkvm.execute(&[]).unwrap();
}
}

View File

@@ -5,8 +5,8 @@ use crate::{
error::{CommonError, ExecuteError, OpenVMError, ProveError, VerifyError},
};
use ere_zkvm_interface::{
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMError,
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use openvm_circuit::arch::instructions::exe::VmExe;
use openvm_continuations::verifier::internal::types::VmStarkProof;
@@ -20,8 +20,7 @@ use openvm_sdk::{
};
use openvm_stark_sdk::openvm_stark_backend::p3_field::PrimeField32;
use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE};
use serde::de::DeserializeOwned;
use std::{env, io::Read, path::PathBuf, sync::Arc, time::Instant};
use std::{env, path::PathBuf, sync::Arc, time::Instant};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
@@ -103,9 +102,9 @@ impl EreOpenVM {
}
impl zkVM for EreOpenVM {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let mut stdin = StdIn::default();
serialize_inputs(&mut stdin, inputs);
stdin.write_bytes(input);
let start = Instant::now();
let public_values = self
@@ -124,7 +123,7 @@ impl zkVM for EreOpenVM {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
if proof_kind != ProofKind::Compressed {
@@ -132,7 +131,7 @@ impl zkVM for EreOpenVM {
}
let mut stdin = StdIn::default();
serialize_inputs(&mut stdin, inputs);
stdin.write_bytes(input);
let now = std::time::Instant::now();
let (proof, app_commit) = match self.resource {
@@ -194,21 +193,6 @@ impl zkVM for EreOpenVM {
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, _: R) -> Result<T, zkVMError> {
unimplemented!("no native serialization in this platform")
}
}
fn serialize_inputs(stdin: &mut StdIn, inputs: &Input) {
for input in inputs.iter() {
match input {
InputItem::Object(obj) => stdin.write(obj),
InputItem::SerializedObject(bytes) | InputItem::Bytes(bytes) => {
stdin.write_bytes(bytes)
}
}
}
}
/// Extract public values in bytes from field elements.
@@ -234,8 +218,9 @@ mod tests {
EreOpenVM,
compiler::{OpenVMProgram, RustRv32imaCustomized},
};
use ere_test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
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;
@@ -256,21 +241,17 @@ mod tests {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
let io = BasicProgramIo::valid().into_output_hashed_io();
run_zkvm_execute(&zkvm, &io);
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_inputs() {
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.execute(&inputs).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
@@ -279,21 +260,17 @@ mod tests {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
let io = BasicProgramIo::valid().into_output_hashed_io();
run_zkvm_prove(&zkvm, &io);
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_inputs() {
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.prove(&inputs, ProofKind::default()).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}

View File

@@ -7,7 +7,7 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
bincode.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
sha2.workspace = true
serde.workspace = true
tempfile.workspace = true

View File

@@ -55,7 +55,7 @@ impl Compiler for RustRv32ima {
mod tests {
use crate::{ErePico, compiler::RustRv32ima};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
#[test]
fn test_compile() {
@@ -70,6 +70,6 @@ mod tests {
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = ErePico::new(program, ProverResourceType::Cpu);
zkvm.execute(&Input::new()).unwrap();
zkvm.execute(&[]).unwrap();
}
}

View File

@@ -61,7 +61,7 @@ pub enum ProveError {
#[error("Pico proving failed: {0}")]
Client(anyhow::Error),
#[error("Serialising proof with `bincode` failed: {0}")]
Bincode(#[from] bincode::Error),
Bincode(#[from] bincode::error::EncodeError),
}
#[derive(Debug, Error)]
@@ -69,7 +69,7 @@ pub enum VerifyError {
#[error("Pico verifying failed: {0}")]
Client(anyhow::Error),
#[error("Deserialising proof with `bincode` failed: {0}")]
Bincode(#[from] bincode::Error),
Bincode(#[from] bincode::error::DecodeError),
#[error("Invalid base proof length {0}, expected 1")]
InvalidBaseProofLength(usize),
#[error("Invalid public values length {0}, expected at least 32")]

View File

@@ -6,16 +6,14 @@ use crate::{
error::{PicoError, ProveError, VerifyError},
};
use ere_zkvm_interface::{
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMError,
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use pico_p3_field::PrimeField32;
use pico_vm::{configs::stark_config::KoalaBearPoseidon2, emulator::stdin::EmulatorStdinBuilder};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::{
env,
io::Read,
time::{self, Instant},
};
@@ -49,11 +47,11 @@ impl ErePico {
}
impl zkVM for ErePico {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let client = self.client();
let mut stdin = client.new_stdin_builder();
serialize_inputs(&mut stdin, inputs);
stdin.write_slice(input);
let start = Instant::now();
let (total_num_cycles, public_values) = client.execute(stdin);
@@ -70,7 +68,7 @@ impl zkVM for ErePico {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<
(
@@ -87,7 +85,7 @@ impl zkVM for ErePico {
let client = self.client();
let mut stdin = client.new_stdin_builder();
serialize_inputs(&mut stdin, inputs);
stdin.write_slice(input);
let now = time::Instant::now();
let (public_values, proof) = client
@@ -95,10 +93,13 @@ impl zkVM for ErePico {
.map_err(|err| PicoError::Prove(ProveError::Client(err)))?;
let elapsed = now.elapsed();
let proof_bytes = bincode::serialize(&PicoProofWithPublicValues {
proof,
public_values: public_values.clone(),
})
let proof_bytes = bincode::serde::encode_to_vec(
&PicoProofWithPublicValues {
proof,
public_values: public_values.clone(),
},
bincode::config::legacy(),
)
.map_err(|err| PicoError::Prove(ProveError::Bincode(err)))?;
Ok((
@@ -117,8 +118,9 @@ impl zkVM for ErePico {
let client = self.client();
let proof: PicoProofWithPublicValues = bincode::deserialize(proof)
.map_err(|err| PicoError::Verify(VerifyError::Bincode(err)))?;
let (proof, _): (PicoProofWithPublicValues, _) =
bincode::serde::decode_from_slice(proof, bincode::config::legacy())
.map_err(|err| PicoError::Verify(VerifyError::Bincode(err)))?;
client
.verify(&proof.proof)
@@ -140,21 +142,6 @@ impl zkVM for ErePico {
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, reader: R) -> Result<T, zkVMError> {
bincode::deserialize_from(reader).map_err(zkVMError::other)
}
}
fn serialize_inputs(stdin: &mut EmulatorStdinBuilder<Vec<u8>, KoalaBearPoseidon2>, inputs: &Input) {
for input in inputs.iter() {
match input {
InputItem::Object(serialize) => stdin.write(serialize),
InputItem::SerializedObject(items) | InputItem::Bytes(items) => {
stdin.write_slice(items)
}
}
}
}
/// Extract public values sha256 digest from base proof of compressed proof.
@@ -186,8 +173,9 @@ mod tests {
ErePico,
compiler::{PicoProgram, RustRv32imaCustomized},
};
use ere_test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
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::{panic, sync::OnceLock};
@@ -209,21 +197,19 @@ mod tests {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu);
let io = BasicProgramIo::valid();
run_zkvm_execute(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_inputs() {
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu);
for inputs_gen in [
BasicProgramIo::empty,
BasicProgramIo::invalid_type,
BasicProgramIo::invalid_data,
] {
panic::catch_unwind(|| zkvm.execute(&inputs_gen())).unwrap_err();
// When guest panics Pico execute will also panics.
// Issue for tracking: https://github.com/eth-act/ere/issues/172.
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
panic::catch_unwind(|| zkvm.execute(&input)).unwrap_err();
}
}
@@ -232,21 +218,19 @@ mod tests {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu);
let io = BasicProgramIo::valid();
run_zkvm_prove(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_inputs() {
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = ErePico::new(program, ProverResourceType::Cpu);
for inputs_gen in [
BasicProgramIo::empty,
BasicProgramIo::invalid_type,
BasicProgramIo::invalid_data,
] {
panic::catch_unwind(|| zkvm.prove(&inputs_gen(), ProofKind::default())).unwrap_err();
// When guest panics Pico prove will also panics.
// Issue for tracking: https://github.com/eth-act/ere/issues/172.
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
panic::catch_unwind(|| zkvm.prove(&input, ProofKind::default())).unwrap_err();
}
}
}

View File

@@ -8,8 +8,7 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
borsh.workspace = true
bytemuck.workspace = true
serde = { workspace = true, features = ["derive", "rc"] }
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
tracing.workspace = true
@@ -23,6 +22,7 @@ ere-compile-utils.workspace = true
ere-zkvm-interface.workspace = true
[dev-dependencies]
ere-io-serde.workspace = true
ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]

View File

@@ -66,7 +66,7 @@ impl Compiler for RustRv32ima {
mod tests {
use crate::{EreRisc0, compiler::RustRv32ima};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
#[test]
fn test_compile() {
@@ -81,6 +81,6 @@ mod tests {
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&Input::new()).unwrap();
zkvm.execute(&[]).unwrap();
}
}

View File

@@ -1,22 +1,20 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use crate::{compiler::Risc0Program, output::deserialize_from};
use crate::compiler::Risc0Program;
use ere_zkvm_interface::{
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMError,
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use risc0_zkvm::{
DEFAULT_MAX_PO2, DefaultProver, ExecutorEnv, ExecutorEnvBuilder, ExternalProver, InnerReceipt,
ProverOpts, Receipt, default_executor, default_prover,
DEFAULT_MAX_PO2, DefaultProver, ExecutorEnv, ExternalProver, InnerReceipt, ProverOpts, Receipt,
default_executor, default_prover,
};
use serde::de::DeserializeOwned;
use std::{env, io::Read, ops::RangeInclusive, rc::Rc, time::Instant};
use std::{env, ops::RangeInclusive, rc::Rc, time::Instant};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub mod compiler;
pub mod error;
mod output;
/// Default logarithmic segment size from [`DEFAULT_SEGMENT_LIMIT_PO2`].
///
@@ -84,11 +82,12 @@ impl EreRisc0 {
}
impl zkVM for EreRisc0 {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let executor = default_executor();
let mut env = ExecutorEnv::builder();
serialize_inputs(&mut env, inputs).map_err(zkVMError::other)?;
let env = env.build().map_err(zkVMError::other)?;
let env = ExecutorEnv::builder()
.write_slice(input)
.build()
.map_err(zkVMError::other)?;
let start = Instant::now();
let session_info = executor
@@ -109,7 +108,7 @@ impl zkVM for EreRisc0 {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
let prover = match self.resource {
@@ -135,9 +134,8 @@ impl zkVM for EreRisc0 {
}
};
let mut env = ExecutorEnv::builder();
serialize_inputs(&mut env, inputs).map_err(zkVMError::other)?;
let env = env
let env = ExecutorEnv::builder()
.write_slice(input)
.segment_limit_po2(self.segment_po2 as _)
.keccak_max_po2(self.keccak_po2 as _)
.map_err(zkVMError::other)?
@@ -199,30 +197,6 @@ impl zkVM for EreRisc0 {
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, reader: R) -> Result<T, zkVMError> {
deserialize_from(reader)
}
}
fn serialize_inputs(env: &mut ExecutorEnvBuilder, inputs: &Input) -> Result<(), anyhow::Error> {
for input in inputs.iter() {
match input {
// Corresponding to `env.read::<T>()`.
InputItem::Object(obj) => env.write(obj)?,
// Corresponding to `env.read::<T>()`.
//
// Note that we call `write_slice` to append the bytes to the inputs
// directly, to avoid double serailization.
InputItem::SerializedObject(bytes) => env.write_slice(bytes),
// Corresponding to `env.read_frame()`.
//
// Note that `write_frame` is different from `write_slice`, it
// prepends the `bytes.len().to_le_bytes()`.
InputItem::Bytes(bytes) => env.write_frame(bytes),
};
}
Ok(())
}
#[cfg(test)]
@@ -231,10 +205,11 @@ mod tests {
EreRisc0,
compiler::{Risc0Program, RustRv32imaCustomized},
};
use ere_test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
use ere_test_utils::{
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgramInput,
};
use ere_zkvm_interface::{Compiler, Input, ProofKind, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM};
use std::sync::OnceLock;
static BASIC_PROGRAM: OnceLock<Risc0Program> = OnceLock::new();
@@ -254,21 +229,17 @@ mod tests {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
let io = BasicProgramIo::valid();
run_zkvm_execute(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_inputs() {
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.execute(&inputs).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
@@ -277,21 +248,17 @@ mod tests {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
let io = BasicProgramIo::valid();
run_zkvm_prove(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_inputs() {
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.prove(&inputs, ProofKind::default()).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
@@ -301,11 +268,10 @@ mod tests {
.compile(&testing_guest_directory("risc0", "allocs_alignment"))
.unwrap();
for i in 1..=16_usize {
for i in 1..=16_u32 {
let zkvm = EreRisc0::new(program.clone(), ProverResourceType::Cpu).unwrap();
let mut input = Input::new();
input.write(i);
let input = i.to_le_bytes();
if i.is_power_of_two() {
zkvm.execute(&input)

View File

@@ -1,23 +0,0 @@
use ere_zkvm_interface::zkVMError;
use risc0_zkvm::serde::{Deserializer, Error, WordRead};
use serde::de::{DeserializeOwned, Error as _};
use std::io::Read;
pub fn deserialize_from<R: Read, T: DeserializeOwned>(reader: R) -> Result<T, zkVMError> {
struct WordReadAdapter<R>(R);
impl<R: Read> WordRead for WordReadAdapter<R> {
fn read_words(&mut self, words: &mut [u32]) -> Result<(), Error> {
let bytes = bytemuck::cast_slice_mut(words);
self.0.read_exact(bytes).map_err(Error::custom)
}
fn read_padded_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> {
let mut padded_bytes = vec![0u8; bytes.len().next_multiple_of(4) - bytes.len()];
self.0.read_exact(bytes).map_err(Error::custom)?;
self.0.read_exact(&mut padded_bytes).map_err(Error::custom)
}
}
T::deserialize(&mut Deserializer::new(WordReadAdapter(reader))).map_err(zkVMError::other)
}

View File

@@ -6,8 +6,7 @@ rust-version.workspace = true
license.workspace = true
[dependencies]
bincode.workspace = true
serde.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
tempfile.workspace = true
thiserror.workspace = true
tracing.workspace = true

View File

@@ -55,7 +55,7 @@ impl Compiler for RustRv32ima {
mod tests {
use crate::{EreSP1, compiler::RustRv32ima};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
use ere_zkvm_interface::{Compiler, ProverResourceType, zkVM};
#[test]
fn test_compile() {
@@ -70,6 +70,6 @@ mod tests {
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
zkvm.execute(&Input::new()).unwrap();
zkvm.execute(&[]).unwrap();
}
}

View File

@@ -73,13 +73,13 @@ pub enum ProveError {
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Serialising proof with `bincode` failed: {0}")]
Bincode(#[from] bincode::Error),
Bincode(#[from] bincode::error::EncodeError),
}
#[derive(Debug, Error)]
pub enum VerifyError {
#[error("Deserialising proof failed: {0}")]
Bincode(#[from] bincode::Error),
Bincode(#[from] bincode::error::DecodeError),
#[error("Invalid proof kind, expected: {}, got: {}", 0.to_string(), 1.to_string() )]
InvalidProofKind(ProofKind, SP1ProofMode),

View File

@@ -5,15 +5,14 @@ use crate::{
error::{ExecuteError, ProveError, SP1Error, VerifyError},
};
use ere_zkvm_interface::{
Input, InputItem, NetworkProverConfig, ProgramExecutionReport, ProgramProvingReport, Proof,
ProofKind, ProverResourceType, PublicValues, zkVM, zkVMError,
NetworkProverConfig, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMError,
};
use serde::de::DeserializeOwned;
use sp1_sdk::{
CpuProver, CudaProver, NetworkProver, Prover, ProverClient, SP1ProofMode,
SP1ProofWithPublicValues, SP1ProvingKey, SP1Stdin, SP1VerifyingKey,
};
use std::{io::Read, time::Instant};
use std::time::Instant;
use tracing::info;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
@@ -145,9 +144,9 @@ impl EreSP1 {
}
impl zkVM for EreSP1 {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let mut stdin = SP1Stdin::new();
serialize_inputs(&mut stdin, inputs);
stdin.write_slice(input);
let client = Self::create_client(&self.resource);
let start = Instant::now();
@@ -165,13 +164,13 @@ impl zkVM for EreSP1 {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
info!("Generating proof…");
let mut stdin = SP1Stdin::new();
serialize_inputs(&mut stdin, inputs);
stdin.write_slice(input);
let mode = match proof_kind {
ProofKind::Compressed => SP1ProofMode::Compressed,
@@ -186,7 +185,8 @@ impl zkVM for EreSP1 {
let public_values = proof.public_values.to_vec();
let proof = Proof::new(
proof_kind,
bincode::serialize(&proof).map_err(|err| SP1Error::Prove(ProveError::Bincode(err)))?,
bincode::serde::encode_to_vec(&proof, bincode::config::legacy())
.map_err(|err| SP1Error::Prove(ProveError::Bincode(err)))?,
);
Ok((
@@ -201,8 +201,9 @@ impl zkVM for EreSP1 {
let proof_kind = proof.kind();
let proof: SP1ProofWithPublicValues = bincode::deserialize(proof.as_bytes())
.map_err(|err| SP1Error::Verify(VerifyError::Bincode(err)))?;
let (proof, _): (SP1ProofWithPublicValues, _) =
bincode::serde::decode_from_slice(proof.as_bytes(), bincode::config::legacy())
.map_err(|err| SP1Error::Verify(VerifyError::Bincode(err)))?;
let inner_proof_kind = SP1ProofMode::from(&proof.proof);
if !matches!(
@@ -231,28 +232,14 @@ impl zkVM for EreSP1 {
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, reader: R) -> Result<T, zkVMError> {
bincode::deserialize_from(reader).map_err(zkVMError::other)
}
}
fn serialize_inputs(stdin: &mut SP1Stdin, inputs: &Input) {
for input in inputs.iter() {
match input {
InputItem::Object(obj) => stdin.write(obj),
InputItem::SerializedObject(bytes) | InputItem::Bytes(bytes) => {
stdin.write_slice(bytes)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{EreSP1, compiler::RustRv32imaCustomized};
use ere_test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
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::{panic, sync::OnceLock};
@@ -274,21 +261,17 @@ mod tests {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
let io = BasicProgramIo::valid();
run_zkvm_execute(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_inputs() {
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.execute(&inputs).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
@@ -297,26 +280,19 @@ mod tests {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
let io = BasicProgramIo::valid();
run_zkvm_prove(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_inputs() {
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
// On invalid inputs SP1 prove will panics, the issue for tracking:
// https://github.com/eth-act/ere/issues/16.
//
// Note that we iterate on methods because `InputItem::Object` doesn't
// implement `RefUnwindSafe`.
for inputs_gen in [
BasicProgramIo::empty,
BasicProgramIo::invalid_type,
BasicProgramIo::invalid_data,
] {
panic::catch_unwind(|| zkvm.prove(&inputs_gen(), ProofKind::default())).unwrap_err();
// When guest panics SP1 prove will also panics.
// Issue for tracking: https://github.com/eth-act/ere/issues/172.
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
panic::catch_unwind(|| zkvm.prove(&input, ProofKind::default())).unwrap_err();
}
}
@@ -337,7 +313,7 @@ mod tests {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Network(network_config));
let io = BasicProgramIo::valid();
run_zkvm_prove(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
}

View File

@@ -6,8 +6,7 @@ rust-version.workspace = true
license.workspace = true
[dependencies]
bincode.workspace = true
serde.workspace = true
bincode = { workspace = true, features = ["std", "serde"] }
thiserror.workspace = true
tracing.workspace = true

View File

@@ -67,7 +67,7 @@ pub enum ExecuteError {
#[derive(Debug, Error)]
pub enum ProveError {
#[error("Serialising proof with `bincode` failed: {0}")]
Bincode(#[from] bincode::Error),
Bincode(#[from] bincode::error::EncodeError),
#[error("Ziren proving failed: {0}")]
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
@@ -76,7 +76,7 @@ pub enum ProveError {
#[derive(Debug, Error)]
pub enum VerifyError {
#[error("Deserialising proof with `bincode` failed: {0}")]
Bincode(#[from] bincode::Error),
Bincode(#[from] bincode::error::DecodeError),
#[error("Invalid proof kind, expected: {}, got: {}", 0.to_string(), 1.to_string() )]
InvalidProofKind(ProofKind, ZKMProofKind),

View File

@@ -5,11 +5,10 @@ use crate::{
error::{ExecuteError, ProveError, VerifyError, ZirenError},
};
use ere_zkvm_interface::{
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMError,
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use serde::de::DeserializeOwned;
use std::{io::Read, time::Instant};
use std::time::Instant;
use tracing::info;
use zkm_sdk::{
CpuProver, Prover, ZKMProofKind, ZKMProofWithPublicValues, ZKMProvingKey, ZKMStdin,
@@ -41,9 +40,9 @@ impl EreZiren {
}
impl zkVM for EreZiren {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let mut stdin = ZKMStdin::new();
serialize_inputs(&mut stdin, inputs);
stdin.write_slice(input);
let start = Instant::now();
let (public_inputs, exec_report) = CpuProver::new()
@@ -63,13 +62,13 @@ impl zkVM for EreZiren {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
info!("Generating proof…");
let mut stdin = ZKMStdin::new();
serialize_inputs(&mut stdin, inputs);
stdin.write_slice(input);
let inner_proof_kind = match proof_kind {
ProofKind::Compressed => ZKMProofKind::Compressed,
@@ -85,7 +84,7 @@ impl zkVM for EreZiren {
let public_values = proof.public_values.to_vec();
let proof = Proof::new(
proof_kind,
bincode::serialize(&proof)
bincode::serde::encode_to_vec(&proof, bincode::config::legacy())
.map_err(|err| ZirenError::Prove(ProveError::Bincode(err)))?,
);
@@ -101,8 +100,9 @@ impl zkVM for EreZiren {
let proof_kind = proof.kind();
let proof: ZKMProofWithPublicValues = bincode::deserialize(proof.as_bytes())
.map_err(|err| ZirenError::Verify(VerifyError::Bincode(err)))?;
let (proof, _): (ZKMProofWithPublicValues, _) =
bincode::serde::decode_from_slice(proof.as_bytes(), bincode::config::legacy())
.map_err(|err| ZirenError::Verify(VerifyError::Bincode(err)))?;
let inner_proof_kind = ZKMProofKind::from(&proof.proof);
if !matches!(
@@ -130,28 +130,14 @@ impl zkVM for EreZiren {
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, reader: R) -> Result<T, zkVMError> {
bincode::deserialize_from(reader).map_err(zkVMError::other)
}
}
fn serialize_inputs(stdin: &mut ZKMStdin, inputs: &Input) {
for input in inputs.iter() {
match input {
InputItem::Object(obj) => stdin.write(obj),
InputItem::SerializedObject(bytes) | InputItem::Bytes(bytes) => {
stdin.write_slice(bytes)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{EreZiren, compiler::RustMips32r2Customized};
use ere_test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
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::{panic, sync::OnceLock};
@@ -173,30 +159,17 @@ mod tests {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu);
let io = BasicProgramIo::valid();
run_zkvm_execute(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_inputs() {
type F = fn() -> ere_zkvm_interface::Input;
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu);
// Note that for some invalid cases the execution panics, but some not.
for (inputs_gen, should_panic) in [
// For empty input (insufficient input), the syscall reading input causes host to panics.
(BasicProgramIo::empty as F, true),
// For invalid type/data, the guest panics but handled properly by the host.
(BasicProgramIo::invalid_type as F, false),
(BasicProgramIo::invalid_data as F, false),
] {
if should_panic {
panic::catch_unwind(|| zkvm.execute(&inputs_gen())).unwrap_err();
} else {
zkvm.execute(&inputs_gen()).unwrap_err();
}
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
@@ -205,21 +178,19 @@ mod tests {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu);
let io = BasicProgramIo::valid();
run_zkvm_prove(&zkvm, &io);
let test_case = BasicProgramInput::valid();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_inputs() {
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreZiren::new(program, ProverResourceType::Cpu);
for inputs_gen in [
BasicProgramIo::empty,
BasicProgramIo::invalid_type,
BasicProgramIo::invalid_data,
] {
panic::catch_unwind(|| zkvm.prove(&inputs_gen(), ProofKind::default())).unwrap_err();
// When guest panics Ziren prove will also panics.
// Issue for tracking: https://github.com/eth-act/ere/issues/172.
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
panic::catch_unwind(|| zkvm.prove(&input, ProofKind::default())).unwrap_err();
}
}
}

View File

@@ -6,10 +6,9 @@ rust-version.workspace = true
license.workspace = true
[dependencies]
bincode.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
blake3.workspace = true
bytemuck.workspace = true
serde = { workspace = true, features = ["derive"] }
strum = { workspace = true, features = ["derive"] }
tempfile.workspace = true
thiserror.workspace = true

View File

@@ -49,8 +49,10 @@ pub enum ZiskError {
CompileUtilError(#[from] ere_compile_utils::CompileError),
// Serialization
#[error("Bincode serialization/deserialization failed: {0}")]
Bincode(#[from] bincode::Error),
#[error("Bincode encode failed: {0}")]
BincodeEncode(#[from] bincode::error::EncodeError),
#[error("Bincode decode failed: {0}")]
BincodeDecode(#[from] bincode::error::DecodeError),
// Execution
#[error("Failed to execute `ziskemu`: {0}")]

View File

@@ -6,12 +6,10 @@ use crate::{
error::ZiskError,
};
use ere_zkvm_interface::{
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResourceType, PublicValues, zkVM, zkVMError,
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
use serde::de::DeserializeOwned;
use std::{
io::Read,
sync::{Mutex, MutexGuard},
time::Instant,
};
@@ -65,11 +63,9 @@ impl EreZisk {
}
impl zkVM for EreZisk {
fn execute(&self, inputs: &Input) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let input_bytes = serialize_inputs(inputs)?;
fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> {
let start = Instant::now();
let (public_values, total_num_cycles) = self.sdk.execute(&input_bytes)?;
let (public_values, total_num_cycles) = self.sdk.execute(input)?;
let execution_duration = start.elapsed();
Ok((
@@ -84,7 +80,7 @@ impl zkVM for EreZisk {
fn prove(
&self,
inputs: &Input,
input: &[u8],
proof_kind: ProofKind,
) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> {
if proof_kind != ProofKind::Compressed {
@@ -94,10 +90,8 @@ impl zkVM for EreZisk {
let mut server = self.server()?;
let server = server.as_mut().expect("server initialized");
let input_bytes = serialize_inputs(inputs)?;
let start = Instant::now();
let (public_values, proof) = server.prove(&input_bytes)?;
let (public_values, proof) = server.prove(input)?;
let proving_time = start.elapsed();
Ok((
@@ -122,33 +116,14 @@ impl zkVM for EreZisk {
fn sdk_version(&self) -> &'static str {
SDK_VERSION
}
fn deserialize_from<R: Read, T: DeserializeOwned>(&self, _: R) -> Result<T, zkVMError> {
unimplemented!("no native serialization in this platform")
}
}
/// Serialize `Input` into sequence of bytes.
///
/// Because ZisK doesn't provide stdin API so we need to handle multiple inputs,
/// the current approach naively serializes each `InputItem` individually, then
/// concat them into single `Vec<u8>`.
fn serialize_inputs(inputs: &Input) -> Result<Vec<u8>, ZiskError> {
inputs.iter().try_fold(Vec::new(), |mut acc, item| {
match item {
InputItem::Object(obj) => bincode::serialize_into(&mut acc, &**obj)?,
InputItem::SerializedObject(bytes) => acc.extend(bytes),
InputItem::Bytes(bytes) => bincode::serialize_into(&mut acc, bytes)?,
};
Ok(acc)
})
}
#[cfg(test)]
mod tests {
use crate::{EreZisk, compiler::RustRv64imaCustomized};
use ere_test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
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};
@@ -174,21 +149,17 @@ mod tests {
let program = basic_program();
let zkvm = EreZisk::new(program, ProverResourceType::Cpu).unwrap();
let io = BasicProgramIo::valid().into_output_hashed_io();
run_zkvm_execute(&zkvm, &io);
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_execute(&zkvm, &test_case);
}
#[test]
fn test_execute_invalid_inputs() {
fn test_execute_invalid_input() {
let program = basic_program();
let zkvm = EreZisk::new(program, ProverResourceType::Cpu).unwrap();
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.execute(&inputs).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.execute(&input).unwrap_err();
}
}
@@ -199,23 +170,19 @@ mod tests {
let _guard = PROVE_LOCK.lock().unwrap();
let io = BasicProgramIo::valid().into_output_hashed_io();
run_zkvm_prove(&zkvm, &io);
let test_case = BasicProgramInput::valid().into_output_sha256();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_inputs() {
fn test_prove_invalid_input() {
let program = basic_program();
let zkvm = EreZisk::new(program, ProverResourceType::Cpu).unwrap();
let _guard = PROVE_LOCK.lock().unwrap();
for inputs in [
BasicProgramIo::empty(),
BasicProgramIo::invalid_type(),
BasicProgramIo::invalid_data(),
] {
zkvm.prove(&inputs, ProofKind::default()).unwrap_err();
for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] {
zkvm.prove(&input, ProofKind::default()).unwrap_err();
}
}
}

View File

@@ -14,8 +14,10 @@ FROM base${CUDA:+_cuda}
ARG CUDA
# Default to build for RTX 50 series
ARG CUDA_ARCH=120
ENV CUDA_ARCH=$CUDA_ARCH
ARG CUDA_ARCH=sm_120
# Env variable read by OpenVM crate `cuda-builder`, need to persist it for building `ere-openvm`.
ENV CUDA_ARCH=${CUDA_ARCH#sm_}
# Copy the OpenVM SDK installer script from the workspace context
COPY --chmod=755 scripts/sdk_installers/install_openvm_sdk.sh /tmp/install_openvm_sdk.sh

View File

@@ -43,7 +43,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
# Default to build for RTX 50 series
ARG CUDA_ARCH=sm_120
ENV CUDA_ARCH=$CUDA_ARCH
# Copy the ZisK SDK installer script from the workspace context
COPY --chmod=755 scripts/sdk_installers/install_zisk_sdk.sh /tmp/install_zisk_sdk.sh

View File

@@ -1,5 +0,0 @@
[target.riscv32i-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Tlink.x",
]
runner="nexus-run"

View File

@@ -5,12 +5,6 @@ edition = "2021"
[dependencies]
nexus-rt = { git = "https://github.com/nexus-xyz/nexus-zkvm.git", tag = "v0.3.4" }
postcard = { version = "1.0", default-features = false, features = ["alloc"] }
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
ere-test-utils = { path = "../../../crates/test-utils" }
# Generated by cargo-nexus, do not remove!
#
[features]
cycles = [] # Enable cycle counting for run command
[workspace]

View File

@@ -3,58 +3,25 @@
extern crate alloc;
use alloc::vec::Vec;
use ere_test_utils::{
guest::Platform,
program::{basic::BasicProgram, Program},
};
use nexus_rt::{read_private_input, write_public_output};
use serde::{Deserialize, Serialize};
struct NexusPlatform;
impl Platform for NexusPlatform {
fn read_input() -> Vec<u8> {
read_private_input().unwrap()
}
fn write_output(output: &[u8]) {
write_public_output(&output).unwrap()
}
}
#[nexus_rt::main]
fn main() {
let input_bytes: Vec<u8> = read_private_input().expect("failed to read input");
// Deserialize the first input (Vec<u8>)
let (bytes, remaining): (Vec<u8>, &[u8]) =
postcard::take_from_bytes(&input_bytes).expect("failed to deserialize bytes");
// Deserialize the second input (BasicStruct)
let basic_struct: BasicStruct =
postcard::from_bytes(remaining).expect("failed to deserialize struct");
// Check `bytes` length is as expected.
assert_eq!(bytes.len(), BYTES_LENGTH);
// Do some computation on `bytes` and `basic_struct`.
let rev_bytes: Vec<u8> = bytes.iter().rev().copied().collect();
let basic_struct_output = basic_struct.output();
// Write `rev_bytes` and `basic_struct_output`
let mut output_bytes = Vec::new();
output_bytes.extend_from_slice(&rev_bytes);
output_bytes.extend_from_slice(&postcard::to_allocvec(&basic_struct_output).unwrap());
write_public_output(&output_bytes).expect("failed to write output");
}
// Copied from test_utils
// test_utils is not used due to no_std conflicts with sha2 dependency.
const BYTES_LENGTH: usize = 32;
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct BasicStruct {
pub a: u8,
pub b: u16,
pub c: u32,
pub d: u64,
pub e: Vec<u8>,
}
impl BasicStruct {
/// Performs some computation (Wrapping add all fields by 1).
pub fn output(&self) -> Self {
Self {
a: self.a.wrapping_add(1),
b: self.b.wrapping_add(1),
c: self.c.wrapping_add(1),
d: self.d.wrapping_add(1),
e: self.e.iter().map(|byte| byte.wrapping_add(1)).collect(),
}
}
BasicProgram::run::<NexusPlatform>();
}

View File

@@ -1,15 +0,0 @@
[package]
name = "ere-nexus-guest-fib"
version = "0.1.0"
edition = "2021"
[dependencies]
nexus-rt = { git = "https://github.com/nexus-xyz/nexus-zkvm.git", tag = "v0.3.4" }
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
postcard = { version = "1.0", default-features = false, features = ["alloc"] }
# Generated by cargo-nexus, do not remove!
#
[features]
cycles = [] # Enable cycle counting for run command
[workspace]

View File

@@ -1,50 +0,0 @@
#![cfg_attr(target_arch = "riscv32", no_std, no_main)]
extern crate alloc;
use alloc::vec::Vec;
use nexus_rt::{read_private_input, write_public_output};
use postcard;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct FibInput {
n: u32,
}
#[nexus_rt::main]
fn main() {
let input_bytes: Vec<u8> = read_private_input().expect("failed to read input");
// Deserialize FibInput from the postcard bytes
let fib_input: FibInput =
postcard::from_bytes(&input_bytes).expect("failed to deserialize input");
let n = fib_input.n;
let result = fibonacci(n);
// Serialize result to bytes before writing
let output_bytes = postcard::to_allocvec(&result).expect("failed to serialize output");
write_public_output(&output_bytes).expect("failed to write output");
}
fn fibonacci(n: u32) -> u32 {
if n == 0 {
return 0;
}
if n == 1 {
return 1;
}
let mut a = 0u32;
let mut b = 1u32;
for _ in 2..=n {
let temp = a.wrapping_add(b);
a = b;
b = temp;
}
b
}

View File

@@ -6,6 +6,5 @@ edition = "2021"
[workspace]
[dependencies]
bincode = "1.3.3"
openvm = { git = "https://github.com/openvm-org/openvm.git", features = ["std"], tag = "v1.4.0" }
ere-test-utils = { path = "../../../crates/test-utils" }

View File

@@ -1,22 +1,22 @@
use openvm::io::{read, read_vec, reveal_bytes32};
use ere_test_utils::guest::{BasicProgramCore, BasicStruct};
use ere_test_utils::{
guest::{Digest, Platform, Sha256},
program::{basic::BasicProgram, Program},
};
use openvm::io::{read_vec, reveal_bytes32};
struct OpenVMPlatform;
impl Platform for OpenVMPlatform {
fn read_input() -> Vec<u8> {
read_vec()
}
fn write_output(output: &[u8]) {
let digest = Sha256::digest(output);
reveal_bytes32(digest.into());
}
}
fn main() {
// Read `bytes`.
let bytes = read_vec();
// Read `basic_struct`.
let basic_struct = read::<BasicStruct>();
// Check `bytes` length is as expected.
assert_eq!(bytes.len(), BasicProgramCore::BYTES_LENGTH);
// Do some computation on `bytes` and `basic_struct`.
let outputs = BasicProgramCore::outputs((bytes, basic_struct));
// Hash `outputs` into digest.
let digest = BasicProgramCore::sha256_outputs(outputs);
// Write `digest`
reveal_bytes32(digest);
BasicProgram::run::<OpenVMPlatform>();
}

View File

@@ -1,24 +1,25 @@
#![no_main]
use pico_sdk::io::{commit, commit_bytes, read_as, read_vec};
use ere_test_utils::guest::{BasicProgramCore, BasicStruct};
use ere_test_utils::{
guest::Platform,
program::{basic::BasicProgram, Program},
};
use pico_sdk::io::{commit_bytes, read_vec};
pico_sdk::entrypoint!(main);
pub fn main() {
// Read `bytes`.
let bytes = read_vec();
struct PicoPlatform;
// Read `basic_struct`.
let basic_struct = read_as::<BasicStruct>();
impl Platform for PicoPlatform {
fn read_input() -> Vec<u8> {
read_vec()
}
// Check `bytes` length is as expected.
assert_eq!(bytes.len(), BasicProgramCore::BYTES_LENGTH);
// Do some computation on `bytes` and `basic_struct`.
let (rev_bytes, basic_struct_output) = BasicProgramCore::outputs((bytes, basic_struct));
// Write `rev_bytes` and `basic_struct_output`
commit_bytes(&rev_bytes);
commit(&basic_struct_output);
fn write_output(output: &[u8]) {
commit_bytes(output);
}
}
pub fn main() {
BasicProgram::run::<PicoPlatform>();
}

View File

@@ -1,7 +1,11 @@
use risc0_zkvm::guest::env;
fn main() {
let alignment = env::read::<usize>();
let alignment = {
let mut buf = [0; 4];
env::read_slice(&mut buf);
u32::from_le_bytes(buf) as usize
};
let layout = std::alloc::Layout::from_size_align(1, alignment).unwrap();
let ptr = unsafe { std::alloc::alloc(layout) };

View File

@@ -1,20 +1,24 @@
use ere_test_utils::{
guest::Platform,
program::{basic::BasicProgram, Program},
};
use risc0_zkvm::guest::env;
use ere_test_utils::guest::{BasicProgramCore, BasicStruct};
use std::io::Read;
struct Risc0Platform;
impl Platform for Risc0Platform {
fn read_input() -> Vec<u8> {
let mut input = Vec::new();
env::stdin().read_to_end(&mut input).unwrap();
input
}
fn write_output(output: &[u8]) {
env::commit_slice(output);
}
}
fn main() {
// Read `bytes`.
let bytes = env::read_frame();
// Read `basic_struct`.
let basic_struct = env::read::<BasicStruct>();
// Check `bytes` length is as expected.
assert_eq!(bytes.len(), BasicProgramCore::BYTES_LENGTH);
// Do some computation on `bytes` and `basic_struct`.
let (rev_bytes, basic_struct_output) = BasicProgramCore::outputs((bytes, basic_struct));
// Write `rev_bytes` and `basic_struct_output`
env::commit_slice(&rev_bytes);
env::commit(&basic_struct_output);
BasicProgram::run::<Risc0Platform>();
}

View File

@@ -1,23 +1,24 @@
#![no_main]
use ere_test_utils::guest::{BasicProgramCore, BasicStruct};
use ere_test_utils::{
guest::Platform,
program::{basic::BasicProgram, Program},
};
sp1_zkvm::entrypoint!(main);
pub fn main() {
// Read `bytes`.
let bytes = sp1_zkvm::io::read_vec();
struct SP1Platform;
// Read `basic_struct`.
let basic_struct = sp1_zkvm::io::read::<BasicStruct>();
impl Platform for SP1Platform {
fn read_input() -> Vec<u8> {
sp1_zkvm::io::read_vec()
}
// Check `bytes` length is as expected.
assert_eq!(bytes.len(), BasicProgramCore::BYTES_LENGTH);
// Do some computation on `bytes` and `basic_struct`.
let (rev_bytes, basic_struct_output) = BasicProgramCore::outputs((bytes, basic_struct));
// Write `rev_bytes` and `basic_struct_output`
sp1_zkvm::io::commit_slice(&rev_bytes);
sp1_zkvm::io::commit(&basic_struct_output);
fn write_output(output: &[u8]) {
sp1_zkvm::io::commit_slice(output);
}
}
pub fn main() {
BasicProgram::run::<SP1Platform>();
}

View File

@@ -1,23 +1,24 @@
#![no_main]
use ere_test_utils::guest::{BasicProgramCore, BasicStruct};
use ere_test_utils::{
guest::Platform,
program::{basic::BasicProgram, Program},
};
zkm_zkvm::entrypoint!(main);
pub fn main() {
// Read `bytes`.
let bytes = zkm_zkvm::io::read_vec();
struct ZirenPlatform;
// Read `basic_struct`.
let basic_struct = zkm_zkvm::io::read::<BasicStruct>();
impl Platform for ZirenPlatform {
fn read_input() -> Vec<u8> {
zkm_zkvm::io::read_vec()
}
// Check `bytes` length is as expected.
assert_eq!(bytes.len(), BasicProgramCore::BYTES_LENGTH);
// Do some computation on `bytes` and `basic_struct`.
let (rev_bytes, basic_struct_output) = BasicProgramCore::outputs((bytes, basic_struct));
// Write `rev_bytes` and `basic_struct_output`
zkm_zkvm::io::commit_slice(&rev_bytes);
zkm_zkvm::io::commit(&basic_struct_output);
fn write_output(output: &[u8]) {
zkm_zkvm::io::commit_slice(output);
}
}
pub fn main() {
BasicProgram::run::<ZirenPlatform>();
}

View File

@@ -6,6 +6,5 @@ edition = "2021"
[workspace]
[dependencies]
bincode = "1.3.3"
ziskos = { git = "https://github.com/0xPolygonHermez/zisk.git", tag = "v0.12.0" }
ere-test-utils = { path = "../../../crates/test-utils" }

View File

@@ -1,33 +1,27 @@
#![no_main]
use ere_test_utils::guest::{BasicProgramCore, BasicStruct};
use ere_test_utils::{
guest::{Digest, Platform, Sha256},
program::{basic::BasicProgram, Program},
};
ziskos::entrypoint!(main);
fn main() {
let input = ziskos::read_input();
let mut input = input.as_slice();
struct ZiskPlatform;
// Read `bytes`.
let bytes: Vec<u8> = bincode::deserialize_from(&mut input).unwrap();
impl Platform for ZiskPlatform {
fn read_input() -> Vec<u8> {
ziskos::read_input()
}
// Read `basic_struct`.
let basic_struct: BasicStruct = bincode::deserialize_from(&mut input).unwrap();
// Check input is fully read.
assert!(input.is_empty());
// Check `bytes` length is as expected.
assert_eq!(bytes.len(), BasicProgramCore::BYTES_LENGTH);
// Do some computation on `bytes` and `basic_struct`.
let outputs = BasicProgramCore::outputs((bytes, basic_struct));
// Hash `outputs` into digest.
let digest = BasicProgramCore::sha256_outputs(outputs);
// Write `digest`
digest.chunks_exact(4).enumerate().for_each(|(idx, bytes)| {
ziskos::set_output(idx, u32::from_le_bytes(bytes.try_into().unwrap()))
});
fn write_output(output: &[u8]) {
let digest = Sha256::digest(output);
digest.chunks_exact(4).enumerate().for_each(|(idx, bytes)| {
ziskos::set_output(idx, u32::from_le_bytes(bytes.try_into().unwrap()))
});
}
}
fn main() {
BasicProgram::run::<ZiskPlatform>();
}