Reorganize compiler modules (#156)

This commit is contained in:
Han
2025-10-01 22:27:57 +08:00
committed by GitHub
parent 70a84a375c
commit f78a21ba1e
56 changed files with 1330 additions and 1380 deletions

29
Cargo.lock generated
View File

@@ -3684,8 +3684,6 @@ dependencies = [
"common",
"compile-utils",
"jolt",
"jolt-core",
"jolt-sdk",
"serde",
"tempfile",
"test-utils",
@@ -3717,11 +3715,11 @@ version = "0.0.12"
dependencies = [
"bincode 1.3.3",
"build-utils",
"compile-utils",
"nexus-sdk",
"serde",
"test-utils",
"thiserror 2.0.12",
"toml 0.8.23",
"tracing",
"zkvm-interface",
]
@@ -3742,7 +3740,6 @@ dependencies = [
"test-utils",
"thiserror 2.0.12",
"toml 0.8.23",
"tracing",
"zkvm-interface",
]
@@ -3833,7 +3830,7 @@ version = "0.0.12"
dependencies = [
"bincode 1.3.3",
"build-utils",
"cargo_metadata 0.19.2",
"compile-utils",
"serde",
"test-utils",
"thiserror 2.0.12",
@@ -3850,12 +3847,12 @@ dependencies = [
"blake3",
"build-utils",
"bytemuck",
"compile-utils",
"serde",
"strum 0.27.2",
"tempfile",
"test-utils",
"thiserror 2.0.12",
"toml 0.8.23",
"tracing",
"zkvm-interface",
]
@@ -10154,10 +10151,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [
"bytes",
"heck 0.5.0",
"itertools 0.12.1",
"heck 0.4.1",
"itertools 0.10.5",
"log",
"multimap 0.10.1",
"multimap 0.8.3",
"once_cell",
"petgraph 0.6.5",
"prettyplease 0.2.32",
@@ -10208,7 +10205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [
"anyhow",
"itertools 0.12.1",
"itertools 0.10.5",
"proc-macro2",
"quote",
"syn 2.0.101",
@@ -10511,9 +10508,9 @@ dependencies = [
[[package]]
name = "rangemap"
version = "1.5.1"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684"
checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223"
dependencies = [
"serde",
]
@@ -12818,9 +12815,9 @@ dependencies = [
[[package]]
name = "symbolic-common"
version = "12.16.0"
version = "12.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5199e46f23c77c611aa2a383b2f72721dfee4fb2bf85979eea1e0f26ba6e35"
checksum = "d03f433c9befeea460a01d750e698aa86caf86dcfbd77d552885cd6c89d52f50"
dependencies = [
"debugid",
"memmap2",
@@ -12830,9 +12827,9 @@ dependencies = [
[[package]]
name = "symbolic-demangle"
version = "12.16.0"
version = "12.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa3c03956e32254f74e461a330b9522a2689686d80481708fb2014780d8d3959"
checksum = "13d359ef6192db1760a34321ec4f089245ede4342c27e59be99642f12a859de8"
dependencies = [
"cpp_demangle",
"rustc-demangle",

View File

@@ -64,8 +64,6 @@ twirp-build = "0.9.0"
ark-serialize = "0.5.0"
common = { git = "https://github.com/a16z/jolt.git", rev = "55b9830a3944dde55d33a55c42522b81dd49f87a" }
jolt = { git = "https://github.com/a16z/jolt.git", rev = "55b9830a3944dde55d33a55c42522b81dd49f87a" }
jolt-core = { git = "https://github.com/a16z/jolt.git", rev = "55b9830a3944dde55d33a55c42522b81dd49f87a" }
jolt-sdk = { git = "https://github.com/a16z/jolt.git", rev = "55b9830a3944dde55d33a55c42522b81dd49f87a" }
# Miden dependencies
miden-assembly = { git = "https://github.com/0xPolygonMiden/miden-vm.git", tag = "v0.17.1" }

View File

@@ -135,31 +135,31 @@ fn main() -> Result<(), Error> {
fn compile(guest_path: PathBuf, program_path: PathBuf) -> Result<(), Error> {
#[cfg(feature = "jolt")]
let program = ere_jolt::JOLT_TARGET.compile(&guest_path);
let program = ere_jolt::compiler::RustRv32imaCustomized.compile(&guest_path);
#[cfg(feature = "miden")]
let program = ere_miden::MIDEN_TARGET.compile(&guest_path);
let program = ere_miden::compiler::MidenAsm.compile(&guest_path);
#[cfg(feature = "nexus")]
let program = ere_nexus::NEXUS_TARGET.compile(&guest_path);
let program = ere_nexus::compiler::RustRv32i.compile(&guest_path);
#[cfg(feature = "openvm")]
let program = ere_openvm::OPENVM_TARGET.compile(&guest_path);
let program = ere_openvm::compiler::RustRv32imaCustomized.compile(&guest_path);
#[cfg(feature = "pico")]
let program = ere_pico::PICO_TARGET.compile(&guest_path);
let program = ere_pico::compiler::RustRv32imaCustomized.compile(&guest_path);
#[cfg(feature = "risc0")]
let program = ere_risc0::RV32_IM_RISC0_ZKVM_ELF.compile(&guest_path);
let program = ere_risc0::compiler::RustRv32imaCustomized.compile(&guest_path);
#[cfg(feature = "sp1")]
let program = ere_sp1::RV32_IM_SUCCINCT_ZKVM_ELF.compile(&guest_path);
let program = ere_sp1::compiler::RustRv32imaCustomized.compile(&guest_path);
#[cfg(feature = "ziren")]
let program = ere_ziren::MIPS32R2_ZKM_ZKVM_ELF.compile(&guest_path);
let program = ere_ziren::compiler::RustMips32r2Customized.compile(&guest_path);
#[cfg(feature = "zisk")]
let program = ere_zisk::RV64_IMA_ZISK_ZKVM_ELF.compile(&guest_path);
let program = ere_zisk::compiler::RustRv64imaCustomized.compile(&guest_path);
serde::write(
&program_path,

View File

@@ -14,8 +14,6 @@ thiserror.workspace = true
ark-serialize = { workspace = true, features = ["derive"] }
common.workspace = true
jolt = { workspace = true, features = ["host"] }
jolt-core = { workspace = true, features = ["host"] }
jolt-sdk = { workspace = true, features = ["host"] }
# Local dependencies
compile-utils.workspace = true

View File

@@ -1,70 +0,0 @@
use crate::error::CompileError;
use compile_utils::CargoBuildCmd;
use std::path::Path;
const TARGET_TRIPLE: &str = "riscv32im-unknown-none-elf";
// According to https://github.com/a16z/jolt/blob/55b9830a3944dde55d33a55c42522b81dd49f87a/jolt-core/src/host/mod.rs#L95
const RUSTFLAGS: &[&str] = &[
"-C",
"passes=lower-atomic",
"-C",
"panic=abort",
"-C",
"strip=symbols",
"-C",
"opt-level=z",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
"--features",
"guest",
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
pub fn compile_jolt_program_stock_rust(
guest_directory: &Path,
toolchain: &String,
) -> Result<Vec<u8>, CompileError> {
compile_program_stock_rust(guest_directory, toolchain)
}
fn compile_program_stock_rust(
guest_directory: &Path,
toolchain: &String,
) -> Result<Vec<u8>, CompileError> {
let elf = CargoBuildCmd::new()
.linker_script(Some(make_linker_script()))
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
}
const DEFAULT_MEMORY_SIZE: u64 = 10 * 1024 * 1024;
const DEFAULT_STACK_SIZE: u64 = 4096;
const LINKER_SCRIPT_TEMPLATE: &str = include_str!("template.ld");
fn make_linker_script() -> String {
LINKER_SCRIPT_TEMPLATE
.replace("{MEMORY_SIZE}", &DEFAULT_MEMORY_SIZE.to_string())
.replace("{STACK_SIZE}", &DEFAULT_STACK_SIZE.to_string())
}
#[cfg(test)]
mod tests {
use crate::compile_stock_rust::compile_jolt_program_stock_rust;
use test_utils::host::testing_guest_directory;
#[test]
fn test_stock_compiler_impl() {
let guest_directory = testing_guest_directory("jolt", "stock_nightly_no_std");
let result = compile_jolt_program_stock_rust(&guest_directory, &"nightly".to_string());
assert!(result.is_ok(), "Jolt guest program compilation failure.");
assert!(
!result.unwrap().is_empty(),
"ELF bytes should not be empty."
);
}
}

View File

@@ -0,0 +1,7 @@
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
pub type JoltProgram = Vec<u8>;

View File

@@ -0,0 +1,80 @@
use crate::{
compiler::JoltProgram,
error::{CompileError, JoltError},
};
use compile_utils::CargoBuildCmd;
use std::{env, path::Path};
use zkvm_interface::Compiler;
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
// According to https://github.com/a16z/jolt/blob/55b9830a3944dde55d33a55c42522b81dd49f87a/jolt-core/src/host/mod.rs#L95
const RUSTFLAGS: &[&str] = &[
"-C",
"passes=lower-atomic",
"-C",
"panic=abort",
"-C",
"strip=symbols",
"-C",
"opt-level=z",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
"--features",
"guest",
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
const DEFAULT_MEMORY_SIZE: u64 = 10 * 1024 * 1024;
const DEFAULT_STACK_SIZE: u64 = 4096;
const LINKER_SCRIPT_TEMPLATE: &str = include_str!("rust_rv32ima/template.ld");
fn make_linker_script() -> String {
LINKER_SCRIPT_TEMPLATE
.replace("{MEMORY_SIZE}", &DEFAULT_MEMORY_SIZE.to_string())
.replace("{STACK_SIZE}", &DEFAULT_STACK_SIZE.to_string())
}
/// Compiler for Rust guest program to RV32IMA architecture.
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = JoltError;
type Program = JoltProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_RUST_TOOLCHAIN").unwrap_or_else(|_| "nightly".into());
let elf = CargoBuildCmd::new()
.linker_script(Some(make_linker_script()))
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)
.map_err(CompileError::CompileUtilError)?;
Ok(elf)
}
}
#[cfg(test)]
mod tests {
use crate::{EreJolt, compiler::RustRv32ima};
use test_utils::host::testing_guest_directory;
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("jolt", "stock_nightly_no_std");
let elf = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute() {
let guest_directory = testing_guest_directory("jolt", "stock_nightly_no_std");
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&Input::new()).unwrap();
}
}

View File

@@ -0,0 +1,68 @@
use crate::{
compiler::JoltProgram,
error::{CompileError, JoltError},
};
use compile_utils::cargo_metadata;
use jolt::host::DEFAULT_TARGET_DIR;
use std::{env::set_current_dir, fs, path::Path};
use zkvm_interface::Compiler;
/// Compiler for Rust guest program to RV32IMA architecture, using customized
/// Rust toolchain of Jolt.
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = JoltError;
type Program = JoltProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
// Change current directory for `Program::build` to build guest program.
set_current_dir(guest_directory).map_err(|source| CompileError::SetCurrentDirFailed {
source,
path: guest_directory.to_path_buf(),
})?;
let metadata = cargo_metadata(guest_directory).map_err(CompileError::CompileUtilError)?;
let package_name = &metadata.root_package().unwrap().name;
// Note that if this fails, it will panic, hence we need to catch it.
let elf_path = std::panic::catch_unwind(|| {
let mut program = jolt::host::Program::new(package_name);
program.set_std(true);
program.build(DEFAULT_TARGET_DIR);
program.elf.unwrap()
})
.map_err(|_| CompileError::BuildFailed)?;
let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed {
source,
path: elf_path.to_path_buf(),
})?;
Ok(elf)
}
}
#[cfg(test)]
mod tests {
use crate::{EreJolt, compiler::RustRv32imaCustomized};
use test_utils::host::testing_guest_directory;
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("jolt", "basic");
let elf = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute() {
let guest_directory = testing_guest_directory("jolt", "basic");
let program = RustRv32imaCustomized.compile(&guest_directory).unwrap();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&Input::new()).unwrap();
}
}

View File

@@ -1,5 +1,5 @@
use ark_serialize::SerializationError;
use jolt_core::utils::errors::ProofVerifyError;
use jolt::jolt_core::utils::errors::ProofVerifyError;
use std::{io, path::PathBuf};
use thiserror::Error;
use zkvm_interface::zkVMError;

View File

@@ -1,83 +1,29 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use crate::error::{CompileError, JoltError, ProveError, VerifyError};
use crate::{
compiler::JoltProgram,
error::{JoltError, ProveError, VerifyError},
jolt_methods::{preprocess_prover, preprocess_verifier, prove_generic, verify_generic},
};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use compile_stock_rust::compile_jolt_program_stock_rust;
use compile_utils::cargo_metadata;
use jolt::{JoltHyperKZGProof, JoltProverPreprocessing, JoltVerifierPreprocessing};
use jolt_core::host::Program;
use jolt_methods::{preprocess_prover, preprocess_verifier, prove_generic, verify_generic};
use jolt_sdk::host::DEFAULT_TARGET_DIR;
use serde::de::DeserializeOwned;
use std::{
env,
env::set_current_dir,
fs,
env, fs,
io::{Cursor, Read},
path::Path,
};
use tempfile::TempDir;
use zkvm_interface::{
Compiler, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType,
PublicValues, zkVM, zkVMError,
Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType, PublicValues,
zkVM, zkVMError,
};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod compile_stock_rust;
mod error;
pub mod compiler;
pub mod error;
mod jolt_methods;
#[allow(non_camel_case_types)]
pub struct JOLT_TARGET;
impl Compiler for JOLT_TARGET {
type Error = JoltError;
type Program = Vec<u8>;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "jolt".into());
match toolchain.as_str() {
"jolt" => Ok(compile_jolt_program(guest_directory)?),
_ => Ok(compile_jolt_program_stock_rust(
guest_directory,
&toolchain,
)?),
}
}
}
fn compile_jolt_program(guest_directory: &Path) -> Result<Vec<u8>, JoltError> {
// Change current directory for `Program::build` to build guest program.
set_current_dir(guest_directory).map_err(|source| CompileError::SetCurrentDirFailed {
source,
path: guest_directory.to_path_buf(),
})?;
let package_name = cargo_metadata(guest_directory)
.map_err(CompileError::CompileUtilError)?
.root_package()
.unwrap()
.name
.clone();
// Note that if this fails, it will panic, hence we need to catch it.
let elf_path = std::panic::catch_unwind(|| {
let mut program = Program::new(&package_name);
program.set_std(true);
program.build(DEFAULT_TARGET_DIR);
program.elf.unwrap()
})
.map_err(|_| CompileError::BuildFailed)?;
let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed {
source,
path: elf_path.to_path_buf(),
})?;
Ok(elf)
}
#[derive(CanonicalSerialize, CanonicalDeserialize)]
pub struct EreJoltProof {
proof: JoltHyperKZGProof,
@@ -85,14 +31,14 @@ pub struct EreJoltProof {
}
pub struct EreJolt {
elf: Vec<u8>,
elf: JoltProgram,
prover_preprocessing: JoltProverPreprocessing<4, jolt::F, jolt::PCS, jolt::ProofTranscript>,
verifier_preprocessing: JoltVerifierPreprocessing<4, jolt::F, jolt::PCS, jolt::ProofTranscript>,
_resource: ProverResourceType,
}
impl EreJolt {
pub fn new(elf: Vec<u8>, _resource: ProverResourceType) -> Result<Self, zkVMError> {
pub fn new(elf: JoltProgram, _resource: ProverResourceType) -> Result<Self, zkVMError> {
let (_tempdir, program) = program(&elf)?;
let prover_preprocessing = preprocess_prover(&program);
let verifier_preprocessing = preprocess_verifier(&program);
@@ -178,47 +124,11 @@ impl zkVM for EreJolt {
/// file, and set the elf path for `program`, so methods like `decode`, `trace`
/// and `trace_analyze` that depend on elf path will work.
pub fn program(elf: &[u8]) -> Result<(TempDir, jolt::host::Program), zkVMError> {
let tempdir = TempDir::new().map_err(|err| zkVMError::Other(err.into()))?;
let tempdir = TempDir::new().map_err(zkVMError::other)?;
let elf_path = tempdir.path().join("guest.elf");
fs::write(&elf_path, elf).map_err(|err| zkVMError::Other(err.into()))?;
fs::write(&elf_path, elf).map_err(zkVMError::other)?;
// Set a dummy package name because we don't need to compile anymore.
let mut program = Program::new("");
let mut program = jolt::host::Program::new("");
program.elf = Some(elf_path);
Ok((tempdir, program))
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::OnceLock;
use test_utils::host::{BasicProgramIo, testing_guest_directory};
static BASIC_PROGRAM: OnceLock<Vec<u8>> = OnceLock::new();
fn basic_program() -> Vec<u8> {
BASIC_PROGRAM
.get_or_init(|| {
JOLT_TARGET
.compile(&testing_guest_directory("jolt", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_compiler_impl() {
let elf_bytes = basic_program();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute_nightly() {
let guest_directory = testing_guest_directory("jolt", "stock_nightly_no_std");
let program =
compile_jolt_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap();
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
let result = zkvm.execute(&BasicProgramIo::empty());
assert!(result.is_ok(), "Jolt execution failure");
}
}

View File

@@ -0,0 +1,31 @@
use miden_core::utils::{Deserializable, Serializable};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
mod miden_asm;
pub use miden_asm::MidenAsm;
/// Wrapper for [`miden_core::Program`] that implements `serde`.
#[derive(Clone)]
pub struct MidenProgram(pub miden_core::Program);
impl Serialize for MidenProgram {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.0.to_bytes())
}
}
impl<'de> Deserialize<'de> for MidenProgram {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = Vec::<u8>::deserialize(deserializer)?;
miden_core::Program::read_from_bytes(&bytes)
.map(Self)
.map_err(D::Error::custom)
}
}

View File

@@ -1,14 +1,16 @@
use crate::{
MIDEN_TARGET, MidenProgram,
compiler::MidenProgram,
error::{CompileError, MidenError},
};
use miden_assembly::Assembler;
use miden_core::utils::Serializable;
use miden_stdlib::StdLibrary;
use std::{fs, path::Path};
use zkvm_interface::Compiler;
impl Compiler for MIDEN_TARGET {
/// Compiler for Miden assembly guest program.
pub struct MidenAsm;
impl Compiler for MidenAsm {
type Error = MidenError;
type Program = MidenProgram;
@@ -43,22 +45,20 @@ impl Compiler for MIDEN_TARGET {
.assemble_program(&source)
.map_err(|e| CompileError::AssemblyCompilation(e.to_string()))?;
Ok(MidenProgram {
program_bytes: program.to_bytes(),
})
Ok(MidenProgram(program))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compiler::MidenAsm;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("miden", "fib");
let program = MIDEN_TARGET.compile(&guest_directory).unwrap();
assert!(!program.program_bytes.is_empty());
let program = MidenAsm.compile(&guest_directory).unwrap();
assert!(program.0.num_procedures() > 0);
}
}

View File

@@ -1,9 +1,8 @@
pub mod compile;
pub mod error;
pub mod io;
use self::error::{ExecuteError, MidenError, VerifyError};
use self::io::{generate_miden_inputs, outputs_to_public_values};
use crate::{
compiler::MidenProgram,
error::{ExecuteError, MidenError, VerifyError},
io::{generate_miden_inputs, outputs_to_public_values},
};
use miden_core::{
Program,
utils::{Deserializable, Serializable},
@@ -23,13 +22,9 @@ use zkvm_interface::{
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
#[allow(non_camel_case_types)]
pub struct MIDEN_TARGET;
#[derive(Clone, Serialize, Deserialize)]
pub struct MidenProgram {
pub program_bytes: Vec<u8>,
}
pub mod compiler;
pub mod error;
mod io;
#[derive(Serialize, Deserialize)]
struct MidenProofBundle {
@@ -44,11 +39,7 @@ pub struct EreMiden {
impl EreMiden {
pub fn new(program: MidenProgram, _resource: ProverResourceType) -> Result<Self, MidenError> {
let program = Program::read_from_bytes(&program.program_bytes)
.map_err(ExecuteError::ProgramDeserialization)
.map_err(MidenError::Execute)?;
Ok(Self { program })
Ok(Self { program: program.0 })
}
fn setup_host() -> Result<DefaultHost, MidenError> {
@@ -166,12 +157,15 @@ impl zkVM for EreMiden {
#[cfg(test)]
mod tests {
use super::*;
use crate::{
EreMiden,
compiler::{MidenAsm, MidenProgram},
};
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
fn load_miden_program(guest_name: &str) -> MidenProgram {
MIDEN_TARGET
MidenAsm
.compile(&testing_guest_directory("miden", guest_name))
.unwrap()
}

View File

@@ -9,13 +9,13 @@ license.workspace = true
bincode.workspace = true
serde.workspace = true
thiserror.workspace = true
toml.workspace = true
tracing.workspace = true
# Nexus dependencies
nexus-sdk.workspace = true
# Local dependencies
compile-utils.workspace = true
zkvm-interface.workspace = true
[dev-dependencies]

View File

@@ -0,0 +1,5 @@
mod rust_rv32i;
pub use rust_rv32i::RustRv32i;
pub type NexusProgram = Vec<u8>;

View File

@@ -0,0 +1,51 @@
use crate::{
compiler::NexusProgram,
error::{CompileError, NexusError},
};
use compile_utils::cargo_metadata;
use nexus_sdk::compile::{Compile, Compiler as NexusCompiler, cargo::CargoPackager};
use std::{fs, path::Path};
use zkvm_interface::Compiler;
/// Compiler for Rust guest program to RV32I architecture.
pub struct RustRv32i;
impl Compiler for RustRv32i {
type Error = NexusError;
type Program = NexusProgram;
fn compile(&self, guest_path: &Path) -> Result<Self::Program, Self::Error> {
// 1. Check guest path
if !guest_path.exists() {
return Err(CompileError::PathNotFound(guest_path.to_path_buf()))?;
}
std::env::set_current_dir(guest_path).map_err(|e| CompileError::Client(e.into()))?;
let metadata = cargo_metadata(guest_path).map_err(CompileError::CompileUtilError)?;
let package_name = &metadata.root_package().unwrap().name;
let mut prover_compiler = NexusCompiler::<CargoPackager>::new(package_name);
let elf_path = prover_compiler
.build()
.map_err(|e| CompileError::Client(e.into()))?;
let elf = fs::read(&elf_path).map_err(|_| CompileError::ElfNotFound(elf_path))?;
Ok(elf)
}
}
#[cfg(test)]
mod tests {
use crate::compiler::RustRv32i;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("nexus", "basic");
let elf = RustRv32i.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -18,20 +18,20 @@ pub enum NexusError {
#[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>),
/// 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),
#[error(transparent)]
CompileUtilError(#[from] compile_utils::CompileError),
}
#[derive(Debug, Error)]

View File

@@ -1,72 +1,33 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![allow(clippy::uninlined_format_args)]
use std::io::Read;
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::Stwo;
use nexus_sdk::{Local, Prover, Verifiable};
use crate::{
compiler::NexusProgram,
error::{NexusError, ProveError, VerifyError},
};
use nexus_sdk::{Local, Prover, Verifiable, stwo::seq::Stwo};
use serde::de::DeserializeOwned;
use std::{io::Read, time::Instant};
use tracing::info;
use zkvm_interface::{
Compiler, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType,
PublicValues, zkVM, zkVMError,
Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType, PublicValues,
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(&self, guest_path: &Path) -> Result<Self::Program, Self::Error> {
// 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 mod compiler;
pub mod error;
pub struct EreNexus {
program: <NEXUS_TARGET as Compiler>::Program,
program: NexusProgram,
}
impl EreNexus {
pub fn new(
program: <NEXUS_TARGET as Compiler>::Program,
_resource_type: ProverResourceType,
) -> Self {
pub fn new(program: NexusProgram, _resource_type: ProverResourceType) -> Self {
Self { program }
}
}
impl zkVM for EreNexus {
fn execute(
&self,
@@ -87,7 +48,7 @@ impl zkVM for EreNexus {
&self,
_inputs: &Input,
) -> Result<(PublicValues, Proof, zkvm_interface::ProgramProvingReport), zkVMError> {
let prover: Stwo<Local> = Stwo::new_from_file(&self.program.to_string_lossy().to_string())
let prover: Stwo<Local> = Stwo::new_from_bytes(&self.program)
.map_err(|e| NexusError::Prove(ProveError::Client(e.into())))
.map_err(zkVMError::from)?;
@@ -116,21 +77,20 @@ impl zkVM for EreNexus {
let proof: nexus_sdk::stwo::seq::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())
let prover: Stwo<Local> = Stwo::new_from_bytes(&self.program)
.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)?;
.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!");
@@ -153,31 +113,3 @@ impl zkVM for EreNexus {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{fs, sync::OnceLock};
use test_utils::host::testing_guest_directory;
static BASIC_PROGRAM: OnceLock<PathBuf> = OnceLock::new();
fn basic_program() -> PathBuf {
BASIC_PROGRAM
.get_or_init(|| {
NEXUS_TARGET
.compile(&testing_guest_directory("nexus", "basic"))
.unwrap()
})
.to_path_buf()
}
#[test]
fn test_compiler_impl() {
let elf_path = basic_program();
assert!(
fs::metadata(&elf_path).unwrap().len() != 0,
"ELF bytes should not be empty."
);
}
}

View File

@@ -1,13 +0,0 @@
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())
}

View File

@@ -8,7 +8,6 @@ license.workspace = true
[dependencies]
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
tracing.workspace = true
toml.workspace = true
# OpenVM dependencies

View File

@@ -1,116 +0,0 @@
use crate::OpenVMProgram;
use crate::error::CompileError;
use compile_utils::CargoBuildCmd;
use openvm_sdk::config::{AppConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP, SdkVmConfig};
use openvm_stark_sdk::config::FriParameters;
use std::fs;
use std::path::Path;
use tracing::info;
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
// Rust flags according to https://github.com/openvm-org/openvm/blob/v1.4.0/crates/toolchain/build/src/lib.rs#L291
const RUSTFLAGS: &[&str] = &[
// Replace atomic ops with nonatomic versions since the guest is single threaded.
"-C",
"passes=lower-atomic",
// Specify where to start loading the program in
// memory. The clang linker understands the same
// command line arguments as the GNU linker does; see
// https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html#SEC3
// for details.
"-C",
"link-arg=-Ttext=0x00200800",
// Apparently not having an entry point is only a linker warning(!), so
// error out in this case.
"-C",
"link-arg=--fatal-warnings",
"-C",
"panic=abort",
// https://docs.rs/getrandom/0.3.2/getrandom/index.html#opt-in-backends
"--cfg",
"getrandom_backend=\"custom\"",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
pub fn compile_openvm_program_stock_rust(
guest_directory: &Path,
toolchain: &String,
) -> Result<OpenVMProgram, CompileError> {
wrap_into_openvm_program(
compile_program_stock_rust(guest_directory, toolchain)?,
guest_directory,
)
}
fn compile_program_stock_rust(
guest_directory: &Path,
toolchain: &String,
) -> Result<Vec<u8>, CompileError> {
let elf = CargoBuildCmd::new()
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
}
fn wrap_into_openvm_program(
elf: Vec<u8>,
guest_directory: &Path,
) -> Result<OpenVMProgram, CompileError> {
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(CompileError::DeserializeConfigFailed)?
} 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(),
}
};
info!("Openvm program compiled OK - {} bytes", elf.len());
Ok(OpenVMProgram { elf, app_config })
}
#[cfg(test)]
mod tests {
use crate::compile_stock_rust::compile_openvm_program_stock_rust;
use test_utils::host::testing_guest_directory;
#[test]
fn test_stock_compiler_impl() {
let guest_directory = testing_guest_directory("openvm", "stock_nightly_no_std");
let result = compile_openvm_program_stock_rust(&guest_directory, &"nightly".to_string());
assert!(result.is_ok(), "Openvm guest program compilation failure.");
assert!(
!result.unwrap().elf.is_empty(),
"ELF bytes should not be empty."
);
}
}

View File

@@ -0,0 +1,56 @@
use crate::error::CompileError;
use openvm_sdk::config::{AppConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP, SdkVmConfig};
use openvm_stark_sdk::config::FriParameters;
use serde::{Deserialize, Serialize};
use std::{fs, path::Path};
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
#[derive(Clone, Serialize, Deserialize)]
pub struct OpenVMProgram {
pub elf: Vec<u8>,
pub app_config: AppConfig<SdkVmConfig>,
}
impl OpenVMProgram {
fn from_elf_and_app_config_path(
elf: Vec<u8>,
app_config_path: impl AsRef<Path>,
) -> Result<Self, CompileError> {
let app_config = if app_config_path.as_ref().exists() {
let toml = fs::read_to_string(app_config_path.as_ref()).map_err(|source| {
CompileError::ReadConfigFailed {
source,
path: app_config_path.as_ref().to_path_buf(),
}
})?;
toml::from_str(&toml).map_err(CompileError::DeserializeConfigFailed)?
} else {
// The default `AppConfig` copied from https://github.com/openvm-org/openvm/blob/v1.4.0/crates/cli/src/default.rs#L35.
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(Self { elf, app_config })
}
}

View File

@@ -0,0 +1,81 @@
use crate::{
OpenVMProgram,
error::{CompileError, OpenVMError},
};
use compile_utils::CargoBuildCmd;
use std::{env, path::Path};
use zkvm_interface::Compiler;
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
// Rust flags according to https://github.com/openvm-org/openvm/blob/v1.4.0/crates/toolchain/build/src/lib.rs#L291
const RUSTFLAGS: &[&str] = &[
// Replace atomic ops with nonatomic versions since the guest is single threaded.
"-C",
"passes=lower-atomic",
// Specify where to start loading the program in
// memory. The clang linker understands the same
// command line arguments as the GNU linker does; see
// https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html#SEC3
// for details.
"-C",
"link-arg=-Ttext=0x00200800",
// Apparently not having an entry point is only a linker warning(!), so
// error out in this case.
"-C",
"link-arg=--fatal-warnings",
"-C",
"panic=abort",
// https://docs.rs/getrandom/0.3.2/getrandom/index.html#opt-in-backends
"--cfg",
"getrandom_backend=\"custom\"",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
/// Compiler for Rust guest program to RV32IMA architecture.
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = OpenVMError;
type Program = OpenVMProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_RUST_TOOLCHAIN").unwrap_or_else(|_| "nightly".into());
let elf = CargoBuildCmd::new()
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)
.map_err(CompileError::CompileUtilError)?;
Ok(OpenVMProgram::from_elf_and_app_config_path(
elf,
guest_directory.join("openvm.toml"),
)?)
}
}
#[cfg(test)]
mod tests {
use crate::{EreOpenVM, compiler::RustRv32ima};
use test_utils::host::testing_guest_directory;
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("openvm", "stock_nightly_no_std");
let program = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute() {
let guest_directory = testing_guest_directory("openvm", "stock_nightly_no_std");
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&Input::new()).unwrap();
}
}

View File

@@ -0,0 +1,54 @@
use crate::{
compiler::OpenVMProgram,
error::{CompileError, OpenVMError},
};
use openvm_build::GuestOptions;
use std::{fs, path::Path};
use zkvm_interface::Compiler;
/// Compiler for Rust guest program to RV32IMA architecture, using customized
/// target `riscv32im-risc0-zkvm-elf`.
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = OpenVMError;
type Program = OpenVMProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
// Inlining `openvm_sdk::Sdk::build` in order to get raw elf bytes.
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))?,
Err(None) => return Err(CompileError::BuildSkipped)?,
};
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(OpenVMProgram::from_elf_and_app_config_path(
elf,
guest_directory.join("openvm.toml"),
)?)
}
}
#[cfg(test)]
mod tests {
use crate::compiler::RustRv32imaCustomized;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("openvm", "basic");
let program = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,114 +1,32 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use crate::compile_stock_rust::compile_openvm_program_stock_rust;
use crate::error::{CommonError, CompileError, ExecuteError, OpenVMError, ProveError, VerifyError};
use openvm_build::GuestOptions;
use crate::{
compiler::OpenVMProgram,
error::{CommonError, ExecuteError, OpenVMError, ProveError, VerifyError},
};
use openvm_circuit::arch::instructions::exe::VmExe;
use openvm_continuations::verifier::internal::types::VmStarkProof;
use openvm_sdk::{
CpuSdk, F, SC, StdIn,
codec::{Decode, Encode},
commit::AppExecutionCommit,
config::{AppConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP, SdkVmConfig},
config::{AppConfig, SdkVmConfig},
fs::read_object_from_file,
keygen::{AggProvingKey, AggVerifyingKey, AppProvingKey},
};
use openvm_stark_sdk::{config::FriParameters, openvm_stark_backend::p3_field::PrimeField32};
use openvm_stark_sdk::openvm_stark_backend::p3_field::PrimeField32;
use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{
env, fs,
io::Read,
path::{Path, PathBuf},
sync::Arc,
time::Instant,
};
use serde::de::DeserializeOwned;
use std::{env, io::Read, path::PathBuf, sync::Arc, time::Instant};
use zkvm_interface::{
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof,
ProverResourceType, PublicValues, zkVM, zkVMError,
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
mod compile_stock_rust;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod error;
#[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 = OpenVMProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "openvm".into());
match toolchain.as_str() {
"openvm" => Ok(compile_openvm_program(guest_directory)?),
_ => Ok(compile_openvm_program_stock_rust(
guest_directory,
&toolchain,
)?),
}
}
}
// Inlining `openvm_sdk::Sdk::build` in order to get raw elf bytes.
fn compile_openvm_program(guest_directory: &Path) -> Result<OpenVMProgram, OpenVMError> {
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))?,
Err(None) => return Err(CompileError::BuildSkipped)?,
};
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(),
})?;
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(CompileError::DeserializeConfigFailed)?
} 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 mod compiler;
pub mod error;
pub struct EreOpenVM {
app_config: AppConfig<SdkVmConfig>,
@@ -299,37 +217,34 @@ fn extract_public_values(user_public_values: &[F]) -> Result<Vec<u8>, CommonErro
.ok_or(CommonError::InvalidPublicValue)
}
pub fn agg_pk_path() -> PathBuf {
fn agg_pk_path() -> PathBuf {
PathBuf::from(std::env::var("HOME").expect("env `$HOME` should be set"))
.join(".openvm/agg_stark.pk")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compile_stock_rust::compile_openvm_program_stock_rust;
use crate::{
EreOpenVM,
compiler::{OpenVMProgram, RustRv32imaCustomized},
};
use std::sync::OnceLock;
use test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
};
use zkvm_interface::{Compiler, ProverResourceType, zkVM};
fn basic_program() -> OpenVMProgram {
static PROGRAM: OnceLock<OpenVMProgram> = OnceLock::new();
PROGRAM
.get_or_init(|| {
OPENVM_TARGET
RustRv32imaCustomized
.compile(&testing_guest_directory("openvm", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_compiler_impl() {
let program = basic_program();
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute() {
let program = basic_program();
@@ -339,17 +254,6 @@ mod tests {
run_zkvm_execute(&zkvm, &io);
}
#[test]
fn test_execute_nightly() {
let guest_directory = testing_guest_directory("openvm", "stock_nightly_no_std");
let program =
compile_openvm_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap();
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
let result = zkvm.execute(&BasicProgramIo::empty());
assert!(result.is_ok(), "Openvm execution failure");
}
#[test]
fn test_execute_invalid_inputs() {
let program = basic_program();

View File

@@ -1,65 +0,0 @@
use crate::error::CompileError;
use compile_utils::CargoBuildCmd;
use std::path::Path;
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
// According to https://github.com/brevis-network/pico/blob/v1.1.7/sdk/cli/src/build/build.rs#L104
const RUSTFLAGS: &[&str] = &[
// Replace atomic ops with nonatomic versions since the guest is single threaded.
"-C",
"passes=lower-atomic",
// Specify where to start loading the program in
// memory. The clang linker understands the same
// command line arguments as the GNU linker does; see
// https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html#SEC3
// for details.
"-C",
"link-arg=-Ttext=0x00200800",
// Apparently not having an entry point is only a linker warning(!), so
// error out in this case.
"-C",
"link-arg=--fatal-warnings",
"-C",
"panic=abort",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
pub fn compile_pico_program_stock_rust(
guest_directory: &Path,
toolchain: &String,
) -> Result<Vec<u8>, CompileError> {
compile_program_stock_rust(guest_directory, toolchain)
}
fn compile_program_stock_rust(
guest_directory: &Path,
toolchain: &String,
) -> Result<Vec<u8>, CompileError> {
let elf = CargoBuildCmd::new()
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
}
#[cfg(test)]
mod tests {
use crate::compile_stock_rust::compile_pico_program_stock_rust;
use test_utils::host::testing_guest_directory;
#[test]
fn test_stock_compiler_impl() {
let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std");
let result = compile_pico_program_stock_rust(&guest_directory, &"nightly".to_string());
assert!(result.is_ok(), "Pico guest program compilation failure.");
assert!(
!result.unwrap().is_empty(),
"ELF bytes should not be empty."
);
}
}

View File

@@ -0,0 +1,7 @@
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
pub type PicoProgram = Vec<u8>;

View File

@@ -0,0 +1,75 @@
use crate::{
compiler::PicoProgram,
error::{CompileError, PicoError},
};
use compile_utils::CargoBuildCmd;
use std::{env, path::Path};
use zkvm_interface::Compiler;
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
// According to https://github.com/brevis-network/pico/blob/v1.1.7/sdk/cli/src/build/build.rs#L104
const RUSTFLAGS: &[&str] = &[
// Replace atomic ops with nonatomic versions since the guest is single threaded.
"-C",
"passes=lower-atomic",
// Specify where to start loading the program in
// memory. The clang linker understands the same
// command line arguments as the GNU linker does; see
// https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html#SEC3
// for details.
"-C",
"link-arg=-Ttext=0x00200800",
// Apparently not having an entry point is only a linker warning(!), so
// error out in this case.
"-C",
"link-arg=--fatal-warnings",
"-C",
"panic=abort",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
/// Compiler for Rust guest program to RV32IMA architecture.
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = PicoError;
type Program = PicoProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_RUST_TOOLCHAIN").unwrap_or_else(|_| "nightly".into());
let elf = CargoBuildCmd::new()
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)
.map_err(CompileError::CompileUtilError)?;
Ok(elf)
}
}
#[cfg(test)]
mod tests {
use crate::{ErePico, compiler::RustRv32ima};
use test_utils::host::testing_guest_directory;
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std");
let elf = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute() {
let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std");
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = ErePico::new(program, ProverResourceType::Cpu);
zkvm.execute(&Input::new()).unwrap();
}
}

View File

@@ -0,0 +1,65 @@
use crate::error::{CompileError, PicoError};
use std::{fs, path::Path, process::Command};
use tempfile::tempdir;
use zkvm_interface::Compiler;
/// Compiler for Rust guest program to RV32IMA architecture, using customized
/// Rust toolchain of Pico.
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = PicoError;
type Program = Vec<u8>;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let tempdir = tempdir().map_err(CompileError::Tempdir)?;
// 1. Check guest path
if !guest_directory.exists() {
return Err(CompileError::PathNotFound(guest_directory.to_path_buf()))?;
}
// 2. Run `cargo pico build`
let status = Command::new("cargo")
.current_dir(guest_directory)
.env("RUST_LOG", "info")
.args(["pico", "build", "--output-directory"])
.arg(tempdir.path())
.status()
.map_err(CompileError::CargoPicoBuild)?;
if !status.success() {
return Err(CompileError::CargoPicoBuildFailed { status })?;
}
// 3. Locate the ELF file
let elf_path = tempdir.path().join("riscv32im-pico-zkvm-elf");
if !elf_path.exists() {
return Err(CompileError::ElfNotFound(elf_path))?;
}
// 4. Read the ELF file
let elf_bytes = fs::read(&elf_path).map_err(|e| CompileError::ReadElf {
path: elf_path,
source: e,
})?;
Ok(elf_bytes)
}
}
#[cfg(test)]
mod tests {
use crate::compiler::RustRv32imaCustomized;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("pico", "basic");
let elf = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -2,88 +2,28 @@
use crate::{
client::{MetaProof, ProverClient},
compile_stock_rust::compile_pico_program_stock_rust,
error::{CompileError, PicoError, ProveError, VerifyError},
compiler::PicoProgram,
error::{PicoError, ProveError, VerifyError},
};
use pico_p3_field::PrimeField32;
use pico_vm::{configs::stark_config::KoalaBearPoseidon2, emulator::stdin::EmulatorStdinBuilder};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use sha2::{Digest, Sha256};
use std::{
env, fs,
env,
io::Read,
path::Path,
process::Command,
time::{self, Instant},
};
use tempfile::tempdir;
use zkvm_interface::{
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof,
ProverResourceType, PublicValues, zkVM, zkVMError,
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod client;
mod compile_stock_rust;
mod error;
#[allow(non_camel_case_types)]
pub struct PICO_TARGET;
impl Compiler for PICO_TARGET {
type Error = PicoError;
type Program = Vec<u8>;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "pico".into());
match toolchain.as_str() {
"pico" => Ok(compile_pico_program(guest_directory)?),
_ => Ok(compile_pico_program_stock_rust(
guest_directory,
&toolchain,
)?),
}
}
}
fn compile_pico_program(guest_directory: &Path) -> Result<Vec<u8>, CompileError> {
let tempdir = tempdir().map_err(CompileError::Tempdir)?;
// 1. Check guest path
if !guest_directory.exists() {
return Err(CompileError::PathNotFound(guest_directory.to_path_buf()));
}
// 2. Run `cargo pico build`
let status = Command::new("cargo")
.current_dir(guest_directory)
.env("RUST_LOG", "info")
.args(["pico", "build", "--output-directory"])
.arg(tempdir.path())
.status()
.map_err(CompileError::CargoPicoBuild)?;
if !status.success() {
return Err(CompileError::CargoPicoBuildFailed { status });
}
// 3. Locate the ELF file
let elf_path = tempdir.path().join("riscv32im-pico-zkvm-elf");
if !elf_path.exists() {
return Err(CompileError::ElfNotFound(elf_path));
}
// 4. Read the ELF file
let elf_bytes = fs::read(&elf_path).map_err(|e| CompileError::ReadElf {
path: elf_path,
source: e,
})?;
Ok(elf_bytes)
}
pub mod client;
pub mod compiler;
pub mod error;
#[derive(Serialize, Deserialize)]
pub struct PicoProofWithPublicValues {
@@ -92,11 +32,11 @@ pub struct PicoProofWithPublicValues {
}
pub struct ErePico {
program: <PICO_TARGET as Compiler>::Program,
program: PicoProgram,
}
impl ErePico {
pub fn new(program: <PICO_TARGET as Compiler>::Program, resource: ProverResourceType) -> Self {
pub fn new(program: PicoProgram, resource: ProverResourceType) -> Self {
if !matches!(resource, ProverResourceType::Cpu) {
panic!("Network or GPU proving not yet implemented for Pico. Use CPU resource type.");
}
@@ -224,30 +164,28 @@ fn extract_public_values_sha256_digest(proof: &MetaProof) -> Result<[u8; 32], Ve
#[cfg(test)]
mod tests {
use super::*;
use crate::{
ErePico,
compiler::{PicoProgram, RustRv32imaCustomized},
};
use std::{panic, sync::OnceLock};
use test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
};
use zkvm_interface::{Compiler, ProverResourceType, zkVM};
static BASIC_PROGRAM: OnceLock<Vec<u8>> = OnceLock::new();
static BASIC_PROGRAM: OnceLock<PicoProgram> = OnceLock::new();
fn basic_program() -> Vec<u8> {
fn basic_program() -> PicoProgram {
BASIC_PROGRAM
.get_or_init(|| {
PICO_TARGET
RustRv32imaCustomized
.compile(&testing_guest_directory("pico", "basic"))
.unwrap()
})
.clone()
}
#[test]
fn test_compiler_impl() {
let elf_bytes = basic_program();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute() {
let program = basic_program();
@@ -257,17 +195,6 @@ mod tests {
run_zkvm_execute(&zkvm, &io);
}
#[test]
fn test_execute_nightly() {
let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std");
let program =
compile_pico_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap();
let zkvm = ErePico::new(program, ProverResourceType::Cpu);
let result = zkvm.execute(&BasicProgramIo::empty());
assert!(result.is_ok(), "Pico execution failure");
}
#[test]
fn test_execute_invalid_inputs() {
let program = basic_program();

View File

@@ -1,54 +0,0 @@
use crate::error::CompileError;
use compile_utils::cargo_metadata;
use risc0_build::GuestOptions;
use risc0_zkvm::Digest;
use serde::{Deserialize, Serialize};
use std::path::Path;
use tracing::info;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Risc0Program {
pub(crate) elf: Vec<u8>,
pub(crate) image_id: Digest,
}
pub fn compile_risc0_program(guest_directory: &Path) -> Result<Risc0Program, CompileError> {
info!("Compiling Risc0 program at {}", guest_directory.display());
let metadata = cargo_metadata(guest_directory)?;
let package = metadata.root_package().unwrap();
// Use `risc0_build::build_package` to build package instead of calling
// `cargo-risczero build` for the `unstable` features.
let guest =
risc0_build::build_package(package, &metadata.target_directory, GuestOptions::default())
.map_err(|source| CompileError::BuildFailure {
source,
crate_path: guest_directory.to_path_buf(),
})?
.into_iter()
.next()
.ok_or(CompileError::Risc0BuildMissingGuest)?;
let elf = guest.elf.to_vec();
let image_id = guest.image_id;
info!("Risc0 program compiled OK - {} bytes", elf.len());
info!("Image ID - {image_id}");
Ok(Risc0Program { elf, image_id })
}
#[cfg(test)]
mod tests {
use crate::RV32_IM_RISC0_ZKVM_ELF;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compiler_impl() {
let guest_directory = testing_guest_directory("risc0", "basic");
let program = RV32_IM_RISC0_ZKVM_ELF.compile(&guest_directory).unwrap();
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,78 +0,0 @@
use crate::compile::Risc0Program;
use crate::error::CompileError;
use compile_utils::CargoBuildCmd;
use risc0_binfmt::ProgramBinary;
use std::path::Path;
use tracing::info;
// TODO: Make this with `zkos` package building to avoid binary file storing in repo.
// File taken from https://github.com/risc0/risc0/blob/v3.0.3/risc0/zkos/v1compat/elfs/v1compat.elf
const V1COMPAT_ELF: &[u8] = include_bytes!("kernel_elf/v1compat.elf");
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
// Rust flags according to https://github.com/risc0/risc0/blob/v3.0.3/risc0/build/src/lib.rs#L455
const RUSTFLAGS: &[&str] = &[
"-C",
"passes=lower-atomic", // Only for rustc > 1.81
"-C",
// Start of the code section
"link-arg=-Ttext=0x00200800",
"-C",
"link-arg=--fatal-warnings",
"-C",
"panic=abort",
"--cfg",
"getrandom_backend=\"custom\"",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
pub fn compile_risc0_program_stock_rust(
guest_directory: &Path,
toolchain: &String,
) -> Result<Risc0Program, CompileError> {
wrap_into_risc0_program(compile_program_stock_rust(guest_directory, toolchain)?)
}
fn compile_program_stock_rust(
guest_directory: &Path,
toolchain: &String,
) -> Result<Vec<u8>, CompileError> {
let elf = CargoBuildCmd::new()
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
}
fn wrap_into_risc0_program(elf: Vec<u8>) -> Result<Risc0Program, CompileError> {
let program = ProgramBinary::new(elf.as_slice(), V1COMPAT_ELF);
let image_id = program.compute_image_id()?;
info!("Risc0 program compiled OK - {} bytes", elf.len());
info!("Image ID - {image_id}");
Ok(Risc0Program {
elf: program.encode(),
image_id,
})
}
#[cfg(test)]
mod tests {
use crate::compile_stock_rust::compile_risc0_program_stock_rust;
use test_utils::host::testing_guest_directory;
#[test]
fn test_stock_compiler_impl() {
let guest_directory = testing_guest_directory("risc0", "stock_nightly_no_std");
let result = compile_risc0_program_stock_rust(&guest_directory, &"nightly".to_string());
assert!(result.is_ok(), "Risc0 guest program compilation failure.");
assert!(
!result.unwrap().elf.is_empty(),
"ELF bytes should not be empty."
);
}
}

View File

@@ -0,0 +1,14 @@
use risc0_zkvm::Digest;
use serde::{Deserialize, Serialize};
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Risc0Program {
pub(crate) elf: Vec<u8>,
pub(crate) image_id: Digest,
}

View File

@@ -0,0 +1,86 @@
use crate::compiler::Risc0Program;
use crate::error::{CompileError, Risc0Error};
use compile_utils::CargoBuildCmd;
use risc0_binfmt::ProgramBinary;
use std::env;
use std::path::Path;
use tracing::info;
use zkvm_interface::Compiler;
// TODO: Make this with `zkos` package building to avoid binary file storing in repo.
// File taken from https://github.com/risc0/risc0/blob/v3.0.3/risc0/zkos/v1compat/elfs/v1compat.elf
const V1COMPAT_ELF: &[u8] = include_bytes!("rust_rv32ima/v1compat.elf");
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
// Rust flags according to https://github.com/risc0/risc0/blob/v3.0.3/risc0/build/src/lib.rs#L455
const RUSTFLAGS: &[&str] = &[
"-C",
"passes=lower-atomic", // Only for rustc > 1.81
"-C",
// Start of the code section
"link-arg=-Ttext=0x00200800",
"-C",
"link-arg=--fatal-warnings",
"-C",
"panic=abort",
"--cfg",
"getrandom_backend=\"custom\"",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
/// Compiler for Rust guest program to RV32IMA architecture.
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = Risc0Error;
type Program = Risc0Program;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_RUST_TOOLCHAIN").unwrap_or_else(|_| "nightly".into());
let elf = CargoBuildCmd::new()
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)
.map_err(CompileError::CompileUtilError)?;
let program = ProgramBinary::new(elf.as_slice(), V1COMPAT_ELF);
let image_id = program
.compute_image_id()
.map_err(CompileError::ImageIDCalculationFailure)?;
info!("Risc0 program compiled OK - {} bytes", elf.len());
info!("Image ID - {image_id}");
Ok(Risc0Program {
elf: program.encode(),
image_id,
})
}
}
#[cfg(test)]
mod tests {
use crate::{EreRisc0, compiler::RustRv32ima};
use test_utils::host::testing_guest_directory;
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("risc0", "stock_nightly_no_std");
let program = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute() {
let guest_directory = testing_guest_directory("risc0", "stock_nightly_no_std");
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
zkvm.execute(&Input::new()).unwrap();
}
}

View File

@@ -0,0 +1,63 @@
use crate::{
compiler::Risc0Program,
error::{CompileError, Risc0Error},
};
use compile_utils::cargo_metadata;
use risc0_build::GuestOptions;
use std::path::Path;
use tracing::info;
use zkvm_interface::Compiler;
/// Compiler for Rust guest program to RV32IMA architecture, using customized
/// Rust toolchain of Risc0.
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = Risc0Error;
type Program = Risc0Program;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
info!("Compiling Risc0 program at {}", guest_directory.display());
let metadata = cargo_metadata(guest_directory).map_err(CompileError::CompileUtilError)?;
let package = metadata.root_package().unwrap();
// Use `risc0_build::build_package` to build package instead of calling
// `cargo-risczero build` for the `unstable` features.
let guest = risc0_build::build_package(
package,
&metadata.target_directory,
GuestOptions::default(),
)
.map_err(|source| CompileError::BuildFailure {
source,
crate_path: guest_directory.to_path_buf(),
})?
.into_iter()
.next()
.ok_or(CompileError::Risc0BuildMissingGuest)?;
let elf = guest.elf.to_vec();
let image_id = guest.image_id;
info!("Risc0 program compiled OK - {} bytes", elf.len());
info!("Image ID - {image_id}");
Ok(Risc0Program { elf, image_id })
}
}
#[cfg(test)]
mod tests {
use crate::compiler::RustRv32imaCustomized;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("risc0", "basic");
let program = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -18,7 +18,7 @@ pub enum CompileError {
#[error("`risc0_build::build_package` succeeded but failed to find guest")]
Risc0BuildMissingGuest,
#[error("ELF binary image calculation failure : {0}")]
ImageIDCalculationFailure(#[from] anyhow::Error),
ImageIDCalculationFailure(anyhow::Error),
#[error(transparent)]
CompileUtilError(#[from] compile_utils::CompileError),
}

View File

@@ -1,29 +1,22 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use crate::{
compile::{Risc0Program, compile_risc0_program},
compile_stock_rust::compile_risc0_program_stock_rust,
error::Risc0Error,
output::deserialize_from,
};
use crate::{compiler::Risc0Program, output::deserialize_from};
use borsh::{BorshDeserialize, BorshSerialize};
use risc0_zkvm::{
DEFAULT_MAX_PO2, DefaultProver, ExecutorEnv, ExecutorEnvBuilder, ExternalProver, InnerReceipt,
Journal, ProverOpts, Receipt, ReceiptClaim, SuccinctReceipt, default_executor, default_prover,
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{env, io::Read, ops::RangeInclusive, path::Path, rc::Rc, time::Instant};
use std::{env, io::Read, ops::RangeInclusive, rc::Rc, time::Instant};
use zkvm_interface::{
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof,
ProverResourceType, PublicValues, zkVM, zkVMError,
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod compile;
mod compile_stock_rust;
mod error;
pub mod compiler;
pub mod error;
mod output;
/// Default logarithmic segment size from [`DEFAULT_SEGMENT_LIMIT_PO2`].
@@ -52,26 +45,6 @@ const DEFAULT_KECCAK_PO2: usize = 17;
/// [`KECCAK_PO2_RANGE`]: https://github.com/risc0/risc0/blob/v3.0.3/risc0/circuit/keccak/src/lib.rs#L29.
const KECCAK_PO2_RANGE: RangeInclusive<usize> = 14..=18;
#[allow(non_camel_case_types)]
pub struct RV32_IM_RISC0_ZKVM_ELF;
impl Compiler for RV32_IM_RISC0_ZKVM_ELF {
type Error = Risc0Error;
type Program = Risc0Program;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "risc0".into());
match toolchain.as_str() {
"risc0" => Ok(compile_risc0_program(guest_directory)?),
_ => Ok(compile_risc0_program_stock_rust(
guest_directory,
&toolchain,
)?),
}
}
}
#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct Risc0ProofWithPublicValues {
/// The aggregated proof generated by the Risc0 zkVM.
@@ -100,17 +73,14 @@ impl From<Risc0ProofWithPublicValues> for Receipt {
}
pub struct EreRisc0 {
program: <RV32_IM_RISC0_ZKVM_ELF as Compiler>::Program,
program: Risc0Program,
resource: ProverResourceType,
segment_po2: usize,
keccak_po2: usize,
}
impl EreRisc0 {
pub fn new(
program: <RV32_IM_RISC0_ZKVM_ELF as Compiler>::Program,
resource: ProverResourceType,
) -> Result<Self, zkVMError> {
pub fn new(program: Risc0Program, resource: ProverResourceType) -> Result<Self, zkVMError> {
if matches!(resource, ProverResourceType::Network(_)) {
panic!(
"Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type."
@@ -267,18 +237,22 @@ fn serialize_inputs(env: &mut ExecutorEnvBuilder, inputs: &Input) -> Result<(),
#[cfg(test)]
mod tests {
use super::*;
use crate::{
EreRisc0,
compiler::{Risc0Program, RustRv32imaCustomized},
};
use std::sync::OnceLock;
use test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
};
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
static BASIC_PROGRAM: OnceLock<Risc0Program> = OnceLock::new();
fn basic_program() -> Risc0Program {
BASIC_PROGRAM
.get_or_init(|| {
RV32_IM_RISC0_ZKVM_ELF
RustRv32imaCustomized
.compile(&testing_guest_directory("risc0", "basic"))
.unwrap()
})
@@ -294,17 +268,6 @@ mod tests {
run_zkvm_execute(&zkvm, &io);
}
#[test]
fn test_execute_nightly() {
let guest_directory = testing_guest_directory("risc0", "stock_nightly_no_std");
let program =
compile_risc0_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap();
let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap();
let result = zkvm.execute(&BasicProgramIo::empty());
assert!(result.is_ok(), "Risc0 execution failure");
}
#[test]
fn test_execute_invalid_inputs() {
let program = basic_program();
@@ -344,7 +307,7 @@ mod tests {
#[test]
fn test_aligned_allocs() {
let program = RV32_IM_RISC0_ZKVM_ELF
let program = RustRv32imaCustomized
.compile(&testing_guest_directory("risc0", "allocs_alignment"))
.unwrap();

View File

@@ -1,96 +0,0 @@
use crate::compile_stock_rust::stock_rust_compile;
use crate::error::CompileError;
use std::{fs, path::Path, process::Command};
use tempfile::tempdir;
use tracing::info;
fn sp1_compile(guest_directory: &Path) -> Result<Vec<u8>, CompileError> {
info!("Compiling SP1 program at {}", guest_directory.display());
if !guest_directory.exists() || !guest_directory.is_dir() {
return Err(CompileError::InvalidProgramPath(
guest_directory.to_path_buf(),
));
}
let guest_manifest_path = guest_directory.join("Cargo.toml");
if !guest_manifest_path.exists() {
return Err(CompileError::CargoTomlMissing {
program_dir: guest_directory.to_path_buf(),
manifest_path: guest_manifest_path.clone(),
});
}
// ── build into a temp dir ─────────────────────────────────────────────
let temp_output_dir = tempdir()?;
let temp_output_dir_path = temp_output_dir.path();
info!(
"Running `cargo prove build` → dir: {}",
temp_output_dir_path.display(),
);
let status = Command::new("cargo")
.current_dir(guest_directory)
.args([
"prove",
"build",
"--output-directory",
temp_output_dir_path.to_str().unwrap(),
"--elf-name",
"guest.elf",
])
.status()
.map_err(|e| CompileError::CargoProveBuild {
cwd: guest_directory.to_path_buf(),
source: e,
})?;
if !status.success() {
return Err(CompileError::CargoProveBuildFailed {
status,
path: guest_directory.to_path_buf(),
});
}
let elf_path = temp_output_dir_path.join("guest.elf");
if !elf_path.exists() {
return Err(CompileError::ElfNotFound(elf_path));
}
let elf_bytes = fs::read(&elf_path).map_err(|e| CompileError::ReadFile {
path: elf_path,
source: e,
})?;
info!("SP1 program compiled OK - {} bytes", elf_bytes.len());
Ok(elf_bytes)
}
pub fn compile(guest_directory: &Path, toolchain: &String) -> Result<Vec<u8>, CompileError> {
match toolchain.as_str() {
"succinct" => sp1_compile(guest_directory),
_ => stock_rust_compile(guest_directory, toolchain),
}
}
#[cfg(test)]
mod tests {
use crate::RV32_IM_SUCCINCT_ZKVM_ELF;
use crate::compile::compile;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compiler_impl() {
let guest_directory = testing_guest_directory("sp1", "basic");
let elf_bytes = RV32_IM_SUCCINCT_ZKVM_ELF.compile(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_stock_compiler_impl() {
let guest_directory = testing_guest_directory("sp1", "stock_nightly_no_std");
let elf_bytes = compile(&guest_directory, &"nightly".to_string()).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,41 +0,0 @@
use crate::error::CompileError;
use compile_utils::CargoBuildCmd;
use std::path::Path;
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
const RUSTFLAGS: &[&str] = &[
"-C",
"passes=lower-atomic", // Only for rustc > 1.81
"-C",
// Start of the code section
"link-arg=-Ttext=0x00201000",
"-C",
// The lowest memory location that will be used when your program is loaded
"link-arg=--image-base=0x00200800",
"-C",
"panic=abort",
"--cfg",
"getrandom_backend=\"custom\"",
"-C",
"llvm-args=-misched-prera-direction=bottomup",
"-C",
"llvm-args=-misched-postra-direction=bottomup",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
pub fn stock_rust_compile(
guest_directory: &Path,
toolchain: &String,
) -> Result<Vec<u8>, CompileError> {
let elf = CargoBuildCmd::new()
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)?;
Ok(elf)
}

View File

@@ -0,0 +1,7 @@
mod rust_rv32ima;
mod rust_rv32ima_customized;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
pub type SP1Program = Vec<u8>;

View File

@@ -0,0 +1,75 @@
use crate::{
compiler::SP1Program,
error::{CompileError, SP1Error},
};
use compile_utils::CargoBuildCmd;
use std::{env, path::Path};
use zkvm_interface::Compiler;
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
const RUSTFLAGS: &[&str] = &[
"-C",
"passes=lower-atomic", // Only for rustc > 1.81
"-C",
// Start of the code section
"link-arg=-Ttext=0x00201000",
"-C",
// The lowest memory location that will be used when your program is loaded
"link-arg=--image-base=0x00200800",
"-C",
"panic=abort",
"--cfg",
"getrandom_backend=\"custom\"",
"-C",
"llvm-args=-misched-prera-direction=bottomup",
"-C",
"llvm-args=-misched-postra-direction=bottomup",
];
const CARGO_BUILD_OPTIONS: &[&str] = &[
// For bare metal we have to build core and alloc
"-Zbuild-std=core,alloc",
];
/// Compiler for Rust guest program to RV32IMA architecture.
pub struct RustRv32ima;
impl Compiler for RustRv32ima {
type Error = SP1Error;
type Program = SP1Program;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain = env::var("ERE_RUST_TOOLCHAIN").unwrap_or_else(|_| "nightly".into());
let elf = CargoBuildCmd::new()
.toolchain(toolchain)
.build_options(CARGO_BUILD_OPTIONS)
.rustflags(RUSTFLAGS)
.exec(guest_directory, TARGET_TRIPLE)
.map_err(CompileError::CompileUtilError)?;
Ok(elf)
}
}
#[cfg(test)]
mod tests {
use crate::{EreSP1, compiler::RustRv32ima};
use test_utils::host::testing_guest_directory;
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("sp1", "stock_nightly_no_std");
let elf = RustRv32ima.compile(&guest_directory).unwrap();
assert!(!elf.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_execute() {
let guest_directory = testing_guest_directory("sp1", "stock_nightly_no_std");
let program = RustRv32ima.compile(&guest_directory).unwrap();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
zkvm.execute(&Input::new()).unwrap();
}
}

View File

@@ -0,0 +1,95 @@
use crate::{
compiler::SP1Program,
error::{CompileError, SP1Error},
};
use std::{fs, path::Path, process::Command};
use tempfile::tempdir;
use tracing::info;
use zkvm_interface::Compiler;
/// Compiler for Rust guest program to RV32IMA architecture, using customized
/// Rust toolchain of Succinct.
pub struct RustRv32imaCustomized;
impl Compiler for RustRv32imaCustomized {
type Error = SP1Error;
type Program = SP1Program;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
info!("Compiling SP1 program at {}", guest_directory.display());
if !guest_directory.exists() || !guest_directory.is_dir() {
return Err(CompileError::InvalidProgramPath(
guest_directory.to_path_buf(),
))?;
}
let guest_manifest_path = guest_directory.join("Cargo.toml");
if !guest_manifest_path.exists() {
return Err(CompileError::CargoTomlMissing {
program_dir: guest_directory.to_path_buf(),
manifest_path: guest_manifest_path.clone(),
})?;
}
// ── build into a temp dir ─────────────────────────────────────────────
let temp_output_dir = tempdir().map_err(CompileError::TempDir)?;
let temp_output_dir_path = temp_output_dir.path();
info!(
"Running `cargo prove build` → dir: {}",
temp_output_dir_path.display(),
);
let status = Command::new("cargo")
.current_dir(guest_directory)
.args([
"prove",
"build",
"--output-directory",
temp_output_dir_path.to_str().unwrap(),
"--elf-name",
"guest.elf",
])
.status()
.map_err(|e| CompileError::CargoProveBuild {
cwd: guest_directory.to_path_buf(),
source: e,
})?;
if !status.success() {
return Err(CompileError::CargoProveBuildFailed {
status,
path: guest_directory.to_path_buf(),
})?;
}
let elf_path = temp_output_dir_path.join("guest.elf");
if !elf_path.exists() {
return Err(CompileError::ElfNotFound(elf_path))?;
}
let elf_bytes = fs::read(&elf_path).map_err(|e| CompileError::ReadFile {
path: elf_path,
source: e,
})?;
info!("SP1 program compiled OK - {} bytes", elf_bytes.len());
Ok(elf_bytes)
}
}
#[cfg(test)]
mod tests {
use crate::compiler::RustRv32imaCustomized;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("sp1", "basic");
let elf_bytes = RustRv32imaCustomized.compile(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -1,26 +1,27 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use std::{io::Read, path::Path, time::Instant};
use crate::{
compiler::SP1Program,
error::{ExecuteError, ProveError, SP1Error, VerifyError},
};
use serde::de::DeserializeOwned;
use sp1_sdk::{
CpuProver, CudaProver, NetworkProver, Prover, ProverClient, SP1ProofWithPublicValues,
SP1ProvingKey, SP1Stdin, SP1VerifyingKey,
};
use std::{io::Read, time::Instant};
use tracing::info;
use zkvm_interface::{
Compiler, Input, InputItem, NetworkProverConfig, ProgramExecutionReport, ProgramProvingReport,
Proof, ProverResourceType, PublicValues, zkVM, zkVMError,
Input, InputItem, NetworkProverConfig, ProgramExecutionReport, ProgramProvingReport, Proof,
ProverResourceType, PublicValues, zkVM, zkVMError,
};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod compile;
mod compile_stock_rust;
mod error;
use error::{ExecuteError, ProveError, SP1Error, VerifyError};
pub mod compiler;
pub mod error;
#[allow(clippy::large_enum_variant)]
enum ProverType {
Cpu(CpuProver),
Gpu(CudaProver),
@@ -28,10 +29,7 @@ enum ProverType {
}
impl ProverType {
fn setup(
&self,
program: &<RV32_IM_SUCCINCT_ZKVM_ELF as Compiler>::Program,
) -> (SP1ProvingKey, SP1VerifyingKey) {
fn setup(&self, program: &SP1Program) -> (SP1ProvingKey, SP1VerifyingKey) {
match self {
ProverType::Cpu(cpu_prover) => cpu_prover.setup(program),
ProverType::Gpu(cuda_prover) => cuda_prover.setup(program),
@@ -41,7 +39,7 @@ impl ProverType {
fn execute(
&self,
program: &<RV32_IM_SUCCINCT_ZKVM_ELF as Compiler>::Program,
program: &SP1Program,
input: &SP1Stdin,
) -> Result<(sp1_sdk::SP1PublicValues, sp1_sdk::ExecutionReport), SP1Error> {
let cpu_executor_builder = match self {
@@ -83,10 +81,8 @@ impl ProverType {
}
}
#[allow(non_camel_case_types)]
pub struct RV32_IM_SUCCINCT_ZKVM_ELF;
pub struct EreSP1 {
program: <RV32_IM_SUCCINCT_ZKVM_ELF as Compiler>::Program,
program: SP1Program,
/// Proving key
pk: SP1ProvingKey,
/// Verification key
@@ -103,18 +99,6 @@ pub struct EreSP1 {
// For more context see: https://github.com/eth-act/zkevm-benchmark-workload/issues/54
}
impl Compiler for RV32_IM_SUCCINCT_ZKVM_ELF {
type Error = SP1Error;
type Program = Vec<u8>;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let toolchain =
std::env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "succinct".into());
compile::compile(guest_directory, &toolchain).map_err(SP1Error::from)
}
}
impl EreSP1 {
fn create_network_prover(config: &NetworkProverConfig) -> NetworkProver {
let mut builder = ProverClient::builder().network();
@@ -148,10 +132,7 @@ impl EreSP1 {
}
}
pub fn new(
program: <RV32_IM_SUCCINCT_ZKVM_ELF as Compiler>::Program,
resource: ProverResourceType,
) -> Self {
pub fn new(program: SP1Program, resource: ProverResourceType) -> Self {
let (pk, vk) = Self::create_client(&resource).setup(&program);
Self {
@@ -249,19 +230,19 @@ fn serialize_inputs(stdin: &mut SP1Stdin, inputs: &Input) {
#[cfg(test)]
mod tests {
use super::*;
use crate::compile::compile;
use crate::{EreSP1, compiler::RustRv32imaCustomized};
use std::{panic, sync::OnceLock};
use test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
};
use zkvm_interface::{Compiler, NetworkProverConfig, ProverResourceType, zkVM};
static BASIC_PROGRAM: OnceLock<Vec<u8>> = OnceLock::new();
fn basic_program() -> Vec<u8> {
BASIC_PROGRAM
.get_or_init(|| {
RV32_IM_SUCCINCT_ZKVM_ELF
RustRv32imaCustomized
.compile(&testing_guest_directory("sp1", "basic"))
.unwrap()
})
@@ -277,15 +258,6 @@ mod tests {
run_zkvm_execute(&zkvm, &io);
}
#[test]
fn test_execute_nightly() {
let guest_directory = testing_guest_directory("sp1", "stock_nightly_no_std");
let program = compile(&guest_directory, &"nightly".to_string()).unwrap();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
zkvm.execute(&Input::new()).unwrap();
}
#[test]
fn test_execute_invalid_inputs() {
let program = basic_program();

View File

@@ -7,7 +7,6 @@ license.workspace = true
[dependencies]
bincode.workspace = true
cargo_metadata.workspace = true
serde.workspace = true
thiserror.workspace = true
tracing.workspace = true
@@ -16,6 +15,7 @@ tracing.workspace = true
zkm-sdk = { workspace = true }
# Local dependencies
compile-utils.workspace = true
zkvm-interface = { workspace = true }
[dev-dependencies]

View File

@@ -0,0 +1,5 @@
mod rust_mips32r2_customized;
pub use rust_mips32r2_customized::RustMips32r2Customized;
pub type ZirenProgram = Vec<u8>;

View File

@@ -0,0 +1,99 @@
use crate::{
compiler::ZirenProgram,
error::{CompileError, ZirenError},
};
use compile_utils::cargo_metadata;
use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use zkvm_interface::Compiler;
const ZKM_TOOLCHAIN: &str = "zkm";
/// Compiler for Rust guest program to MIPS32R2 architecture, using customized
/// Rust toolchain of ZKM.
pub struct RustMips32r2Customized;
impl Compiler for RustMips32r2Customized {
type Error = ZirenError;
type Program = ZirenProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let metadata = cargo_metadata(guest_directory).map_err(CompileError::CompileUtilError)?;
let package = metadata.root_package().unwrap();
let rustc = {
let output = Command::new("rustc")
.env("RUSTUP_TOOLCHAIN", ZKM_TOOLCHAIN)
.args(["--print", "sysroot"])
.output()
.map_err(CompileError::RustcSysrootFailed)?;
if !output.status.success() {
return Err(CompileError::RustcSysrootExitNonZero {
status: output.status,
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
})?;
}
PathBuf::from(String::from_utf8_lossy(&output.stdout).trim())
.join("bin")
.join("rustc")
};
// Use `cargo ziren build` instead of using crate `zkm-build`, because
// it exits if the underlying `cargo build` fails, and there is no way
// to recover.
let output = Command::new("cargo")
.current_dir(guest_directory)
.env("RUSTC", rustc)
.env("ZIREN_ZKM_CC", "mipsel-zkm-zkvm-elf-gcc")
.args(["ziren", "build"])
.output()
.map_err(CompileError::CargoZirenBuildFailed)?;
if !output.status.success() {
return Err(CompileError::CargoZirenBuildExitNonZero {
status: output.status,
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
})?;
}
let elf_path = String::from_utf8_lossy(&output.stdout)
.lines()
.find_map(|line| {
let line = line.strip_prefix("cargo:rustc-env=ZKM_ELF_")?;
let (package_name, elf_path) = line.split_once("=")?;
(package_name == package.name).then(|| PathBuf::from(elf_path))
})
.ok_or_else(|| CompileError::GuestNotFound {
name: package.name.clone(),
})?;
let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadFile {
path: elf_path,
source,
})?;
Ok(elf)
}
}
#[cfg(test)]
mod tests {
use crate::compiler::RustMips32r2Customized;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("ziren", "basic");
let elf_bytes = RustMips32r2Customized.compile(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -26,10 +26,6 @@ pub enum ZirenError {
#[derive(Debug, Error)]
pub enum CompileError {
#[error("`cargo metadata` failed: {0}")]
MetadataCommand(#[from] cargo_metadata::Error),
#[error("Failed to find root package")]
MissingRootPackage,
#[error("`RUSTUP_TOOLCHAIN=zkm rustc --print sysroot` failed to execute: {0}")]
RustcSysrootFailed(#[source] io::Error),
#[error(
@@ -58,6 +54,8 @@ pub enum CompileError {
#[source]
source: std::io::Error,
},
#[error(transparent)]
CompileUtilError(#[from] compile_utils::CompileError),
}
#[derive(Debug, Error)]

View File

@@ -1,114 +1,34 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use crate::error::{CompileError, ExecuteError, ProveError, VerifyError, ZirenError};
use cargo_metadata::MetadataCommand;
use serde::de::DeserializeOwned;
use std::{
fs,
io::Read,
path::{Path, PathBuf},
process::Command,
time::Instant,
use crate::{
compiler::ZirenProgram,
error::{ExecuteError, ProveError, VerifyError, ZirenError},
};
use serde::de::DeserializeOwned;
use std::{io::Read, time::Instant};
use tracing::info;
use zkm_sdk::{
CpuProver, Prover, ZKMProofKind, ZKMProofWithPublicValues, ZKMProvingKey, ZKMStdin,
ZKMVerifyingKey,
};
use zkvm_interface::{
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof,
ProverResourceType, PublicValues, zkVM, zkVMError,
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod error;
const ZKM_TOOLCHAIN: &str = "zkm";
#[allow(non_camel_case_types)]
pub struct MIPS32R2_ZKM_ZKVM_ELF;
impl Compiler for MIPS32R2_ZKM_ZKVM_ELF {
type Error = CompileError;
type Program = Vec<u8>;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let metadata = MetadataCommand::new().current_dir(guest_directory).exec()?;
let package = metadata
.root_package()
.ok_or(CompileError::MissingRootPackage)?;
let rustc = {
let output = Command::new("rustc")
.env("RUSTUP_TOOLCHAIN", ZKM_TOOLCHAIN)
.args(["--print", "sysroot"])
.output()
.map_err(CompileError::RustcSysrootFailed)?;
if !output.status.success() {
return Err(CompileError::RustcSysrootExitNonZero {
status: output.status,
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
});
}
PathBuf::from(String::from_utf8_lossy(&output.stdout).trim())
.join("bin")
.join("rustc")
};
// Use `cargo ziren build` instead of using crate `zkm-build`, because
// it exits if the underlying `cargo build` fails, and there is no way
// to recover.
let output = Command::new("cargo")
.current_dir(guest_directory)
.env("RUSTC", rustc)
.env("ZIREN_ZKM_CC", "mipsel-zkm-zkvm-elf-gcc")
.args(["ziren", "build"])
.output()
.map_err(CompileError::CargoZirenBuildFailed)?;
if !output.status.success() {
return Err(CompileError::CargoZirenBuildExitNonZero {
status: output.status,
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
});
}
let elf_path = String::from_utf8_lossy(&output.stdout)
.lines()
.find_map(|line| {
let line = line.strip_prefix("cargo:rustc-env=ZKM_ELF_")?;
let (package_name, elf_path) = line.split_once("=")?;
(package_name == package.name).then(|| PathBuf::from(elf_path))
})
.ok_or_else(|| CompileError::GuestNotFound {
name: package.name.clone(),
})?;
let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadFile {
path: elf_path,
source,
})?;
Ok(elf)
}
}
pub mod compiler;
pub mod error;
pub struct EreZiren {
program: <MIPS32R2_ZKM_ZKVM_ELF as Compiler>::Program,
program: ZirenProgram,
pk: ZKMProvingKey,
vk: ZKMVerifyingKey,
}
impl EreZiren {
pub fn new(
program: <MIPS32R2_ZKM_ZKVM_ELF as Compiler>::Program,
resource: ProverResourceType,
) -> Self {
pub fn new(program: ZirenProgram, resource: ProverResourceType) -> Self {
if matches!(
resource,
ProverResourceType::Gpu | ProverResourceType::Network(_)
@@ -212,18 +132,19 @@ fn serialize_inputs(stdin: &mut ZKMStdin, inputs: &Input) {
#[cfg(test)]
mod tests {
use super::*;
use crate::{EreZiren, compiler::RustMips32r2Customized};
use std::{panic, sync::OnceLock};
use test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
};
use zkvm_interface::{Compiler, ProverResourceType, zkVM};
static BASIC_PROGRAM: OnceLock<Vec<u8>> = OnceLock::new();
fn basic_program() -> Vec<u8> {
BASIC_PROGRAM
.get_or_init(|| {
MIPS32R2_ZKM_ZKVM_ELF
RustMips32r2Customized
.compile(&testing_guest_directory("ziren", "basic"))
.unwrap()
})

View File

@@ -13,10 +13,10 @@ serde = { workspace = true, features = ["derive"] }
strum = { workspace = true, features = ["derive"] }
tempfile.workspace = true
thiserror.workspace = true
toml.workspace = true
tracing.workspace = true
# Local dependencies
compile-utils.workspace = true
zkvm-interface.workspace = true
[dev-dependencies]

View File

@@ -1,137 +0,0 @@
use crate::error::ZiskError;
use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use toml::Value as TomlValue;
use tracing::info;
const ZISK_TOOLCHAIN: &str = "zisk";
const ZISK_TARGET: &str = "riscv64ima-zisk-zkvm-elf";
/// Compile the guest crate and return raw ELF bytes.
pub fn compile_zisk_program(program_crate_path: &Path) -> Result<Vec<u8>, ZiskError> {
info!("Compiling ZisK program at {}", program_crate_path.display());
if !program_crate_path.exists() || !program_crate_path.is_dir() {
return Err(ZiskError::InvalidProgramPath(
program_crate_path.to_path_buf(),
));
}
let guest_manifest_path = program_crate_path.join("Cargo.toml");
if !guest_manifest_path.exists() {
return Err(ZiskError::CargoTomlMissing {
program_dir: program_crate_path.to_path_buf(),
manifest_path: guest_manifest_path.clone(),
});
}
// ── read + parse Cargo.toml ───────────────────────────────────────────
let manifest_content =
fs::read_to_string(&guest_manifest_path).map_err(|e| ZiskError::ReadFile {
path: guest_manifest_path.clone(),
source: e,
})?;
let manifest_toml: TomlValue =
manifest_content
.parse::<TomlValue>()
.map_err(|e| ZiskError::ParseCargoToml {
path: guest_manifest_path.clone(),
source: e,
})?;
let program_name = manifest_toml
.get("package")
.and_then(|p| p.get("name"))
.and_then(|n| n.as_str())
.ok_or_else(|| ZiskError::MissingPackageName {
path: guest_manifest_path.clone(),
})?;
info!("Parsed program name: {program_name}");
// ── build ─────────────────────────────────────────────────────────────
// Get the path to ZisK toolchain's `rustc` so we could set the env
// `RUSTC=...` for `cargo` instead of using `cargo +zisk ...`.
let zisk_rustc = {
let output = Command::new("rustc")
.env("RUSTUP_TOOLCHAIN", ZISK_TOOLCHAIN)
.arg("--print")
.arg("sysroot")
.output()
.map_err(ZiskError::RustcSysroot)?;
PathBuf::from(String::from_utf8_lossy(&output.stdout).trim())
.join("bin")
.join("rustc")
};
let status = Command::new("cargo")
.current_dir(program_crate_path)
.env("RUSTC", zisk_rustc)
.args(["build", "--release", "--target", ZISK_TARGET])
.status()
.map_err(|e| ZiskError::CargoBuild {
cwd: program_crate_path.to_path_buf(),
source: e,
})?;
if !status.success() {
return Err(ZiskError::CargoBuildFailed {
status,
path: program_crate_path.to_path_buf(),
});
}
// Get the workspace directory.
let program_workspace_path = {
let output = Command::new("cargo")
.current_dir(program_crate_path)
.arg("locate-project")
.arg("--workspace")
.arg("--message-format=plain")
.output()
.map_err(ZiskError::CargoLocateProject)?;
PathBuf::from(
String::from_utf8_lossy(&output.stdout)
.trim()
.strip_suffix("Cargo.toml")
.expect("location to be path to Cargo.toml"),
)
};
let elf_path = program_workspace_path
.join("target")
.join("riscv64ima-zisk-zkvm-elf")
.join("release")
.join(program_name);
let elf_bytes = fs::read(&elf_path).map_err(|e| ZiskError::ReadFile {
path: elf_path.clone(),
source: e,
})?;
Ok(elf_bytes)
}
#[cfg(test)]
mod tests {
use crate::{RV64_IMA_ZISK_ZKVM_ELF, compile::compile_zisk_program};
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("zisk", "basic");
let elf_bytes = compile_zisk_program(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
#[test]
fn test_compiler_impl() {
let guest_directory = testing_guest_directory("zisk", "basic");
let elf_bytes = RV64_IMA_ZISK_ZKVM_ELF.compile(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -0,0 +1,5 @@
mod rust_rv64ima_customized;
pub use rust_rv64ima_customized::RustRv64imaCustomized;
pub type ZiskProgram = Vec<u8>;

View File

@@ -0,0 +1,106 @@
use crate::error::ZiskError;
use compile_utils::cargo_metadata;
use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use tracing::info;
use zkvm_interface::Compiler;
const ZISK_TOOLCHAIN: &str = "zisk";
const ZISK_TARGET: &str = "riscv64ima-zisk-zkvm-elf";
/// Compiler for Rust guest program to RV64IMA architecture, using customized
/// Rust toolchain of ZisK.
pub struct RustRv64imaCustomized;
impl Compiler for RustRv64imaCustomized {
type Error = ZiskError;
type Program = Vec<u8>;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
info!("Compiling ZisK program at {}", guest_directory.display());
let metadata = cargo_metadata(guest_directory)?;
let package_name = &metadata.root_package().unwrap().name;
info!("Parsed program name: {package_name}");
// ── build ─────────────────────────────────────────────────────────────
// Get the path to ZisK toolchain's `rustc` so we could set the env
// `RUSTC=...` for `cargo` instead of using `cargo +zisk ...`.
let zisk_rustc = {
let output = Command::new("rustc")
.env("RUSTUP_TOOLCHAIN", ZISK_TOOLCHAIN)
.arg("--print")
.arg("sysroot")
.output()
.map_err(ZiskError::RustcSysroot)?;
PathBuf::from(String::from_utf8_lossy(&output.stdout).trim())
.join("bin")
.join("rustc")
};
let status = Command::new("cargo")
.current_dir(guest_directory)
.env("RUSTC", zisk_rustc)
.args(["build", "--release", "--target", ZISK_TARGET])
.status()
.map_err(|e| ZiskError::CargoBuild {
cwd: guest_directory.to_path_buf(),
source: e,
})?;
if !status.success() {
return Err(ZiskError::CargoBuildFailed {
status,
path: guest_directory.to_path_buf(),
});
}
// Get the workspace directory.
let program_workspace_path = {
let output = Command::new("cargo")
.current_dir(guest_directory)
.arg("locate-project")
.arg("--workspace")
.arg("--message-format=plain")
.output()
.map_err(ZiskError::CargoLocateProject)?;
PathBuf::from(
String::from_utf8_lossy(&output.stdout)
.trim()
.strip_suffix("Cargo.toml")
.expect("location to be path to Cargo.toml"),
)
};
let elf_path = program_workspace_path
.join("target")
.join("riscv64ima-zisk-zkvm-elf")
.join("release")
.join(package_name);
let elf_bytes = fs::read(&elf_path).map_err(|e| ZiskError::ReadFile {
path: elf_path.clone(),
source: e,
})?;
Ok(elf_bytes)
}
}
#[cfg(test)]
mod tests {
use crate::compiler::RustRv64imaCustomized;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
#[test]
fn test_compile() {
let guest_directory = testing_guest_directory("zisk", "basic");
let elf_bytes = RustRv64imaCustomized.compile(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -31,23 +31,6 @@ pub enum ZiskError {
},
// Compilation
#[error("Program path does not exist or is not a directory: {0}")]
InvalidProgramPath(PathBuf),
#[error(
"Cargo.toml not found in program directory: {program_dir}. Expected at: {manifest_path}"
)]
CargoTomlMissing {
program_dir: PathBuf,
manifest_path: PathBuf,
},
#[error("Could not find `[package].name` in guest Cargo.toml at {path}")]
MissingPackageName { path: PathBuf },
#[error("Failed to parse guest Cargo.toml at {path}: {source}")]
ParseCargoToml {
path: PathBuf,
#[source]
source: toml::de::Error,
},
#[error("Failed to execute `RUSTUP_TOOLCHAIN=zisk rustc --print sysroot`")]
RustcSysroot(#[source] io::Error),
#[error("Failed to execute `cargo locate-project --workspace --message-format=plain`")]
@@ -62,6 +45,8 @@ pub enum ZiskError {
"`RUSTC=$ZISK_RUSTC cargo build --release ...` failed with status: {status} for program at {path}"
)]
CargoBuildFailed { status: ExitStatus, path: PathBuf },
#[error(transparent)]
CompileUtilError(#[from] compile_utils::CompileError),
// Serialization
#[error("Bincode serialization/deserialization failed: {0}")]

View File

@@ -2,39 +2,25 @@
use crate::{
client::{ZiskOptions, ZiskSdk, ZiskServer},
compile::compile_zisk_program,
compiler::ZiskProgram,
error::ZiskError,
};
use serde::de::DeserializeOwned;
use std::{
io::Read,
path::Path,
sync::{Mutex, MutexGuard},
time::Instant,
};
use zkvm_interface::{
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof,
ProverResourceType, PublicValues, zkVM, zkVMError,
Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType,
PublicValues, zkVM, zkVMError,
};
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
mod client;
mod compile;
mod error;
#[allow(non_camel_case_types)]
pub struct RV64_IMA_ZISK_ZKVM_ELF;
impl Compiler for RV64_IMA_ZISK_ZKVM_ELF {
type Error = ZiskError;
type Program = Vec<u8>;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
compile_zisk_program(guest_directory)
}
}
pub mod compiler;
pub mod error;
pub struct EreZisk {
sdk: ZiskSdk,
@@ -46,7 +32,7 @@ pub struct EreZisk {
}
impl EreZisk {
pub fn new(elf: Vec<u8>, resource: ProverResourceType) -> Result<Self, zkVMError> {
pub fn new(elf: ZiskProgram, resource: ProverResourceType) -> Result<Self, zkVMError> {
if matches!(resource, ProverResourceType::Network(_)) {
panic!("Network proving not yet implemented for ZisK. Use CPU or GPU resource type.");
}
@@ -151,11 +137,12 @@ fn serialize_inputs(inputs: &Input) -> Result<Vec<u8>, ZiskError> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{EreZisk, compiler::RustRv64imaCustomized};
use std::sync::{Mutex, OnceLock};
use test_utils::host::{
BasicProgramIo, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
};
use zkvm_interface::{Compiler, ProverResourceType, zkVM};
/// It fails if multiple servers created concurrently using the same port,
/// so we have a lock to avoid that.
@@ -166,7 +153,7 @@ mod tests {
fn basic_program() -> Vec<u8> {
BASIC_PROGRAM
.get_or_init(|| {
RV64_IMA_ZISK_ZKVM_ELF
RustRv64imaCustomized
.compile(&testing_guest_directory("zisk", "basic"))
.unwrap()
})