diff --git a/Cargo.lock b/Cargo.lock index 89ee58e..d4ad189 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2320,7 +2320,9 @@ dependencies = [ "openvm-sdk", "openvm-stark-sdk", "openvm-transpiler", + "serde", "thiserror 2.0.12", + "toml 0.8.22", "zkvm-interface", ] diff --git a/crates/ere-openvm/Cargo.toml b/crates/ere-openvm/Cargo.toml index 4666808..8a4b024 100644 --- a/crates/ere-openvm/Cargo.toml +++ b/crates/ere-openvm/Cargo.toml @@ -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 } diff --git a/crates/ere-openvm/src/error.rs b/crates/ere-openvm/src/error.rs index c21a4df..5c63844 100644 --- a/crates/ere-openvm/src/error.rs +++ b/crates/ere-openvm/src/error.rs @@ -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), + #[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), + #[error("Failed to decode elf: {0}")] + DecodeFailed(Box), + #[error("Failed to transpile elf: {0}")] + TranspileFailed(Box), +} + +#[derive(Debug, Error)] +pub enum ExecuteError { + #[error("OpenVM execute failed: {0}")] + Client(#[source] Box), +} + +#[derive(Debug, Error)] +pub enum ProveError { + #[error("OpenVM prove failed: {0}")] Client(#[source] Box), } diff --git a/crates/ere-openvm/src/lib.rs b/crates/ere-openvm/src/lib.rs index ddb2f1c..00f1d50 100644 --- a/crates/ere-openvm/src/lib.rs +++ b/crates/ere-openvm/src/lib.rs @@ -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, + app_config: AppConfig, +} + 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 { - 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: ::Program, + app_config: AppConfig, + app_exe: VmExe, + app_committed_exe: Arc>, + app_pk: Arc>, + _resource: ProverResourceType, } impl EreOpenVM { - pub fn new( - program: ::Program, - _resource_type: ProverResourceType, - ) -> Self { - Self { program } + pub fn new(program: OpenVMProgram, _resource: ProverResourceType) -> Result { + 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 { 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, 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::::decode(&mut proof).unwrap(); - let app_pk = sdk.app_keygen(app_config).unwrap(); - - let proof = ContinuationVmProof::::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"); }