Skip to content
Open
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.

265 changes: 133 additions & 132 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 @@ -16,7 +16,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
58 changes: 29 additions & 29 deletions bench_vs/lambda/recursion/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
//! Naive recursion guest: verifies an inner lambda-vm proof inside the VM.
//!
//! Private input (postcard): `(VmProof, Vec<u8>, Commitment, Vec<(u64, Commitment)>)`
//! — the inner program's ELF bytes plus its precomputed DECODE and
//! ELF-data-page commitments, supplied instead of recomputed in-VM.
//! `verify_with_options` does NOT bind the supplied roots to `inner_elf`; that
//! binding is established by folding them into `program_id` (below) and having
//! the host recompute that id and compare. That recompute is expensive, so it
//! happens once at the top level in the host, never in the guest — see
//! `program_id` in the prover's `statement` module.
//! Private input layout: a 12-byte `"LVMR" + version + reserved` prefix
//! followed by an rkyv archive of `lambda_vm_prover::RecursionInput`
//! `{ vm_proof, inner_elf, decode_commitment, page_commitments }` — 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 `verify_recursion_blob` — no deserialization
//! pass, no owned `VmProof`.
//!
//! `verify_recursion_blob`/`verify_with_options` does NOT bind the supplied
//! roots to `inner_elf`; that binding is established by folding them into
//! `program_id` (below) and having the host recompute that id and compare.
//! That recompute is expensive, so it happens once at the top level in the
//! host, never in the guest — see `program_id` in the prover's `statement`
//! module.
//!
//! `ProofOptions` is fixed by the `min`/`blowup8` Cargo feature, not private
//! input (an attacker could otherwise pick trivially weak options and have the
Expand All @@ -25,7 +34,7 @@

#[cfg(feature = "blowup8")]
use lambda_vm_prover::GoldilocksCubicProofOptions;
use lambda_vm_prover::{Commitment, ProofOptions, VmProof};
use lambda_vm_prover::ProofOptions;

#[cfg(not(any(feature = "min", feature = "blowup8")))]
compile_error!("select exactly one of the `min`/`blowup8` features");
Expand Down Expand Up @@ -61,38 +70,29 @@ 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): (
VmProof,
Vec<u8>,
Commitment,
Vec<(u64, Commitment)>,
) = 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 },
>();

let options = recursion_proof_options();
let ok = lambda_vm_prover::verify_with_options(
&vm_proof,
&inner_elf,
&options,
Some(decode_commitment),
Some(&page_commitments),
)
.expect("verify errored");
assert!(ok, "inner proof failed verification");
let verification =
lambda_vm_prover::verify_recursion_blob(blob, &options).expect("verify errored");
assert!(verification.ok, "inner proof failed verification");

// program_id is not self-enforcing: a consumer must recompute it natively
// and reject on mismatch. Commit the inner output alongside it.
let id = lambda_vm_prover::statement::program_id_from_elf(
&inner_elf,
&decode_commitment,
&page_commitments,
verification.inner_elf,
&verification.decode_commitment,
&verification.page_commitments,
)
.expect("program_id");
let mut output = id.to_vec();
output.extend_from_slice(&vm_proof.public_output);
output.extend_from_slice(verification.public_output);
lambda_vm_syscalls::syscalls::commit(&output);
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
23 changes: 13 additions & 10 deletions bin/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,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 @@ -617,7 +617,7 @@ fn cmd_verify(proof_path: PathBuf, elf_path: PathBuf, blowup: u8, time: bool) ->
}
};

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 @@ -738,7 +738,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 @@ -784,13 +784,16 @@ fn cmd_verify_continuation(
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
Loading