Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,595 changes: 32 additions & 1,563 deletions Cargo.lock

Large diffs are not rendered by default.

266 changes: 133 additions & 133 deletions bench_vs/lambda/recursion/Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bench_vs/lambda/recursion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ lambda-vm-prover = { path = "../../../prover", default-features = false, feature
"profile-markers",
] }
lambda-vm-syscalls = { path = "../../../syscalls" }
postcard = { version = "1.0", features = ["alloc"] }
rkyv = { version = "0.8.10", default-features = false, features = ["alloc", "bytecheck", "aligned"] }

[profile.release]
debug = 2
46 changes: 25 additions & 21 deletions bench_vs/lambda/recursion/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
//! Naive recursion guest: verifies an inner lambda-vm proof inside the VM.
//!
//! Private input (postcard): `lambda_vm_prover::recursion::GuestInput` — the
//! inner proof, the inner program's ELF bytes, and its precomputed DECODE and
//! ELF-data-page commitments, supplied instead of recomputed in-VM.
//! Private input layout: a 12-byte `"LVMR" + version + reserved` prefix
//! followed by an rkyv archive of `lambda_vm_prover::recursion::GuestInput`
//! `{ vm_proof, inner_elf, decode_commitment, page_commitments }` (built
//! host-side by `recursion::encode_guest_input`) — the inner program's ELF
//! bytes plus its precomputed DECODE and ELF-data-page commitments, supplied
//! instead of recomputed in-VM. The prefix 16-aligns the archive in guest
//! memory (the executor maps the payload at `PRIVATE_INPUT_START + 4`, which
//! is only 4-aligned) and tags the format so the guest rejects a wrong-format
//! blob before the unsafe access. The proof is verified **in place** via
//! `recursion::verify_and_attest_blob` — no deserialization pass, no owned
//! `VmProof`.
//!
//! `ProofOptions` is fixed by the `min`/`blowup8` Cargo feature (a `Preset`),
//! not private input — an attacker could otherwise pick trivially weak options
//! and have the guest accept as if a real proof had been checked.
//!
//! On success commits `program_id || inner_public_output` via
//! `recursion::verify_and_attest` (a single ELF parse and a single full-ELF
//! Keccak, shared between the statement absorb and the `program_id` fold). The
//! attestation is not self-enforcing: the binding is established by the
//! consumer via `recursion::check_attestation` (a host-side recompute+compare),
//! never in-guest.
//! `recursion::verify_and_attest_blob` (a single ELF parse and a single
//! full-ELF Keccak, shared between the statement absorb and the `program_id`
//! fold). The id fold is what the consumer rebinds to a trusted ELF
//! (`check_attestation`); it is not self-enforcing here — the binding is
//! established by the consumer via `recursion::check_attestation` (a
//! host-side recompute+compare), never in-guest.
//!
//! std (not `no_std`): `build-std` provides it, prove-side code is DCE'd.
//! `#![no_main]`; inits the syscalls global allocator first thing in `main`.

#![no_main]

use lambda_vm_prover::recursion::{GuestInput, Preset};
use lambda_vm_prover::recursion::Preset;

#[cfg(not(any(feature = "min", feature = "blowup8")))]
compile_error!("select exactly one of the `min`/`blowup8` features");
Expand All @@ -43,9 +52,10 @@ pub fn main() -> ! {
lambda_vm_syscalls::syscalls::sys_panic(PANIC_MSG.as_ptr(), PANIC_MSG.len())
}));

