Do preprocess when instantiating EreOpenVM (#67)

This commit is contained in:
Han
2025-07-28 22:02:31 +08:00
committed by GitHub
parent 21e2c161de
commit 7f812cda7e
4 changed files with 155 additions and 95 deletions

2
Cargo.lock generated
View File

@@ -2320,7 +2320,9 @@ dependencies = [
"openvm-sdk",
"openvm-stark-sdk",
"openvm-transpiler",
"serde",
"thiserror 2.0.12",
"toml 0.8.22",
"zkvm-interface",
]

View File

@@ -14,7 +14,9 @@ openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", ta
openvm-build = { git = "https://github.com/openvm-org/openvm.git", tag = "v1.2.0", default-features = false }
openvm-transpiler = { git = "https://github.com/openvm-org/openvm.git", tag = "v1.2.0", default-features = false }
toml = "0.8"
thiserror = "2"
serde = { version = "1.0", features = ["derive"] }
[build-dependencies]
build-utils = { workspace = true }

View File

@@ -1,3 +1,4 @@
use std::{io, path::PathBuf};
use thiserror::Error;
use zkvm_interface::zkVMError;
@@ -12,13 +13,45 @@ pub enum OpenVMError {
#[error(transparent)]
Compile(#[from] CompileError),
#[error(transparent)]
Execute(#[from] ExecuteError),
#[error(transparent)]
Prove(#[from] ProveError),
#[error(transparent)]
Verify(#[from] VerifyError),
}
#[derive(Debug, Error)]
pub enum CompileError {
#[error("OpenVM execution failed: {0}")]
#[error("Failed to build guest, code: {0}")]
BuildFailed(i32),
#[error("Guest building skipped (OPENVM_SKIP_BUILD is set)")]
BuildSkipped,
#[error("Missing to find unique elf: {0}")]
UniqueElfNotFound(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Failed to read elf at {path}: {source}")]
ReadElfFailed { source: io::Error, path: PathBuf },
#[error("Failed to read OpenVM's config file at {path}: {source}")]
ReadConfigFailed { source: io::Error, path: PathBuf },
#[error("Failed to deserialize OpenVM's config file: {0}")]
DeserializeConfigFailed(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Failed to decode elf: {0}")]
DecodeFailed(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Failed to transpile elf: {0}")]
TranspileFailed(Box<dyn std::error::Error + Send + Sync + 'static>),
}
#[derive(Debug, Error)]
pub enum ExecuteError {
#[error("OpenVM execute failed: {0}")]
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
}
#[derive(Debug, Error)]
pub enum ProveError {
#[error("OpenVM prove failed: {0}")]
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
}

View File

@@ -1,18 +1,19 @@
use std::{path::Path, time::Instant};
use crate::error::{CompileError, ExecuteError, OpenVMError, VerifyError};
use openvm_build::GuestOptions;
use openvm_circuit::arch::ContinuationVmProof;
use openvm_circuit::{
arch::{ContinuationVmProof, instructions::exe::VmExe},
system::program::trace::VmCommittedExe,
};
use openvm_sdk::{
Sdk, StdIn,
F, SC, Sdk, StdIn,
codec::{Decode, Encode},
config::{AppConfig, SdkVmConfig},
prover::AppProver,
config::{AppConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP, SdkVmConfig},
keygen::AppProvingKey,
};
use openvm_stark_sdk::config::{
FriParameters, baby_bear_poseidon2::BabyBearPoseidon2Config,
baby_bear_poseidon2::BabyBearPoseidon2Engine,
};
use openvm_transpiler::elf::Elf;
use openvm_stark_sdk::config::FriParameters;
use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE};
use serde::{Deserialize, Serialize};
use std::{fs, path::Path, sync::Arc, time::Instant};
use zkvm_interface::{
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, ProverResourceType,
zkVM, zkVMError,
@@ -20,59 +21,115 @@ use zkvm_interface::{
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod error;
use error::{CompileError, OpenVMError, VerifyError};
#[allow(non_camel_case_types)]
pub struct OPENVM_TARGET;
#[derive(Clone, Serialize, Deserialize)]
pub struct OpenVMProgram {
elf: Vec<u8>,
app_config: AppConfig<SdkVmConfig>,
}
impl Compiler for OPENVM_TARGET {
type Error = OpenVMError;
type Program = Elf;
type Program = OpenVMProgram;
// Inlining `openvm_sdk::Sdk::build` in order to get raw elf bytes.
fn compile(workspace_path: &Path, guest_relative: &Path) -> Result<Self::Program, Self::Error> {
let sdk = Sdk::new();
let guest_directory = workspace_path.join(guest_relative);
let pkg = openvm_build::get_package(&guest_directory);
let guest_opts = GuestOptions::default().with_profile("release".to_string());
let target_dir = match openvm_build::build_guest_package(&pkg, &guest_opts, None, &None) {
Ok(target_dir) => target_dir,
Err(Some(code)) => return Err(CompileError::BuildFailed(code).into()),
Err(None) => return Err(CompileError::BuildSkipped.into()),
};
// Build the guest crate
let elf: Elf = sdk
.build(
GuestOptions::default(),
workspace_path.join(guest_relative),
&Default::default(),
)
.map_err(|e| CompileError::Client(e.into()))?;
// TODO: note that this does not transpile (check to see how expensive that is)
let elf_path = openvm_build::find_unique_executable(&guest_directory, target_dir, &None)
.map_err(|e| CompileError::UniqueElfNotFound(e.into()))?;
let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed {
source,
path: elf_path.to_path_buf(),
})?;
Ok(elf)
let app_config_path = guest_directory.join("openvm.toml");
let app_config = if app_config_path.exists() {
let toml = fs::read_to_string(&app_config_path).map_err(|source| {
CompileError::ReadConfigFailed {
source,
path: app_config_path.to_path_buf(),
}
})?;
toml::from_str(&toml)
.map_err(|err| CompileError::DeserializeConfigFailed(err.into()))?
} else {
// The default `AppConfig` copied from https://github.com/openvm-org/openvm/blob/ca36de3/crates/cli/src/default.rs#L31.
AppConfig {
app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_APP_LOG_BLOWUP,
)
.into(),
// By default it supports RISCV32IM with IO but no precompiles.
app_vm_config: SdkVmConfig::builder()
.system(Default::default())
.rv32i(Default::default())
.rv32m(Default::default())
.io(Default::default())
.build(),
leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_LEAF_LOG_BLOWUP,
)
.into(),
compiler_options: Default::default(),
}
};
Ok(OpenVMProgram { elf, app_config })
}
}
pub struct EreOpenVM {
program: <OPENVM_TARGET as Compiler>::Program,
app_config: AppConfig<SdkVmConfig>,
app_exe: VmExe<F>,
app_committed_exe: Arc<VmCommittedExe<SC>>,
app_pk: Arc<AppProvingKey<SdkVmConfig>>,
_resource: ProverResourceType,
}
impl EreOpenVM {
pub fn new(
program: <OPENVM_TARGET as Compiler>::Program,
_resource_type: ProverResourceType,
) -> Self {
Self { program }
pub fn new(program: OpenVMProgram, _resource: ProverResourceType) -> Result<Self, zkVMError> {
let sdk = Sdk::new();
let elf = Elf::decode(&program.elf, MEM_SIZE as u32)
.map_err(|e| OpenVMError::from(CompileError::DecodeFailed(e.into())))?;
let app_exe = sdk
.transpile(elf, program.app_config.app_vm_config.transpiler())
.map_err(|e| OpenVMError::from(CompileError::TranspileFailed(e.into())))?;
let app_pk = sdk
.app_keygen(program.app_config.clone())
.map_err(|e| zkVMError::Other(e.into()))?;
let app_committed_exe = sdk
.commit_app_exe(app_pk.app_fri_params(), app_exe.clone())
.map_err(|e| zkVMError::Other(e.into()))?;
Ok(Self {
app_config: program.app_config,
app_exe,
app_committed_exe,
app_pk: Arc::new(app_pk),
_resource,
})
}
}
impl zkVM for EreOpenVM {
fn execute(&self, inputs: &Input) -> Result<zkvm_interface::ProgramExecutionReport, zkVMError> {
let sdk = Sdk::new();
let vm_cfg = SdkVmConfig::builder()
.system(Default::default())
.rv32i(Default::default())
.rv32m(Default::default())
.io(Default::default())
.build();
let exe = sdk
.transpile(self.program.clone(), vm_cfg.transpiler())
.map_err(|e| CompileError::Client(e.into()))
.map_err(OpenVMError::from)?;
let mut stdin = StdIn::default();
for input in inputs.iter() {
@@ -84,9 +141,12 @@ impl zkVM for EreOpenVM {
let start = Instant::now();
let _outputs = sdk
.execute(exe.clone(), vm_cfg.clone(), stdin)
.map_err(|e| CompileError::Client(e.into()))
.map_err(OpenVMError::from)?;
.execute(
self.app_exe.clone(),
self.app_config.app_vm_config.clone(),
stdin,
)
.map_err(|e| OpenVMError::from(ExecuteError::Client(e.into())))?;
Ok(ProgramExecutionReport {
execution_duration: start.elapsed(),
@@ -98,21 +158,7 @@ impl zkVM for EreOpenVM {
&self,
inputs: &Input,
) -> Result<(Vec<u8>, zkvm_interface::ProgramProvingReport), zkVMError> {
// TODO: We need a stateful version in order to not spend a lot of time
// TODO doing things like computing the pk and vk.
let sdk = Sdk::new();
let vm_cfg = SdkVmConfig::builder()
.system(Default::default())
.rv32i(Default::default())
.rv32m(Default::default())
.io(Default::default())
.build();
let app_exe = sdk
.transpile(self.program.clone(), vm_cfg.transpiler())
.map_err(|e| CompileError::Client(e.into()))
.map_err(OpenVMError::from)?;
let mut stdin = StdIn::default();
for input in inputs.iter() {
@@ -122,20 +168,10 @@ impl zkVM for EreOpenVM {
}
}
let app_config = AppConfig::new(FriParameters::standard_fast(), vm_cfg);
let app_pk = sdk.app_keygen(app_config).unwrap();
let app_committed_exe = sdk
.commit_app_exe(app_pk.app_fri_params(), app_exe)
.unwrap();
let prover = AppProver::<_, BabyBearPoseidon2Engine>::new(
app_pk.app_vm_pk.clone(),
app_committed_exe,
);
let now = std::time::Instant::now();
let proof = prover.generate_app_proof(stdin);
let proof = sdk
.generate_app_proof(self.app_pk.clone(), self.app_committed_exe.clone(), stdin)
.unwrap();
let elapsed = now.elapsed();
let proof_bytes = proof.encode_to_vec().unwrap();
@@ -145,20 +181,10 @@ impl zkVM for EreOpenVM {
fn verify(&self, mut proof: &[u8]) -> Result<(), zkVMError> {
let sdk = Sdk::new();
let vm_cfg = SdkVmConfig::builder()
.system(Default::default())
.rv32i(Default::default())
.rv32m(Default::default())
.io(Default::default())
.build();
let app_config = AppConfig::new(FriParameters::standard_fast(), vm_cfg);
let proof = ContinuationVmProof::<SC>::decode(&mut proof).unwrap();
let app_pk = sdk.app_keygen(app_config).unwrap();
let proof = ContinuationVmProof::<BabyBearPoseidon2Config>::decode(&mut proof).unwrap();
let app_vk = app_pk.get_app_vk();
let app_vk = self.app_pk.get_app_vk();
sdk.verify_app_proof(&app_vk, &proof)
.map(|_payload| ())
.map_err(|e| OpenVMError::Verify(VerifyError::Client(e.into())))
@@ -199,12 +225,9 @@ mod tests {
#[test]
fn test_compile() {
let test_guest_path = get_compile_test_guest_program_path();
let elf =
let program =
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
assert!(
!elf.instructions.is_empty(),
"ELF bytes should not be empty."
);
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
}
#[test]
@@ -212,10 +235,10 @@ mod tests {
fn test_execute_empty_input_panic() {
// Panics because the program expects input arguments, but we supply none
let test_guest_path = get_compile_test_guest_program_path();
let elf =
let program =
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
let empty_input = Input::new();
let zkvm = EreOpenVM::new(elf, ProverResourceType::Cpu);
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&empty_input).unwrap();
}
@@ -223,24 +246,24 @@ mod tests {
#[test]
fn test_execute() {
let test_guest_path = get_compile_test_guest_program_path();
let elf =
let program =
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
let mut input = Input::new();
input.write(10u64);
let zkvm = EreOpenVM::new(elf, ProverResourceType::Cpu);
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&input).unwrap();
}
#[test]
fn test_prove_verify() {
let test_guest_path = get_compile_test_guest_program_path();
let elf =
let program =
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
let mut input = Input::new();
input.write(10u64);
let zkvm = EreOpenVM::new(elf, ProverResourceType::Cpu);
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
let (proof, _) = zkvm.prove(&input).unwrap();
zkvm.verify(&proof).expect("proof should verify");
}