From afc686b4c8f92bdabe5d249f14514dc0d0f07e21 Mon Sep 17 00:00:00 2001 From: Han Date: Tue, 19 Aug 2025 22:30:38 +0800 Subject: [PATCH] In ZisK verify method checks rom digest is expected as the preprocessed one (#99) --- Cargo.lock | 10 +- Cargo.toml | 1 + crates/ere-zisk/Cargo.toml | 2 + crates/ere-zisk/src/error.rs | 51 +++++-- crates/ere-zisk/src/lib.rs | 266 +++++++++++++++++++---------------- 5 files changed, 193 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1b9922..e5d6ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2386,7 +2386,9 @@ dependencies = [ "bincode", "blake3", "build-utils", + "dashmap", "serde", + "serde_json", "tempfile", "test-utils", "thiserror 2.0.12", @@ -3124,9 +3126,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -4519,9 +4521,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", diff --git a/Cargo.toml b/Cargo.toml index 1fb420d..f3c9fc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ 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" serde = "1.0.219" diff --git a/crates/ere-zisk/Cargo.toml b/crates/ere-zisk/Cargo.toml index fad91e8..ff3b9e1 100644 --- a/crates/ere-zisk/Cargo.toml +++ b/crates/ere-zisk/Cargo.toml @@ -8,7 +8,9 @@ license.workspace = true [dependencies] bincode.workspace = true blake3.workspace = true +dashmap.workspace = true serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true tempfile.workspace = true thiserror.workspace = true toml.workspace = true diff --git a/crates/ere-zisk/src/error.rs b/crates/ere-zisk/src/error.rs index b6b2df9..91c3a34 100644 --- a/crates/ere-zisk/src/error.rs +++ b/crates/ere-zisk/src/error.rs @@ -1,4 +1,5 @@ -use std::{io, path::PathBuf, process::ExitStatus}; +use crate::RomDigest; +use std::{io, num::ParseIntError, path::PathBuf, process::ExitStatus}; use thiserror::Error; use zkvm_interface::zkVMError; @@ -8,6 +9,12 @@ impl From for zkVMError { } } +impl From for zkVMError { + fn from(value: CommonError) -> Self { + zkVMError::Other(Box::new(value)) + } +} + #[derive(Debug, Error)] pub enum ZiskError { #[error(transparent)] @@ -101,13 +108,6 @@ pub enum ProveError { TempDir(io::Error), #[error("Failed to serialize input: {0}")] SerializeInput(Box), - #[error("Failed to execute `cargo-zisk rom-setup`: {source}")] - CargoZiskRomSetup { - #[source] - source: io::Error, - }, - #[error("`cargo-zisk rom-setup` failed with status: {status}")] - CargoZiskRomSetupFailed { status: ExitStatus }, #[error("Failed to execute `cargo prove`: {source}")] CargoZiskProve { #[source] @@ -117,7 +117,7 @@ pub enum ProveError { CargoZiskProveFailed { status: ExitStatus }, #[error("Serialising proof with `bincode` failed: {0}")] Bincode(#[from] bincode::Error), - #[error("Failed to obtain prove lock")] + #[error("Prove lock poisoned")] ProveLockPoisoned, } @@ -134,4 +134,37 @@ pub enum VerifyError { }, #[error("Invalid proof: {0}")] InvalidProof(String), + #[error("Invalid public values: {0}")] + DeserializePublicValues(serde_json::Error), + #[error("Invalid public value: {0}")] + ParsePublicValue(ParseIntError), + #[error("Unexpected ROM digest")] + UnexpectedRomDigest { + preprocessed: RomDigest, + public_values: Vec, + }, +} + +#[derive(Debug, Error)] +pub enum CommonError { + #[error("IO failure in temporary directory: {0}")] + TempDir(io::Error), + #[error("ROM digest map poisoned")] + RomDigestMapPoisoned, + #[error("Failed to execute `cargo-zisk rom-setup`: {source}")] + CargoZiskRomSetup { + #[source] + source: io::Error, + }, + #[error("`cargo-zisk rom-setup` failed with status: {status}")] + CargoZiskRomSetupFailed { status: ExitStatus }, + #[error("Failed to find ROM digest")] + RomDigestNotFound, + #[error("Failed to execute `cargo-zisk check-setup`: {source}")] + CargoZiskCheckSetup { + #[source] + source: io::Error, + }, + #[error("`cargo-zisk check-setup` failed with status: {status}")] + CargoZiskCheckSetupFailed { status: ExitStatus }, } diff --git a/crates/ere-zisk/src/lib.rs b/crates/ere-zisk/src/lib.rs index c402341..327dd36 100644 --- a/crates/ere-zisk/src/lib.rs +++ b/crates/ere-zisk/src/lib.rs @@ -2,16 +2,18 @@ use crate::{ compile::compile_zisk_program, - error::{ExecuteError, ProveError, VerifyError, ZiskError}, + error::{CommonError, ExecuteError, ProveError, VerifyError, ZiskError}, }; +use blake3::Hash; +use dashmap::{DashMap, Entry}; use serde::{Deserialize, Serialize}; use std::{ fs, - io::{self, Write}, + io::{self, BufRead, Write}, os::unix::fs::symlink, path::{Path, PathBuf}, process::{Command, Stdio}, - sync::Mutex, + sync::{LazyLock, Mutex}, time, }; use tempfile::{TempDir, tempdir}; @@ -29,10 +31,16 @@ mod error; /// Lock for the command `cargo-zisk check-setup` to avoid multiple runs. static SETUP_LOCK: Mutex = Mutex::new(false); +/// Mapping from ELF hash to ROM digest. It uses `blake3` for ELF hash as ZisK. +static ROM_DIGEST_MAP: LazyLock> = LazyLock::new(DashMap::new); + /// It panics if `EreZisk::prove` is called concurrently, so we need a lock here /// to avoid that. static PROVE_LOCK: Mutex<()> = Mutex::new(()); +/// Merkle root of ROM trace generated by `cargo-zisk rom-setup`. +pub type RomDigest = [u64; 4]; + #[allow(non_camel_case_types)] pub struct RV64_IMA_ZISK_ZKVM_ELF; @@ -74,7 +82,7 @@ impl zkVM for EreZisk { .map_err(ZiskError::Execute)?; let mut tempdir = - ZiskTempDir::new(false).map_err(|e| ZiskError::Execute(ExecuteError::TempDir(e)))?; + ZiskTempDir::new().map_err(|e| ZiskError::Execute(ExecuteError::TempDir(e)))?; tempdir .write_elf(&self.elf) .map_err(|e| ZiskError::Execute(ExecuteError::TempDir(e)))?; @@ -96,10 +104,9 @@ impl zkVM for EreZisk { .map_err(|e| ZiskError::Execute(ExecuteError::Ziskemu { source: e }))?; if !output.status.success() { - return Err(ZiskError::Execute(ExecuteError::ZiskemuFailed { + Err(ZiskError::Execute(ExecuteError::ZiskemuFailed { status: output.status, - }) - .into()); + }))? } let execution_duration = start.elapsed(); @@ -126,6 +133,9 @@ impl zkVM for EreZisk { // Make sure proving key setup is done. check_setup()?; + // Run ELF specific setup + rom_setup(&self.elf)?; + // Obtain the prove lock to make sure proving can't be called concurrently. let _guard = PROVE_LOCK .lock() @@ -139,7 +149,7 @@ impl zkVM for EreZisk { .map_err(ZiskError::Prove)?; let mut tempdir = - ZiskTempDir::new(true).map_err(|e| ZiskError::Prove(ProveError::TempDir(e)))?; + ZiskTempDir::new().map_err(|e| ZiskError::Prove(ProveError::TempDir(e)))?; tempdir .write_elf(&self.elf) .map_err(|e| ZiskError::Prove(ProveError::TempDir(e)))?; @@ -147,29 +157,6 @@ impl zkVM for EreZisk { .write_input(&input_bytes) .map_err(|e| ZiskError::Prove(ProveError::TempDir(e)))?; - // Setup ROM. - - if !is_bin_exists(&self.elf, tempdir.elf_path()) { - info!("Running command `cargo-zisk rom-setup` ..."); - - let status = Command::new("cargo-zisk") - .arg("rom-setup") - .arg("--elf") - .arg(tempdir.elf_path()) - .arg("--zisk-path") - .arg(tempdir.zisk_dir_path()) - .status() - .map_err(|e| ZiskError::Prove(ProveError::CargoZiskRomSetup { source: e }))?; - - if !status.success() { - return Err( - ZiskError::Prove(ProveError::CargoZiskRomSetupFailed { status }).into(), - ); - } - - info!("Command `cargo-zisk rom-setup` succeeded"); - } - // Prove. // TODO: Use `mpirun --np {num_processes} cargo-zisk prove ...` to @@ -198,9 +185,9 @@ impl zkVM for EreZisk { .map_err(|e| ZiskError::Prove(ProveError::CargoZiskProve { source: e }))?; if !status.success() { - return Err( - ZiskError::Prove(ProveError::CargoZiskProveFailed { status }).into(), - ); + Err(ZiskError::Prove(ProveError::CargoZiskProveFailed { + status, + }))? } } ProverResourceType::Gpu => { @@ -233,9 +220,9 @@ impl zkVM for EreZisk { .map_err(|e| ZiskError::Prove(ProveError::CargoZiskProve { source: e }))?; if !status.success() { - return Err( - ZiskError::Prove(ProveError::CargoZiskProveFailed { status }).into(), - ); + Err(ZiskError::Prove(ProveError::CargoZiskProveFailed { + status, + }))? } } ProverResourceType::Network(_) => { @@ -263,13 +250,16 @@ impl zkVM for EreZisk { } fn verify(&self, bytes: &[u8]) -> Result<(), zkVMError> { + // Run ELF specific setup + let rom_digest = rom_setup(&self.elf)?; + // Write proof and public values to file. let proof_with_public_values: ZiskProofWithPublicValues = bincode::deserialize(bytes) .map_err(|err| ZiskError::Verify(VerifyError::Bincode(err)))?; let mut tempdir = - ZiskTempDir::new(false).map_err(|e| ZiskError::Verify(VerifyError::TempDir(e)))?; + ZiskTempDir::new().map_err(|e| ZiskError::Verify(VerifyError::TempDir(e)))?; tempdir .write_proof(&proof_with_public_values.proof) .map_err(|e| ZiskError::Verify(VerifyError::TempDir(e)))?; @@ -289,10 +279,26 @@ impl zkVM for EreZisk { .map_err(|e| ZiskError::Verify(VerifyError::CargoZiskVerify { source: e }))?; if !output.status.success() { - return Err(ZiskError::Verify(VerifyError::InvalidProof( + Err(ZiskError::Verify(VerifyError::InvalidProof( String::from_utf8_lossy(&output.stderr).to_string(), - )) - .into()); + )))? + } + + // Deserialize public values as json string sequence. + let public_values = + serde_json::from_slice::>(&proof_with_public_values.public_values) + .map_err(|e| ZiskError::Verify(VerifyError::DeserializePublicValues(e)))? + .into_iter() + .map(|v| v.parse()) + .collect::, _>>() + .map_err(|e| ZiskError::Verify(VerifyError::ParsePublicValue(e)))?; + + // The first 4 elements of public values should be equal to preprocessed ROM digest. + if public_values.len() < 4 || public_values[..4] != rom_digest { + Err(ZiskError::Verify(VerifyError::UnexpectedRomDigest { + preprocessed: rom_digest, + public_values, + }))? } Ok(()) @@ -327,6 +333,7 @@ fn dot_zisk_dir_path() -> PathBuf { PathBuf::from(std::env::var("HOME").expect("env `$HOME` should be set")).join(".zisk") } +/// Run ELF independent proving key setup. fn check_setup() -> Result<(), zkVMError> { let mut setup = SETUP_LOCK .lock() @@ -335,23 +342,13 @@ fn check_setup() -> Result<(), zkVMError> { if !*setup { info!("Running command `cargo-zisk check-setup --aggregation`..."); - let output = Command::new("cargo-zisk") + let status = Command::new("cargo-zisk") .args(["check-setup", "--aggregation"]) - .output() - .map_err(|e| { - zkVMError::Other( - format!("Failed to run command `cargo-zisk check-setup`: {e}").into(), - ) - })?; + .status() + .map_err(|e| CommonError::CargoZiskCheckSetup { source: e })?; - if !output.status.success() { - return Err(zkVMError::Other( - format!( - "Command `cargo-zisk check-setup` failed: {}", - String::from_utf8_lossy(&output.stderr) - ) - .into(), - )); + if !status.success() { + Err(CommonError::CargoZiskCheckSetupFailed { status })? } info!("Command `cargo-zisk check-setup --aggregation` succeeded"); @@ -362,96 +359,117 @@ fn check_setup() -> Result<(), zkVMError> { Ok(()) } -/// Check if these files exists in `$HOME/.zisk/cache`: -/// -/// - `{elf_file_stem}-{elf_hash}-mo.bin` -/// - `{elf_file_stem}-{elf_hash}-mt.bin` -/// - `{elf_file_stem}-{elf_hash}-rh.bin` -/// -/// Which are generated by `cargo-zisk rom-setup ...`. -fn is_bin_exists(elf: &[u8], elf_path: impl AsRef) -> bool { - let stem = elf_path - .as_ref() - .file_stem() - .expect("ELF file has name") - .to_str() - .expect("ELF file name is valid UTF-8"); - let hash = blake3::hash(elf).to_hex().to_string(); - ["mo", "mt", "rh"].into_iter().all(|suffix| { - fs::exists( - dot_zisk_dir_path() - .join("cache") - .join(format!("{stem}-{hash}-{suffix}.bin")), - ) - .ok() - == Some(true) - }) +/// Run ELF specific setup and returns digest of ROM. +fn rom_setup(elf: &[u8]) -> Result { + let mut tempdir = ZiskTempDir::new().map_err(CommonError::TempDir)?; + tempdir.create_zisk_dir().map_err(CommonError::TempDir)?; + tempdir.write_elf(elf).map_err(CommonError::TempDir)?; + + let rom_digest = match ROM_DIGEST_MAP.entry(blake3::hash(elf)) { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => { + info!("Running command `cargo-zisk rom-setup` ..."); + + let output = Command::new("cargo-zisk") + .arg("rom-setup") + .arg("--elf") + .arg(tempdir.elf_path()) + .arg("--zisk-path") + .arg(tempdir.zisk_dir_path()) + .output() + .map_err(|e| CommonError::CargoZiskRomSetup { source: e })?; + + if !output.status.success() { + Err(CommonError::CargoZiskRomSetupFailed { + status: output.status, + })?; + } + + let rom_digest = output + .stdout + .lines() + .find_map(|line| { + let line = line.ok()?; + let line = line.split_once("Root hash: [")?.1; + let line = line.strip_suffix("]")?; + line.split(", ") + .filter_map(|word| word.parse::().ok()) + .collect::>() + .try_into() + .ok() + }) + .ok_or(CommonError::RomDigestNotFound)?; + + info!("Command `cargo-zisk rom-setup` succeeded"); + + *entry.insert(rom_digest) + } + }; + + Ok(rom_digest) } struct ZiskTempDir { tempdir: TempDir, - elf_hash: Option, } impl ZiskTempDir { /// Create temporary directories for: /// - `guest.elf` - ELF compiled from guest program. - /// - `zisk/` - Directory for building process during `rom-setup`. /// - `input.bin` - Input of execution or proving. /// - `output/vadcop_final_proof.json` - Aggregated proof generated by proving. /// - `output/publics.json` - Public values generated by proving. - /// - /// Set `with_zisk_dir` only when `rom-setup` is to be used. - fn new(with_zisk_dir: bool) -> io::Result { + fn new() -> io::Result { let tempdir = Self { tempdir: tempdir()?, - elf_hash: None, }; fs::create_dir(tempdir.output_dir_path())?; - if with_zisk_dir { - fs::create_dir_all(tempdir.zisk_dir_path())?; - - // Check the global zisk directory exists. - let global_zisk_dir_path = dot_zisk_dir_path().join("zisk"); - if !global_zisk_dir_path.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - format!( - "Global .zisk/zisk directory not found at: {}", - global_zisk_dir_path.display() - ), - )); - } - - // Symlink necessary files for `make` command of `cargo-zisk rom-setup`. - // The `Makefile` can be found https://github.com/0xPolygonHermez/zisk/blob/main/emulator-asm/Makefile. - symlink( - dot_zisk_dir_path().join("bin"), - tempdir.dot_zisk_dir_path().join("bin"), - )?; - let temp_zisk_dir_path = tempdir.zisk_dir_path(); - fs::create_dir_all(temp_zisk_dir_path.join("emulator-asm").join("build"))?; - symlink( - global_zisk_dir_path.join("emulator-asm").join("Makefile"), - temp_zisk_dir_path.join("emulator-asm").join("Makefile"), - )?; - symlink( - global_zisk_dir_path.join("emulator-asm").join("src"), - temp_zisk_dir_path.join("emulator-asm").join("src"), - )?; - symlink( - global_zisk_dir_path.join("lib-c"), - temp_zisk_dir_path.join("lib-c"), - )?; - } - Ok(tempdir) } + /// Create temporary directory `zisk` for `rom-setup`. + fn create_zisk_dir(&mut self) -> io::Result<()> { + fs::create_dir_all(self.zisk_dir_path())?; + + // Check the global zisk directory exists. + let global_zisk_dir_path = dot_zisk_dir_path().join("zisk"); + if !global_zisk_dir_path.exists() { + Err(io::Error::new( + io::ErrorKind::NotFound, + format!( + "Global .zisk/zisk directory not found at: {}", + global_zisk_dir_path.display() + ), + ))? + } + + // Symlink necessary files for `make` command of `cargo-zisk rom-setup`. + // The `Makefile` can be found https://github.com/0xPolygonHermez/zisk/blob/v0.10.0/emulator-asm/Makefile. + symlink( + dot_zisk_dir_path().join("bin"), + self.dot_zisk_dir_path().join("bin"), + )?; + let temp_zisk_dir_path = self.zisk_dir_path(); + fs::create_dir_all(temp_zisk_dir_path.join("emulator-asm").join("build"))?; + symlink( + global_zisk_dir_path.join("emulator-asm").join("Makefile"), + temp_zisk_dir_path.join("emulator-asm").join("Makefile"), + )?; + symlink( + global_zisk_dir_path.join("emulator-asm").join("src"), + temp_zisk_dir_path.join("emulator-asm").join("src"), + )?; + symlink( + global_zisk_dir_path.join("lib-c"), + temp_zisk_dir_path.join("lib-c"), + )?; + + Ok(()) + } + fn write_elf(&mut self, elf: &[u8]) -> io::Result<()> { - self.elf_hash = Some(blake3::hash(elf).to_hex().to_string()); fs::write(self.elf_path(), elf) }