mirror of
https://github.com/eth-act/ere.git
synced 2026-04-03 03:00:17 -04:00
feat: Impl zkVM for Nexus zkvm (#47)
Co-authored-by: Han <tinghan0110@gmail.com>
This commit is contained in:
@@ -153,7 +153,7 @@ mod tests {
|
||||
let test_guest_path = get_compile_test_guest_program_path();
|
||||
let program = JOLT_TARGET::compile(&test_guest_path, Path::new("")).unwrap();
|
||||
let mut inputs = Input::new();
|
||||
inputs.write(1 as u32);
|
||||
inputs.write(1_u32);
|
||||
|
||||
let zkvm = EreJolt::new(program, ProverResourceType::Cpu);
|
||||
let _execution = zkvm.execute(&inputs).unwrap();
|
||||
|
||||
26
crates/ere-nexus/Cargo.toml
Normal file
26
crates/ere-nexus/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "ere-nexus"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
zkvm-interface = { workspace = true }
|
||||
|
||||
nexus-sdk = { git = "https://github.com/nexus-xyz/nexus-zkvm.git", tag = "0.3.4", version = "0.3.4" }
|
||||
|
||||
thiserror = "2"
|
||||
bincode = "1.3"
|
||||
tracing = "0.1"
|
||||
toml = { version = "0.9.2", features = ["parse", "display", "serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
build-utils.workspace = true
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
5
crates/ere-nexus/build.rs
Normal file
5
crates/ere-nexus/build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use build_utils::detect_and_generate_name_and_sdk_version;
|
||||
|
||||
fn main() {
|
||||
detect_and_generate_name_and_sdk_version("nexus", "nexus-sdk");
|
||||
}
|
||||
51
crates/ere-nexus/src/error.rs
Normal file
51
crates/ere-nexus/src/error.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
use zkvm_interface::zkVMError;
|
||||
|
||||
impl From<NexusError> for zkVMError {
|
||||
fn from(value: NexusError) -> Self {
|
||||
zkVMError::Other(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NexusError {
|
||||
#[error(transparent)]
|
||||
Compile(#[from] CompileError),
|
||||
|
||||
#[error(transparent)]
|
||||
Prove(#[from] ProveError),
|
||||
|
||||
#[error(transparent)]
|
||||
Verify(#[from] VerifyError),
|
||||
|
||||
/// Guest program directory does not exist.
|
||||
#[error("guest program directory not found: {0}")]
|
||||
PathNotFound(PathBuf),
|
||||
|
||||
/// Expected ELF file was not produced.
|
||||
#[error("ELF file not found at {0}")]
|
||||
ElfNotFound(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CompileError {
|
||||
#[error("nexus execution failed: {0}")]
|
||||
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ProveError {
|
||||
#[error("nexus execution failed: {0}")]
|
||||
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("Serialising proof with `bincode` failed: {0}")]
|
||||
Bincode(#[from] bincode::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VerifyError {
|
||||
#[error("nexus verification failed: {0}")]
|
||||
Client(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("Deserialising proof failed: {0}")]
|
||||
Bincode(#[from] bincode::Error),
|
||||
}
|
||||
219
crates/ere-nexus/src/lib.rs
Normal file
219
crates/ere-nexus/src/lib.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use nexus_sdk::compile::cargo::CargoPackager;
|
||||
use nexus_sdk::compile::{Compile, Compiler as NexusCompiler};
|
||||
use nexus_sdk::stwo::seq::{Proof, Stwo};
|
||||
use nexus_sdk::{Local, Prover, Verifiable};
|
||||
use tracing::info;
|
||||
use zkvm_interface::{
|
||||
Compiler, Input, ProgramExecutionReport, ProgramProvingReport, ProverResourceType, zkVM,
|
||||
zkVMError,
|
||||
};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
|
||||
|
||||
mod error;
|
||||
pub(crate) mod utils;
|
||||
|
||||
use crate::error::ProveError;
|
||||
use crate::utils::get_cargo_package_name;
|
||||
use error::{CompileError, NexusError, VerifyError};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct NEXUS_TARGET;
|
||||
|
||||
impl Compiler for NEXUS_TARGET {
|
||||
type Error = NexusError;
|
||||
|
||||
type Program = PathBuf;
|
||||
|
||||
fn compile(
|
||||
workspace_directory: &Path,
|
||||
guest_relative: &Path,
|
||||
) -> Result<Self::Program, Self::Error> {
|
||||
let guest_path = workspace_directory.join(guest_relative);
|
||||
|
||||
// 1. Check guest path
|
||||
if !guest_path.exists() {
|
||||
return Err(NexusError::PathNotFound(guest_path.to_path_buf()));
|
||||
}
|
||||
std::env::set_current_dir(&guest_path).map_err(|e| CompileError::Client(e.into()))?;
|
||||
|
||||
let package_name = get_cargo_package_name(&guest_path)
|
||||
.ok_or(CompileError::Client(Box::from(format!(
|
||||
"Failed to get guest package name, where guest path: {:?}",
|
||||
guest_path
|
||||
))))
|
||||
.map_err(|e| CompileError::Client(e.into()))?;
|
||||
let mut prover_compiler = NexusCompiler::<CargoPackager>::new(&package_name);
|
||||
let elf_path = prover_compiler
|
||||
.build()
|
||||
.map_err(|e| CompileError::Client(e.into()))?;
|
||||
|
||||
Ok(elf_path)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EreNexus {
|
||||
program: <NEXUS_TARGET as Compiler>::Program,
|
||||
}
|
||||
|
||||
impl EreNexus {
|
||||
pub fn new(
|
||||
program: <NEXUS_TARGET as Compiler>::Program,
|
||||
_resource_type: ProverResourceType,
|
||||
) -> Self {
|
||||
Self { program }
|
||||
}
|
||||
}
|
||||
impl zkVM for EreNexus {
|
||||
fn execute(&self, inputs: &Input) -> Result<zkvm_interface::ProgramExecutionReport, zkVMError> {
|
||||
let start = Instant::now();
|
||||
|
||||
// let mut public_input = vec![];
|
||||
let mut private_input = vec![];
|
||||
for input in inputs.iter() {
|
||||
private_input.extend(
|
||||
input
|
||||
.as_bytes()
|
||||
.map_err(|err| NexusError::Prove(ProveError::Client(err)))
|
||||
.map_err(zkVMError::from)?,
|
||||
);
|
||||
}
|
||||
// TODO: Doesn't catch execute for guest in nexus. so only left some dummy code(parse input) here.
|
||||
// Besides, public input is not supported yet, so we just pass an empty tuple
|
||||
|
||||
Ok(ProgramExecutionReport {
|
||||
execution_duration: start.elapsed(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn prove(
|
||||
&self,
|
||||
inputs: &Input,
|
||||
) -> Result<(Vec<u8>, zkvm_interface::ProgramProvingReport), zkVMError> {
|
||||
let prover: Stwo<Local> = Stwo::new_from_file(&self.program.to_string_lossy().to_string())
|
||||
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))
|
||||
.map_err(zkVMError::from)?;
|
||||
|
||||
// One convention that may be useful for simplifying the design is that all inputs to the vm are private and all outputs are public.
|
||||
// If an input should be public, then it could just be returned from the function.
|
||||
// let mut public_input = vec![];
|
||||
let mut private_input = vec![];
|
||||
for input in inputs.iter() {
|
||||
private_input.extend(
|
||||
input
|
||||
.as_bytes()
|
||||
.map_err(|err| NexusError::Prove(ProveError::Client(err)))
|
||||
.map_err(zkVMError::from)?,
|
||||
);
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
let (_view, proof) = prover
|
||||
.prove_with_input(&private_input, &())
|
||||
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))
|
||||
.map_err(zkVMError::from)?;
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
let bytes = bincode::serialize(&proof)
|
||||
.map_err(|err| NexusError::Prove(ProveError::Bincode(err)))?;
|
||||
|
||||
Ok((bytes, ProgramProvingReport::new(elapsed)))
|
||||
}
|
||||
|
||||
fn verify(&self, proof: &[u8]) -> Result<(), zkVMError> {
|
||||
info!("Verifying proof...");
|
||||
|
||||
let proof: Proof = bincode::deserialize(proof)
|
||||
.map_err(|err| NexusError::Verify(VerifyError::Bincode(err)))?;
|
||||
|
||||
let prover: Stwo<Local> = Stwo::new_from_file(&self.program.to_string_lossy().to_string())
|
||||
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))
|
||||
.map_err(zkVMError::from)?;
|
||||
let elf = prover.elf.clone(); // save elf for use with verification
|
||||
#[rustfmt::skip]
|
||||
proof
|
||||
.verify_expected::<(), ()>(
|
||||
&(), // no public input
|
||||
nexus_sdk::KnownExitCodes::ExitSuccess as u32,
|
||||
&(), // no public output
|
||||
&elf, // expected elf (program binary)
|
||||
&[], // no associated data,
|
||||
)
|
||||
.map_err(|e| NexusError::Verify(VerifyError::Client(e.into())))
|
||||
.map_err(zkVMError::from)?;
|
||||
|
||||
info!("Verify Succeeded!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
NAME
|
||||
}
|
||||
|
||||
fn sdk_version(&self) -> &'static str {
|
||||
SDK_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use zkvm_interface::Compiler;
|
||||
|
||||
use crate::NEXUS_TARGET;
|
||||
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn get_test_guest_program_path() -> PathBuf {
|
||||
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
|
||||
PathBuf::from(workspace_dir)
|
||||
.join("tests")
|
||||
.join("nexus")
|
||||
.join("guest")
|
||||
.canonicalize()
|
||||
.expect("Failed to find or canonicalize test guest program at <CARGO_WORKSPACE_DIR>/tests/compile/nexus")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile() -> anyhow::Result<()> {
|
||||
let test_guest_path = get_test_guest_program_path();
|
||||
let elf_path = NEXUS_TARGET::compile(&test_guest_path, Path::new(""))?;
|
||||
let prover: Stwo<Local> = Stwo::new_from_file(&elf_path.to_string_lossy().to_string())?;
|
||||
let elf = prover.elf.clone();
|
||||
assert!(
|
||||
!elf.instructions.is_empty(),
|
||||
"ELF bytes should not be empty."
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute() {
|
||||
let test_guest_path = get_test_guest_program_path();
|
||||
let elf =
|
||||
NEXUS_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
|
||||
let mut input = Input::new();
|
||||
input.write(10u64);
|
||||
|
||||
let zkvm = EreNexus::new(elf, ProverResourceType::Cpu);
|
||||
zkvm.execute(&input).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prove_verify() -> anyhow::Result<()> {
|
||||
let test_guest_path = get_test_guest_program_path();
|
||||
let elf = NEXUS_TARGET::compile(&test_guest_path, Path::new(""))?;
|
||||
let mut input = Input::new();
|
||||
input.write(10u64);
|
||||
|
||||
let zkvm = EreNexus::new(elf, ProverResourceType::Cpu);
|
||||
let (proof, _) = zkvm.prove(&input).unwrap();
|
||||
zkvm.verify(&proof).expect("proof should verify");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
13
crates/ere-nexus/src/utils.rs
Normal file
13
crates/ere-nexus/src/utils.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use std::fs;
|
||||
use toml::Table;
|
||||
|
||||
pub fn get_cargo_package_name(crate_path: &std::path::Path) -> Option<String> {
|
||||
let cargo_contents = fs::read_to_string(crate_path.join("Cargo.toml")).ok()?;
|
||||
let cargo_toml: Table = toml::from_str(&cargo_contents).ok()?;
|
||||
|
||||
cargo_toml
|
||||
.get("package")?
|
||||
.get("name")?
|
||||
.as_str()
|
||||
.map(|s| s.to_string())
|
||||
}
|
||||
@@ -161,7 +161,7 @@ mod tests {
|
||||
"Attempting to find test guest program at: {}",
|
||||
path.display()
|
||||
);
|
||||
println!("Workspace dir is: {}", workspace_dir);
|
||||
println!("Workspace dir is: {workspace_dir}");
|
||||
|
||||
path.canonicalize()
|
||||
.expect("Failed to find or canonicalize test guest program at <CARGO_WORKSPACE_DIR>/tests/pico/compile/basic/app")
|
||||
@@ -176,11 +176,8 @@ mod tests {
|
||||
Ok(elf_bytes) => {
|
||||
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
|
||||
}
|
||||
Err(e) => {
|
||||
panic!(
|
||||
"compile_sp1_program direct call failed for dedicated guest: {:?}",
|
||||
e
|
||||
);
|
||||
Err(err) => {
|
||||
panic!("compile_sp1_program direct call failed for dedicated guest: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ license.workspace = true
|
||||
[dependencies]
|
||||
zkvm-interface = { workspace = true }
|
||||
anyhow = "1.0" #TODO: remove only needed in tests
|
||||
toml = "0.8"
|
||||
risc0-zkvm = { version = "^2.3.0", features = ["unstable"] }
|
||||
#toml = "0.8"
|
||||
risc0-zkvm = { version = "2.3.0", features = ["unstable"] }
|
||||
borsh = "1.5.7"
|
||||
hex = "*"
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ impl zkVM for EreRisc0 {
|
||||
env.write(serialize).unwrap();
|
||||
}
|
||||
InputItem::Bytes(items) => {
|
||||
env.write_frame(&items);
|
||||
env.write_frame(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ impl zkVM for EreRisc0 {
|
||||
|
||||
fn verify(&self, proof: &[u8]) -> Result<(), zkVMError> {
|
||||
let decoded: Receipt =
|
||||
borsh::from_slice(&proof).map_err(|err| zkVMError::Other(Box::new(err)))?;
|
||||
borsh::from_slice(proof).map_err(|err| zkVMError::Other(Box::new(err)))?;
|
||||
|
||||
decoded
|
||||
.verify(self.program.image_id)
|
||||
@@ -175,7 +175,7 @@ mod prove_tests {
|
||||
let proof_bytes = match zkvm.prove(&input_builder) {
|
||||
Ok((prove_result, _)) => prove_result,
|
||||
Err(err) => {
|
||||
panic!("Proving error in test: {:?}", err);
|
||||
panic!("Proving error in test: {err}",);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -238,8 +238,8 @@ mod execute_tests {
|
||||
|
||||
let result = zkvm.execute(&input_builder);
|
||||
|
||||
if let Err(e) = &result {
|
||||
panic!("Execution error: {:?}", e);
|
||||
if let Err(err) = &result {
|
||||
panic!("Execution error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,8 +93,8 @@ mod tests {
|
||||
Ok(elf_bytes) => {
|
||||
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("compile failed for dedicated guest: {:?}", e);
|
||||
Err(err) => {
|
||||
panic!("compile failed for dedicated guest: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,11 +106,8 @@ mod tests {
|
||||
Ok(elf_bytes) => {
|
||||
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
|
||||
}
|
||||
Err(e) => {
|
||||
panic!(
|
||||
"compile_sp1_program direct call failed for dedicated guest: {:?}",
|
||||
e
|
||||
);
|
||||
Err(err) => {
|
||||
panic!("compile_sp1_program direct call failed for dedicated guest: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,8 +264,8 @@ mod execute_tests {
|
||||
|
||||
let result = zkvm.execute(&input_builder);
|
||||
|
||||
if let Err(e) = &result {
|
||||
panic!("Execution error: {:?}", e);
|
||||
if let Err(err) = &result {
|
||||
panic!("Execution error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ mod prove_tests {
|
||||
let proof_bytes = match zkvm.prove(&input_builder) {
|
||||
Ok((prove_result, _)) => prove_result,
|
||||
Err(err) => {
|
||||
panic!("Proving error in test: {:?}", err);
|
||||
panic!("Proving error in test: {err}");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -387,7 +387,7 @@ mod prove_tests {
|
||||
prove_result
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("Network proving error: {:?}", err);
|
||||
panic!("Network proving error: {err}");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user