zkVM takes opaque input (#173)

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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