let blob = lambda_vm_syscalls::syscalls::get_private_input();
let (vm_proof, inner_elf, decode_commitment, page_commitments): GuestInput =
postcard::from_bytes(&blob).expect("failed to deserialize recursion input");
// Zero-copy: borrow the blob straight from the mapped private-input region.
// The 12-byte prefix puts the archive at a 16-aligned guest address, so the
// verifier's in-place doubleword loads don't trap.
let blob = lambda_vm_syscalls::syscalls::get_private_input_slice();
lambda_vm_prover::profile_markers::step_marker::<
{ lambda_vm_prover::profile_markers::STEP_DECODE_DONE },
>();
Expand All @@ -55,15 +65,9 @@ pub fn main() -> ! {
// is what the consumer rebinds to a trusted ELF (`check_attestation`); it is
// not self-enforcing here.
let options = PRESET.options();
let attestation = lambda_vm_prover::recursion::verify_and_attest(
&vm_proof,
&inner_elf,
&options,
decode_commitment,
&page_commitments,
)
.expect("verify errored")
.expect("inner proof failed verification");
let attestation = lambda_vm_prover::recursion::verify_and_attest_blob(blob, &options)
.expect("verify errored")
.expect("inner proof failed verification");
lambda_vm_syscalls::syscalls::commit(&attestation);
lambda_vm_syscalls::syscalls::sys_halt();
}
2 changes: 1 addition & 1 deletion bin/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ executor = { path = "../../executor" }
prover = { path = "../../prover", package = "lambda-vm-prover" }
stark = { path = "../../crypto/stark" }
clap = { version = "4.3.10", features = ["derive"] }
bincode = "1"
rkyv = { version = "0.8.10", default-features = false, features = ["alloc", "bytecheck", "aligned"] }
tempfile = "3"
tikv-jemallocator = "0.6"
tikv-jemalloc-ctl = { version = "0.6", features = ["stats"], optional = true }
Expand Down
42 changes: 30 additions & 12 deletions bin/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ use stark::proof::options::GoldilocksCubicProofOptions;
const DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2: u32 = 20;
const MIN_CONTINUATION_EPOCH_SIZE_LOG2: u32 = 18;

/// Read a file into a buffer aligned for `rkyv::from_bytes`. A plain
/// `Vec<u8>` from `std::fs::read` is align-1 by the type system even though
/// the allocator happens to return well-aligned memory in practice — read
/// straight into an `AlignedVec` instead of relying on that.
fn read_aligned_file(path: &Path) -> std::io::Result<rkyv::util::AlignedVec<16>> {
use std::os::unix::fs::FileExt;

let file = std::fs::File::open(path)?;
let len = file.metadata()?.len() as usize;
let mut aligned = rkyv::util::AlignedVec::<16>::with_capacity(len);
aligned.resize(len, 0);
file.read_exact_at(&mut aligned, 0)?;
Ok(aligned)
}

/// Polls jemalloc `stats.allocated` every 10ms from a background thread,
/// tracking the high-water mark. Near-zero overhead because jemalloc uses
/// thread-local caches — `epoch::advance()` just merges cached counters.
Expand Down Expand Up @@ -627,7 +642,7 @@ fn cmd_prove(
};
let mut writer = BufWriter::new(file);

