In ZisK verify method checks rom digest is expected as the preprocessed one (#99)

This commit is contained in:
Han
2025-08-19 22:30:38 +08:00
committed by GitHub
parent 7749e1dfc6
commit afc686b4c8
5 changed files with 193 additions and 137 deletions

10
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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

View File

@@ -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<ZiskError> for zkVMError {
}
}
impl From<CommonError> 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<dyn std::error::Error + Send + Sync>),
#[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<u64>,
},
}
#[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 },
}

View File

@@ -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<bool> = Mutex::new(false);
/// Mapping from ELF hash to ROM digest. It uses `blake3` for ELF hash as ZisK.
static ROM_DIGEST_MAP: LazyLock<DashMap<Hash, RomDigest>> = 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::<Vec<String>>(&proof_with_public_values.public_values)
.map_err(|e| ZiskError::Verify(VerifyError::DeserializePublicValues(e)))?
.into_iter()
.map(|v| v.parse())
.collect::<Result<Vec<u64>, _>>()
.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<Path>) -> 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<RomDigest, zkVMError> {
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::<u64>().ok())
.collect::<Vec<_>>()
.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<String>,
}
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<Self> {
fn new() -> io::Result<Self> {
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)
}