From 577f97165eb759cf4515af1ccebb1d1e2b8e1581 Mon Sep 17 00:00:00 2001 From: Han Date: Sat, 18 Oct 2025 11:04:35 +0800 Subject: [PATCH] `zkVM` takes opaque input (#173) --- .github/workflows/test-common.yml | 2 + Cargo.lock | 70 +++---- Cargo.toml | 11 +- README.md | 24 ++- crates/dockerized/compiler/Cargo.toml | 2 +- crates/dockerized/compiler/src/main.rs | 5 +- crates/dockerized/dockerized/Cargo.toml | 5 - crates/dockerized/dockerized/src/input.rs | 73 ------- crates/dockerized/dockerized/src/lib.rs | 94 ++++----- crates/dockerized/dockerized/src/output.rs | 28 --- crates/dockerized/server/Cargo.toml | 2 +- crates/dockerized/server/src/client.rs | 27 ++- crates/dockerized/server/src/input.rs | 29 --- crates/dockerized/server/src/lib.rs | 1 - crates/dockerized/server/src/main.rs | 4 +- crates/dockerized/server/src/server.rs | 21 +-- crates/io-serde/Cargo.toml | 15 ++ crates/io-serde/src/bincode.rs | 62 ++++++ crates/io-serde/src/lib.rs | 22 +++ crates/test-utils/Cargo.toml | 2 +- crates/test-utils/src/guest.rs | 62 +----- crates/test-utils/src/host.rs | 155 ++++----------- crates/test-utils/src/lib.rs | 1 + crates/test-utils/src/program.rs | 28 +++ crates/test-utils/src/program/basic.rs | 95 ++++++++++ crates/zkvm-interface/Cargo.toml | 3 +- crates/zkvm-interface/src/input.rs | 178 ------------------ crates/zkvm-interface/src/lib.rs | 26 +-- crates/zkvm/jolt/Cargo.toml | 1 - crates/zkvm/jolt/src/compiler/rust_rv32ima.rs | 4 +- .../src/compiler/rust_rv32ima_customized.rs | 4 +- crates/zkvm/jolt/src/jolt_methods.rs | 3 +- crates/zkvm/jolt/src/lib.rs | 22 +-- crates/zkvm/miden/Cargo.toml | 2 +- crates/zkvm/miden/src/error.rs | 10 +- crates/zkvm/miden/src/io.rs | 52 ----- crates/zkvm/miden/src/lib.rs | 127 ++++++++----- crates/zkvm/nexus/Cargo.toml | 4 +- crates/zkvm/nexus/src/error.rs | 4 +- crates/zkvm/nexus/src/lib.rs | 177 +++++------------ .../zkvm/openvm/src/compiler/rust_rv32ima.rs | 6 +- crates/zkvm/openvm/src/lib.rs | 63 ++----- crates/zkvm/pico/Cargo.toml | 2 +- crates/zkvm/pico/src/compiler/rust_rv32ima.rs | 4 +- crates/zkvm/pico/src/error.rs | 4 +- crates/zkvm/pico/src/lib.rs | 84 ++++----- crates/zkvm/risc0/Cargo.toml | 4 +- .../zkvm/risc0/src/compiler/rust_rv32ima.rs | 4 +- crates/zkvm/risc0/src/lib.rs | 94 +++------ crates/zkvm/risc0/src/output.rs | 23 --- crates/zkvm/sp1/Cargo.toml | 3 +- crates/zkvm/sp1/src/compiler/rust_rv32ima.rs | 4 +- crates/zkvm/sp1/src/error.rs | 4 +- crates/zkvm/sp1/src/lib.rs | 82 +++----- crates/zkvm/ziren/Cargo.toml | 3 +- crates/zkvm/ziren/src/error.rs | 4 +- crates/zkvm/ziren/src/lib.rs | 81 +++----- crates/zkvm/zisk/Cargo.toml | 3 +- crates/zkvm/zisk/src/error.rs | 6 +- crates/zkvm/zisk/src/lib.rs | 71 ++----- docker/openvm/Dockerfile.base | 6 +- docker/zisk/Dockerfile.base | 1 - tests/nexus/basic/.cargo/config.toml | 5 - tests/nexus/basic/Cargo.toml | 8 +- tests/nexus/basic/src/main.rs | 67 ++----- tests/nexus/fib/Cargo.toml | 15 -- tests/nexus/fib/src/main.rs | 50 ----- tests/openvm/basic/Cargo.toml | 1 - tests/openvm/basic/src/main.rs | 38 ++-- tests/pico/basic/src/main.rs | 33 ++-- tests/risc0/allocs_alignment/src/main.rs | 6 +- tests/risc0/basic/src/main.rs | 36 ++-- tests/sp1/basic/src/main.rs | 31 +-- tests/ziren/basic/src/main.rs | 31 +-- tests/zisk/basic/Cargo.toml | 1 - tests/zisk/basic/src/main.rs | 44 ++--- 76 files changed, 851 insertions(+), 1528 deletions(-) delete mode 100644 crates/dockerized/dockerized/src/input.rs delete mode 100644 crates/dockerized/dockerized/src/output.rs delete mode 100644 crates/dockerized/server/src/input.rs create mode 100644 crates/io-serde/Cargo.toml create mode 100644 crates/io-serde/src/bincode.rs create mode 100644 crates/io-serde/src/lib.rs create mode 100644 crates/test-utils/src/program.rs create mode 100644 crates/test-utils/src/program/basic.rs delete mode 100644 crates/zkvm-interface/src/input.rs delete mode 100644 crates/zkvm/miden/src/io.rs delete mode 100644 crates/zkvm/risc0/src/output.rs delete mode 100644 tests/nexus/basic/.cargo/config.toml delete mode 100644 tests/nexus/fib/Cargo.toml delete mode 100644 tests/nexus/fib/src/main.rs diff --git a/.github/workflows/test-common.yml b/.github/workflows/test-common.yml index 66fa769..4b42c71 100644 --- a/.github/workflows/test-common.yml +++ b/.github/workflows/test-common.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 6eb47e7..f3dc3b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 5967155..408de69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/README.md b/README.md index 3ccac65..f4e1c15 100644 --- a/README.md +++ b/README.md @@ -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> { let guest_directory = std::path::Path::new("workspace/guest"); @@ -94,15 +94,14 @@ fn main() -> Result<(), Box> { // 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> { let guest_directory = std::path::Path::new("workspace/guest"); @@ -141,15 +140,14 @@ fn main() -> Result<(), Box> { // 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>`) and contiguous (`Vec`) 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 diff --git a/crates/dockerized/compiler/Cargo.toml b/crates/dockerized/compiler/Cargo.toml index 0939b1d..7acec52 100644 --- a/crates/dockerized/compiler/Cargo.toml +++ b/crates/dockerized/compiler/Cargo.toml @@ -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"] } diff --git a/crates/dockerized/compiler/src/main.rs b/crates/dockerized/compiler/src/main.rs index da5fc86..ee8ddde 100644 --- a/crates/dockerized/compiler/src/main.rs +++ b/crates/dockerized/compiler/src/main.rs @@ -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(()) } diff --git a/crates/dockerized/dockerized/Cargo.toml b/crates/dockerized/dockerized/Cargo.toml index aa639f0..b07d6ff 100644 --- a/crates/dockerized/dockerized/Cargo.toml +++ b/crates/dockerized/dockerized/Cargo.toml @@ -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 diff --git a/crates/dockerized/dockerized/src/input.rs b/crates/dockerized/dockerized/src/input.rs deleted file mode 100644 index 99995da..0000000 --- a/crates/dockerized/dockerized/src/input.rs +++ /dev/null @@ -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, 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 { - 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::>() - .map(SerializedInput) - } -} diff --git a/crates/dockerized/dockerized/src/lib.rs b/crates/dockerized/dockerized/src/lib.rs index eff7320..16d180e 100644 --- a/crates/dockerized/dockerized/src/lib.rs +++ b/crates/dockerized/dockerized/src/lib.rs @@ -27,7 +27,7 @@ //! ```rust,no_run //! # fn main() -> Result<(), Box> { //! 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(&self, reader: R) -> Result { - self.zkvm.deserialize_from(reader) - } } fn block_on(future: impl Future) -> 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 = 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()); } } diff --git a/crates/dockerized/dockerized/src/output.rs b/crates/dockerized/dockerized/src/output.rs deleted file mode 100644 index 9ee05fa..0000000 --- a/crates/dockerized/dockerized/src/output.rs +++ /dev/null @@ -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( - &self, - reader: R, - ) -> Result { - 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"), - } - } -} diff --git a/crates/dockerized/server/Cargo.toml b/crates/dockerized/server/Cargo.toml index c585e5d..4f5a9ff 100644 --- a/crates/dockerized/server/Cargo.toml +++ b/crates/dockerized/server/Cargo.toml @@ -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 diff --git a/crates/dockerized/server/src/client.rs b/crates/dockerized/server/src/client.rs index 1d2106e..d94836f 100644 --- a/crates/dockerized/server/src/client.rs +++ b/crates/dockerized/server/src/client.rs @@ -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, ) -> 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, 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)) } diff --git a/crates/dockerized/server/src/input.rs b/crates/dockerized/server/src/input.rs deleted file mode 100644 index ea27def..0000000 --- a/crates/dockerized/server/src/input.rs +++ /dev/null @@ -1,29 +0,0 @@ -use ere_zkvm_interface::{Input, InputItem}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -pub struct SerializedInput(pub Vec); - -impl From for Input { - fn from(value: SerializedInput) -> Self { - Self::from(value.0.into_iter().map(Into::into).collect::>()) - } -} - -/// `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), - Bytes(Vec), -} - -impl From for InputItem { - fn from(value: SerializedInputItem) -> Self { - match value { - SerializedInputItem::SerializedObject(bytes) => Self::SerializedObject(bytes), - SerializedInputItem::Bytes(bytes) => Self::Bytes(bytes), - } - } -} diff --git a/crates/dockerized/server/src/lib.rs b/crates/dockerized/server/src/lib.rs index 1191fc9..d2cef53 100644 --- a/crates/dockerized/server/src/lib.rs +++ b/crates/dockerized/server/src/lib.rs @@ -1,5 +1,4 @@ pub mod client; -pub mod input; #[allow(dead_code)] pub(crate) mod api { diff --git a/crates/dockerized/server/src/main.rs b/crates/dockerized/server/src/main.rs index cb54536..c2e88a0 100644 --- a/crates/dockerized/server/src/main.rs +++ b/crates/dockerized/server/src/main.rs @@ -108,8 +108,8 @@ async fn shutdown_signal() { } fn construct_zkvm(program: Vec, resource: ProverResourceType) -> Result { - 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); diff --git a/crates/dockerized/server/src/server.rs b/crates/dockerized/server/src/server.rs index 091c584..6819d3c 100644 --- a/crates/dockerized/server/src/server.rs +++ b/crates/dockerized/server/src/server.rs @@ -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 ZkvmService for zkVMServer { ) -> twirp::Result> { let request = request.into_body(); - let input = bincode::deserialize::(&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 ZkvmService for zkVMServer { 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 ZkvmService for zkVMServer { ) -> twirp::Result> { let request = request.into_body(); - let input = bincode::deserialize::(&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 ZkvmService for zkVMServer { 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(), })) } diff --git a/crates/io-serde/Cargo.toml b/crates/io-serde/Cargo.toml new file mode 100644 index 0000000..c5db767 --- /dev/null +++ b/crates/io-serde/Cargo.toml @@ -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 diff --git a/crates/io-serde/src/bincode.rs b/crates/io-serde/src/bincode.rs new file mode 100644 index 0000000..a0847b2 --- /dev/null +++ b/crates/io-serde/src/bincode.rs @@ -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(pub O); + +impl Bincode> { + /// `Bincode` with legacy configuration, same as the default of `bincode@1`. + pub fn legacy() -> Self { + Self(bincode::config::legacy()) + } +} + +impl Bincode> { + /// `Bincode` with standard configuration. + pub fn standard() -> Self { + Self(bincode::config::standard()) + } +} + +impl IoSerde for Bincode { + type Error = BincodeError; + + fn serialize(&self, value: &T) -> Result, 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 { + let (value, _) = bincode::serde::borrow_decode_from_slice(bytes, self.0) + .map_err(BincodeError::Decode)?; + Ok(value) + } +} diff --git a/crates/io-serde/src/lib.rs b/crates/io-serde/src/lib.rs new file mode 100644 index 0000000..6cfd8a1 --- /dev/null +++ b/crates/io-serde/src/lib.rs @@ -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(&self, value: &T) -> Result, Self::Error>; + + fn deserialize<'a, T: Deserialize<'a>>(&self, bytes: &'a [u8]) -> Result; +} diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 06b4f5d..d9249fc 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -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 diff --git a/crates/test-utils/src/guest.rs b/crates/test-utils/src/guest.rs index 7763b07..92b78ca 100644 --- a/crates/test-utils/src/guest.rs +++ b/crates/test-utils/src/guest.rs @@ -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; - pub fn outputs(inputs: (Vec, BasicStruct)) -> (Vec, BasicStruct) { - let (bytes, basic_struct) = inputs; - (bytes.iter().rev().copied().collect(), basic_struct.output()) - } - - pub fn sha256_outputs(outputs: (Vec, BasicStruct)) -> [u8; 32] { - let (rev_bytes, basic_struct) = outputs; - Sha256::digest( - iter::empty() - .chain(rev_bytes) - .chain(bincode::serialize(&basic_struct).unwrap()) - .collect::>(), - ) - .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, -} - -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]); } diff --git a/crates/test-utils/src/host.rs b/crates/test-utils/src/host.rs index 17736e2..222b9ab 100644 --- a/crates/test-utils/src/host.rs +++ b/crates/test-utils/src/host.rs @@ -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` - random bytes -/// - [`BasicStruct`] - structure filled with random values -/// -/// Commit 2 outputs: -/// - `Vec` that should be reverse of the input random bytes. -/// - [`BasicStruct`] that should be computed by [`BasicStruct::output`]. -#[derive(Clone)] -pub struct BasicProgramIo { - bytes: Vec, - 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; + + fn assert_output(&self, public_values: &[u8]); } -impl Io for BasicProgramIo { - type Output = (Vec, 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 TestCase for T { + fn serialized_input(&self) -> Vec { + 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 { +/// Wrapper for [`TestCase`] that asserts output to be hashed. +pub struct OutputHashedTestCase { inner: T, - hasher: H, _marker: PhantomData, } -impl OutputHashedIo { - pub fn new(inner: T, hasher: H) -> Self { +impl OutputHashedTestCase { + pub fn new(inner: T) -> Self { Self { inner, - hasher, _marker: PhantomData, } } } -impl Io for OutputHashedIo +impl TestCase for OutputHashedTestCase 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 { + 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) } } diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 0442a0b..5c40d37 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -3,6 +3,7 @@ extern crate alloc; pub mod guest; +pub mod program; #[cfg(feature = "host")] pub mod host; diff --git a/crates/test-utils/src/program.rs b/crates/test-utils/src/program.rs new file mode 100644 index 0000000..e95ab87 --- /dev/null +++ b/crates/test-utils/src/program.rs @@ -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() { + 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; +} diff --git a/crates/test-utils/src/program/basic.rs b/crates/test-utils/src/program/basic.rs new file mode 100644 index 0000000..c480dcd --- /dev/null +++ b/crates/test-utils/src/program/basic.rs @@ -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, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct BasicProgramOutput { + pub e: Vec, + 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) + } + } +} diff --git a/crates/zkvm-interface/Cargo.toml b/crates/zkvm-interface/Cargo.toml index 100c1d0..f8cd481 100644 --- a/crates/zkvm-interface/Cargo.toml +++ b/crates/zkvm-interface/Cargo.toml @@ -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] diff --git a/crates/zkvm-interface/src/input.rs b/crates/zkvm-interface/src/input.rs deleted file mode 100644 index 121c910..0000000 --- a/crates/zkvm-interface/src/input.rs +++ /dev/null @@ -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), - /// 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), - /// Serialized bytes with opaque serializer (e.g. bincode) - Bytes(Vec), -} - -impl Debug for InputItem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - InputItem::Object(_) => f.write_str("Object()"), - 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, -} -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(&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) { - 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> for Input { - fn from(items: Vec) -> 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); - } -} diff --git a/crates/zkvm-interface/src/lib.rs b/crates/zkvm-interface/src/lib.rs index c7dd2b3..5de77d8 100644 --- a/crates/zkvm-interface/src/lib.rs +++ b/crates/zkvm-interface/src/lib.rs @@ -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(&self, reader: R) -> Result; } diff --git a/crates/zkvm/jolt/Cargo.toml b/crates/zkvm/jolt/Cargo.toml index ee8c56f..a148299 100644 --- a/crates/zkvm/jolt/Cargo.toml +++ b/crates/zkvm/jolt/Cargo.toml @@ -6,7 +6,6 @@ rust-version.workspace = true license.workspace = true [dependencies] -serde.workspace = true tempfile.workspace = true thiserror.workspace = true diff --git a/crates/zkvm/jolt/src/compiler/rust_rv32ima.rs b/crates/zkvm/jolt/src/compiler/rust_rv32ima.rs index 76840bc..eeacc2f 100644 --- a/crates/zkvm/jolt/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/jolt/src/compiler/rust_rv32ima.rs @@ -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(); } } diff --git a/crates/zkvm/jolt/src/compiler/rust_rv32ima_customized.rs b/crates/zkvm/jolt/src/compiler/rust_rv32ima_customized.rs index b66cb97..9d77a4a 100644 --- a/crates/zkvm/jolt/src/compiler/rust_rv32ima_customized.rs +++ b/crates/zkvm/jolt/src/compiler/rust_rv32ima_customized.rs @@ -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(); } } diff --git a/crates/zkvm/jolt/src/jolt_methods.rs b/crates/zkvm/jolt/src/jolt_methods.rs index 65fd997..48177fb 100644 --- a/crates/zkvm/jolt/src/jolt_methods.rs +++ b/crates/zkvm/jolt/src/jolt_methods.rs @@ -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(); diff --git a/crates/zkvm/jolt/src/lib.rs b/crates/zkvm/jolt/src/lib.rs index 1634a95..1018645 100644 --- a/crates/zkvm/jolt/src/lib.rs +++ b/crates/zkvm/jolt/src/lib.rs @@ -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(&self, _reader: R) -> Result { - // Issue for tracking: https://github.com/eth-act/ere/issues/4. - todo!() - } } /// Create `jolt::host::Program` by storing the compiled `elf` to a temporary diff --git a/crates/zkvm/miden/Cargo.toml b/crates/zkvm/miden/Cargo.toml index ed5750b..58b94ca 100644 --- a/crates/zkvm/miden/Cargo.toml +++ b/crates/zkvm/miden/Cargo.toml @@ -6,7 +6,7 @@ rust-version.workspace = true license.workspace = true [dependencies] -bincode.workspace = true +bincode = { workspace = true, features = ["alloc", "serde"] } serde = { workspace = true, features = ["derive"] } thiserror.workspace = true diff --git a/crates/zkvm/miden/src/error.rs b/crates/zkvm/miden/src/error.rs index 26b36d2..d762fef 100644 --- a/crates/zkvm/miden/src/error.rs +++ b/crates/zkvm/miden/src/error.rs @@ -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), } diff --git a/crates/zkvm/miden/src/io.rs b/crates/zkvm/miden/src/io.rs deleted file mode 100644 index f8197d3..0000000 --- a/crates/zkvm/miden/src/io.rs +++ /dev/null @@ -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 = { - let mut words: Vec = 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 { - let output_ints: Vec = outputs.iter().map(|f| f.as_int()).collect(); - bincode::serialize(&output_ints) -} diff --git a/crates/zkvm/miden/src/lib.rs b/crates/zkvm/miden/src/lib.rs index 7e950e9..f9adc8d 100644 --- a/crates/zkvm/miden/src/lib.rs +++ b/crates/zkvm/miden/src/lib.rs @@ -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, } +/// [`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(&self, reader: R) -> Result { - 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 { + 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, 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::, _>>() + .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 = 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 = 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()); } } diff --git a/crates/zkvm/nexus/Cargo.toml b/crates/zkvm/nexus/Cargo.toml index 0896b8d..ce92ea9 100644 --- a/crates/zkvm/nexus/Cargo.toml +++ b/crates/zkvm/nexus/Cargo.toml @@ -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 diff --git a/crates/zkvm/nexus/src/error.rs b/crates/zkvm/nexus/src/error.rs index 4ffb88d..b57299f 100644 --- a/crates/zkvm/nexus/src/error.rs +++ b/crates/zkvm/nexus/src/error.rs @@ -39,7 +39,7 @@ pub enum ProveError { #[error("nexus execution failed: {0}")] Client(#[source] Box), #[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), #[error("Deserialising proof failed: {0}")] - Bincode(#[from] bincode::Error), + Bincode(#[from] bincode::error::DecodeError), } diff --git a/crates/zkvm/nexus/src/lib.rs b/crates/zkvm/nexus/src/lib.rs index 5bc7c6e..9f188fc 100644 --- a/crates/zkvm/nexus/src/lib.rs +++ b/crates/zkvm/nexus/src/lib.rs @@ -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::>() + .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::, ()>(&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::>() + .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::(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(&self, reader: R) -> Result { - 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, NexusError> { - inputs - .iter() - .try_fold(Vec::new(), |mut acc, item| -> Result, 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> = OnceLock::new(); - static FIB_PROGRAM: OnceLock> = OnceLock::new(); - - fn basic_program() -> Vec { - BASIC_PROGRAM + fn basic_program() -> NexusProgram { + static PROGRAM: OnceLock = OnceLock::new(); + PROGRAM .get_or_init(|| { RustRv32i .compile(&testing_guest_directory("nexus", "basic")) @@ -210,36 +178,22 @@ mod tests { .clone() } - fn fib_program() -> Vec { - 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"); - } } diff --git a/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs b/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs index f16db2f..b734f15 100644 --- a/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs @@ -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(); } } diff --git a/crates/zkvm/openvm/src/lib.rs b/crates/zkvm/openvm/src/lib.rs index ea9004a..bb368cb 100644 --- a/crates/zkvm/openvm/src/lib.rs +++ b/crates/zkvm/openvm/src/lib.rs @@ -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(&self, _: R) -> Result { - 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(); } } } diff --git a/crates/zkvm/pico/Cargo.toml b/crates/zkvm/pico/Cargo.toml index 5d761cc..50ef31b 100644 --- a/crates/zkvm/pico/Cargo.toml +++ b/crates/zkvm/pico/Cargo.toml @@ -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 diff --git a/crates/zkvm/pico/src/compiler/rust_rv32ima.rs b/crates/zkvm/pico/src/compiler/rust_rv32ima.rs index e63d7e3..7cb6f5f 100644 --- a/crates/zkvm/pico/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/pico/src/compiler/rust_rv32ima.rs @@ -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(); } } diff --git a/crates/zkvm/pico/src/error.rs b/crates/zkvm/pico/src/error.rs index a86eb16..f4c5cf8 100644 --- a/crates/zkvm/pico/src/error.rs +++ b/crates/zkvm/pico/src/error.rs @@ -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")] diff --git a/crates/zkvm/pico/src/lib.rs b/crates/zkvm/pico/src/lib.rs index 7d2c633..bb7d420 100644 --- a/crates/zkvm/pico/src/lib.rs +++ b/crates/zkvm/pico/src/lib.rs @@ -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(&self, reader: R) -> Result { - bincode::deserialize_from(reader).map_err(zkVMError::other) - } -} - -fn serialize_inputs(stdin: &mut EmulatorStdinBuilder, 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(); } } } diff --git a/crates/zkvm/risc0/Cargo.toml b/crates/zkvm/risc0/Cargo.toml index e2fde7f..ea7a1b5 100644 --- a/crates/zkvm/risc0/Cargo.toml +++ b/crates/zkvm/risc0/Cargo.toml @@ -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] diff --git a/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs b/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs index d1d49a4..84dab72 100644 --- a/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs @@ -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(); } } diff --git a/crates/zkvm/risc0/src/lib.rs b/crates/zkvm/risc0/src/lib.rs index 67035de..693735e 100644 --- a/crates/zkvm/risc0/src/lib.rs +++ b/crates/zkvm/risc0/src/lib.rs @@ -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(&self, reader: R) -> Result { - 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::()`. - InputItem::Object(obj) => env.write(obj)?, - // Corresponding to `env.read::()`. - // - // 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 = 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) diff --git a/crates/zkvm/risc0/src/output.rs b/crates/zkvm/risc0/src/output.rs deleted file mode 100644 index d7f2396..0000000 --- a/crates/zkvm/risc0/src/output.rs +++ /dev/null @@ -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(reader: R) -> Result { - struct WordReadAdapter(R); - - impl WordRead for WordReadAdapter { - 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) -} diff --git a/crates/zkvm/sp1/Cargo.toml b/crates/zkvm/sp1/Cargo.toml index e75e30a..adef140 100644 --- a/crates/zkvm/sp1/Cargo.toml +++ b/crates/zkvm/sp1/Cargo.toml @@ -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 diff --git a/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs b/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs index 493c5f3..09ce7d9 100644 --- a/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs @@ -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(); } } diff --git a/crates/zkvm/sp1/src/error.rs b/crates/zkvm/sp1/src/error.rs index f252073..518a7d9 100644 --- a/crates/zkvm/sp1/src/error.rs +++ b/crates/zkvm/sp1/src/error.rs @@ -73,13 +73,13 @@ pub enum ProveError { Client(#[source] Box), #[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), diff --git a/crates/zkvm/sp1/src/lib.rs b/crates/zkvm/sp1/src/lib.rs index 67fc350..6a25171 100644 --- a/crates/zkvm/sp1/src/lib.rs +++ b/crates/zkvm/sp1/src/lib.rs @@ -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(&self, reader: R) -> Result { - 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); } } diff --git a/crates/zkvm/ziren/Cargo.toml b/crates/zkvm/ziren/Cargo.toml index 1752e33..2453c51 100644 --- a/crates/zkvm/ziren/Cargo.toml +++ b/crates/zkvm/ziren/Cargo.toml @@ -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 diff --git a/crates/zkvm/ziren/src/error.rs b/crates/zkvm/ziren/src/error.rs index 2bd8419..aa1c862 100644 --- a/crates/zkvm/ziren/src/error.rs +++ b/crates/zkvm/ziren/src/error.rs @@ -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), @@ -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), diff --git a/crates/zkvm/ziren/src/lib.rs b/crates/zkvm/ziren/src/lib.rs index b315c42..76f375c 100644 --- a/crates/zkvm/ziren/src/lib.rs +++ b/crates/zkvm/ziren/src/lib.rs @@ -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(&self, reader: R) -> Result { - 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(); } } } diff --git a/crates/zkvm/zisk/Cargo.toml b/crates/zkvm/zisk/Cargo.toml index 7bf52cf..deb3c1e 100644 --- a/crates/zkvm/zisk/Cargo.toml +++ b/crates/zkvm/zisk/Cargo.toml @@ -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 diff --git a/crates/zkvm/zisk/src/error.rs b/crates/zkvm/zisk/src/error.rs index 0a3ccac..820b6d3 100644 --- a/crates/zkvm/zisk/src/error.rs +++ b/crates/zkvm/zisk/src/error.rs @@ -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}")] diff --git a/crates/zkvm/zisk/src/lib.rs b/crates/zkvm/zisk/src/lib.rs index 5099913..3864333 100644 --- a/crates/zkvm/zisk/src/lib.rs +++ b/crates/zkvm/zisk/src/lib.rs @@ -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(&self, _: R) -> Result { - 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`. -fn serialize_inputs(inputs: &Input) -> Result, 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(); } } } diff --git a/docker/openvm/Dockerfile.base b/docker/openvm/Dockerfile.base index 5416676..7be6b66 100644 --- a/docker/openvm/Dockerfile.base +++ b/docker/openvm/Dockerfile.base @@ -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 diff --git a/docker/zisk/Dockerfile.base b/docker/zisk/Dockerfile.base index 3dd5f66..8dcaa1e 100644 --- a/docker/zisk/Dockerfile.base +++ b/docker/zisk/Dockerfile.base @@ -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 diff --git a/tests/nexus/basic/.cargo/config.toml b/tests/nexus/basic/.cargo/config.toml deleted file mode 100644 index 80e2c50..0000000 --- a/tests/nexus/basic/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[target.riscv32i-unknown-none-elf] -rustflags = [ - "-C", "link-arg=-Tlink.x", -] -runner="nexus-run" diff --git a/tests/nexus/basic/Cargo.toml b/tests/nexus/basic/Cargo.toml index 2f98a27..e6146db 100644 --- a/tests/nexus/basic/Cargo.toml +++ b/tests/nexus/basic/Cargo.toml @@ -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] - diff --git a/tests/nexus/basic/src/main.rs b/tests/nexus/basic/src/main.rs index b4cea4d..4f33370 100644 --- a/tests/nexus/basic/src/main.rs +++ b/tests/nexus/basic/src/main.rs @@ -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 { + read_private_input().unwrap() + } + + fn write_output(output: &[u8]) { + write_public_output(&output).unwrap() + } +} #[nexus_rt::main] fn main() { - let input_bytes: Vec = read_private_input().expect("failed to read input"); - - // Deserialize the first input (Vec) - let (bytes, remaining): (Vec, &[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 = 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, -} - -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::(); } diff --git a/tests/nexus/fib/Cargo.toml b/tests/nexus/fib/Cargo.toml deleted file mode 100644 index 0b26f13..0000000 --- a/tests/nexus/fib/Cargo.toml +++ /dev/null @@ -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] diff --git a/tests/nexus/fib/src/main.rs b/tests/nexus/fib/src/main.rs deleted file mode 100644 index 4527283..0000000 --- a/tests/nexus/fib/src/main.rs +++ /dev/null @@ -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 = 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 -} diff --git a/tests/openvm/basic/Cargo.toml b/tests/openvm/basic/Cargo.toml index 3821b4d..50391c3 100644 --- a/tests/openvm/basic/Cargo.toml +++ b/tests/openvm/basic/Cargo.toml @@ -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" } diff --git a/tests/openvm/basic/src/main.rs b/tests/openvm/basic/src/main.rs index 8edc8aa..5879569 100644 --- a/tests/openvm/basic/src/main.rs +++ b/tests/openvm/basic/src/main.rs @@ -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 { + 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::(); - - // 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::(); } diff --git a/tests/pico/basic/src/main.rs b/tests/pico/basic/src/main.rs index b447601..d55faa1 100644 --- a/tests/pico/basic/src/main.rs +++ b/tests/pico/basic/src/main.rs @@ -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::(); +impl Platform for PicoPlatform { + fn read_input() -> Vec { + 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::(); } diff --git a/tests/risc0/allocs_alignment/src/main.rs b/tests/risc0/allocs_alignment/src/main.rs index 2f7f764..6ad6e78 100644 --- a/tests/risc0/allocs_alignment/src/main.rs +++ b/tests/risc0/allocs_alignment/src/main.rs @@ -1,7 +1,11 @@ use risc0_zkvm::guest::env; fn main() { - let alignment = env::read::(); + 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) }; diff --git a/tests/risc0/basic/src/main.rs b/tests/risc0/basic/src/main.rs index 5364d88..927ed4d 100644 --- a/tests/risc0/basic/src/main.rs +++ b/tests/risc0/basic/src/main.rs @@ -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 { + 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::(); - - // 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::(); } diff --git a/tests/sp1/basic/src/main.rs b/tests/sp1/basic/src/main.rs index 8b11cb1..45378ff 100644 --- a/tests/sp1/basic/src/main.rs +++ b/tests/sp1/basic/src/main.rs @@ -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::(); +impl Platform for SP1Platform { + fn read_input() -> Vec { + 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::(); } diff --git a/tests/ziren/basic/src/main.rs b/tests/ziren/basic/src/main.rs index 79ccc4d..424818c 100644 --- a/tests/ziren/basic/src/main.rs +++ b/tests/ziren/basic/src/main.rs @@ -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::(); +impl Platform for ZirenPlatform { + fn read_input() -> Vec { + 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::(); } diff --git a/tests/zisk/basic/Cargo.toml b/tests/zisk/basic/Cargo.toml index 88bf2a2..68042d8 100644 --- a/tests/zisk/basic/Cargo.toml +++ b/tests/zisk/basic/Cargo.toml @@ -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" } diff --git a/tests/zisk/basic/src/main.rs b/tests/zisk/basic/src/main.rs index 57f45ec..e5c866b 100644 --- a/tests/zisk/basic/src/main.rs +++ b/tests/zisk/basic/src/main.rs @@ -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 = bincode::deserialize_from(&mut input).unwrap(); +impl Platform for ZiskPlatform { + fn read_input() -> Vec { + 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::(); }