let bytes = match bincode::serialize(&proof) {
let bytes = match rkyv::to_bytes::<rkyv::rancor::Error>(&proof) {
Ok(b) => b,
Err(e) => {
eprintln!("Failed to serialize proof: {}", e);
Expand Down Expand Up @@ -670,15 +685,15 @@ fn cmd_verify(proof_path: PathBuf, elf_path: PathBuf, blowup: u8, time: bool) ->
};

eprintln!("Reading proof...");
let proof_bytes = match std::fs::read(&proof_path) {
let proof_bytes = match read_aligned_file(&proof_path) {
Ok(b) => b,
Err(e) => {
eprintln!("Failed to read proof file: {}", e);
return ExitCode::FAILURE;
}
};

let proof: VmProof = match bincode::deserialize(&proof_bytes) {
let proof: VmProof = match rkyv::from_bytes::<VmProof, rkyv::rancor::Error>(&proof_bytes) {
Ok(p) => p,
Err(e) => {
eprintln!("Failed to deserialize proof: {}", e);
Expand Down Expand Up @@ -799,7 +814,7 @@ fn cmd_prove_continuation(
}
};
let mut writer = BufWriter::new(file);
let bytes = match bincode::serialize(&bundle) {
let bytes = match rkyv::to_bytes::<rkyv::rancor::Error>(&bundle) {
Ok(b) => b,
Err(e) => {
eprintln!("Failed to serialize proof: {}", e);
Expand Down Expand Up @@ -838,20 +853,23 @@ fn cmd_verify_continuation(
};

eprintln!("Reading proof...");
let proof_bytes = match std::fs::read(&proof_path) {
let proof_bytes = match read_aligned_file(&proof_path) {
Ok(b) => b,
Err(e) => {
eprintln!("Failed to read proof file: {}", e);
return ExitCode::FAILURE;
}
};
let bundle: prover::continuation::ContinuationProof = match bincode::deserialize(&proof_bytes) {
Ok(p) => p,
Err(e) => {
eprintln!("Failed to deserialize proof: {}", e);
return ExitCode::FAILURE;
}
};
let bundle: prover::continuation::ContinuationProof =
match rkyv::from_bytes::<prover::continuation::ContinuationProof, rkyv::rancor::Error>(
&proof_bytes,
) {
Ok(p) => p,
Err(e) => {
eprintln!("Failed to deserialize proof: {}", e);
return ExitCode::FAILURE;
}
};

let opts = match GoldilocksCubicProofOptions::with_blowup(blowup) {
Ok(opts) => opts,
Expand Down
8 changes: 7 additions & 1 deletion crypto/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ rand_chacha = { version = "0.3.1", default-features = false }
memmap2 = { version = "0.9", optional = true }
tempfile = { version = "3", optional = true }
libc = { version = "0.2", optional = true }
rkyv = { version = "0.8.10", default-features = false, features = [
"alloc",
"bytecheck",
"aligned",
], optional = true }

[dev-dependencies]
math = { path = "../math", features = ["test-utils"] }
Expand All @@ -37,4 +42,5 @@ std = ["math/std", "sha3/std", "serde?/std"]
serde = ["dep:serde"]
parallel = ["dep:rayon"]
disk-spill = ["std", "dep:memmap2", "dep:tempfile", "dep:libc"]
alloc = []
alloc = []
rkyv = ["dep:rkyv", "math/rkyv"]
48 changes: 34 additions & 14 deletions crypto/crypto/src/merkle_tree/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,49 @@ use super::{
/// when verifying.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub struct Proof<T: PartialEq + Eq> {
pub merkle_path: Vec<T>,
}

/// Verifies a Merkle inclusion proof given the authentication path as a borrowed
/// slice. Shared by [`Proof::verify`] (owned) and the zero-copy verifier (which
/// reads the path straight from an rkyv-archived proof buffer) so both compute
/// the identical root.
pub fn verify_merkle_path<B>(
merkle_path: &[B::Node],
root_hash: &B::Node,
mut index: usize,
value: &B::Data,
) -> bool
where
B: IsMerkleTreeBackend,
{
let mut hashed_value = B::hash_data(value);

for sibling_node in merkle_path.iter() {
if index.is_multiple_of(2) {
hashed_value = B::hash_new_parent(&hashed_value, sibling_node);
} else {
hashed_value = B::hash_new_parent(sibling_node, &hashed_value);
}

index >>= 1;
}

root_hash == &hashed_value
}

impl<T: PartialEq + Eq> Proof<T> {
/// Verifies a Merkle inclusion proof for the value contained at leaf index.
pub fn verify<B>(&self, root_hash: &B::Node, mut index: usize, value: &B::Data) -> bool
pub fn verify<B>(&self, root_hash: &B::Node, index: usize, value: &B::Data) -> bool
where
B: IsMerkleTreeBackend<Node = T>,
{
let mut hashed_value = B::hash_data(value);

for sibling_node in self.merkle_path.iter() {
if index.is_multiple_of(2) {
hashed_value = B::hash_new_parent(&hashed_value, sibling_node);
} else {
hashed_value = B::hash_new_parent(sibling_node, &hashed_value);
}

index >>= 1;
}

root_hash == &hashed_value
verify_merkle_path::<B>(&self.merkle_path, root_hash, index, value)
}
}

Expand Down
9 changes: 9 additions & 0 deletions crypto/math/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ rayon = { version = "1.7", optional = true }
num-bigint = { version = "0.4.6", default-features = false }
num-traits = { version = "0.2.19", default-features = false }

# rkyv zero-copy (de)serialization. Optional; used by the recursion verifier to
# read a proof straight from its byte buffer with no deserialization pass.
rkyv = { version = "0.8.10", default-features = false, features = [
"alloc",
"bytecheck",
"aligned",
], optional = true }

[dev-dependencies]
rand_chacha = "0.3.1"
criterion = "0.5.1"
Expand All @@ -39,6 +47,7 @@ lambdaworks-serde-string = ["dep:serde", "dep:serde_json", "alloc"]
proptest = ["dep:proptest"]
instruments = []
test-utils = []
rkyv = ["dep:rkyv"]

[target.wasm32-unknown-unknown.dependencies]
getrandom = { version = "0.2.15", features = ["js"] }
Expand Down
Loading