From 08f6e103962228957ebb16861c941dc4e505fd3e Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 10 Sep 2026 11:06:24 -0400 Subject: [PATCH] build(deps): track leanVM main (362a7c9b) and its claim-based API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit leanVM renamed the aggregation surface around the claims it publishes: `AggregateSignature` is now `EthereumProof`, `XmssGroup` and `SphincsSigner` became the named-field `XmssClaimGroup` and `SphincsClaim`, and the `WireKeys` tuple became `SignatureClaims`. `aggregate` gained a `blobs` argument and takes a `ClaimSelection` — signatures plus DA roots — where it took `&WireKeys`. The rename came with LeanDA: a proof can now carry blob commitments alongside signature claims. ethlambda makes no such claim, so it passes no blobs and an empty DA root list, which leaves the signature statement exactly as it was. Nothing about the scheme moved: still XmssTargetSumLifetime32Dim42Base8 over BLAKE2s with 32-byte pubkeys, so keys and stored proofs stay valid across this bump. The `leanvm_rev` in the keygen manifest and the benchmark report is read from the lockfile, so it follows the pin on its own; the doc samples showing it are updated to match. --- Cargo.lock | 39 ++++++++---- Cargo.toml | 2 +- crates/common/crypto/src/lib.rs | 102 ++++++++++++++++++++------------ docs/benchmarking.md | 2 +- docs/keygen.md | 2 +- 5 files changed, 95 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61184c1e..88b710ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2506,7 +2506,7 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "fiat_shamir" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "parallel", "primitives", @@ -2544,7 +2544,7 @@ dependencies = [ [[package]] name = "flock" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "fiat_shamir", "parallel", @@ -3714,16 +3714,29 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lean_compiler" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "lean_vm", "primitives", ] +[[package]] +name = "lean_da" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" +dependencies = [ + "fiat_shamir", + "parallel", + "pcs", + "primitives", + "serde", + "tracing", +] + [[package]] name = "lean_vm" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "fiat_shamir", "flock", @@ -3737,9 +3750,10 @@ dependencies = [ [[package]] name = "leanvm" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "clap", + "lean_da", "lean_vm", "primitives", "rand 0.9.4", @@ -5112,7 +5126,7 @@ dependencies = [ [[package]] name = "parallel" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "libc", ] @@ -5183,7 +5197,7 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pcs" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "fiat_shamir", "parallel", @@ -5420,7 +5434,7 @@ dependencies = [ [[package]] name = "primitives" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "libc", "parallel", @@ -5869,11 +5883,12 @@ dependencies = [ [[package]] name = "rec_aggregation" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "bincode", "flock", "lean_compiler", + "lean_da", "lean_vm", "parallel", "pcs", @@ -6750,7 +6765,7 @@ dependencies = [ [[package]] name = "sphincs" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "parallel", "primitives", @@ -8241,7 +8256,7 @@ dependencies = [ [[package]] name = "xmss" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "ethereum_ssz", "parallel", @@ -8410,7 +8425,7 @@ dependencies = [ [[package]] name = "zk_alloc" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=5a4f55c1138759f43f78483a7b70fde973e4a1ee#5a4f55c1138759f43f78483a7b70fde973e4a1ee" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=362a7c9b58ca29f8d57fb788ad6064b01e87deaa#362a7c9b58ca29f8d57fb788ad6064b01e87deaa" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index eef00613..4f3fbac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ clap = { version = "4.3", features = ["derive", "env"] } # SSZ codec for the two wire types) and the `rand` that signing draws from, so # the whole crypto stack arrives as one dependency at one revision. # Pinned to a `main` commit for reproducible builds; bump the rev to track main. -leanvm = { git = "https://github.com/leanEthereum/leanVM.git", rev = "5a4f55c1138759f43f78483a7b70fde973e4a1ee" } +leanvm = { git = "https://github.com/leanEthereum/leanVM.git", rev = "362a7c9b58ca29f8d57fb788ad6064b01e87deaa" } # Secret-key (de)serialization for the leanVM xmss key format. postcard = { version = "1.1.3", features = ["alloc"] } diff --git a/crates/common/crypto/src/lib.rs b/crates/common/crypto/src/lib.rs index 384fe718..73c41266 100644 --- a/crates/common/crypto/src/lib.rs +++ b/crates/common/crypto/src/lib.rs @@ -33,7 +33,7 @@ use ethlambda_types::{block::ByteList512KiB, primitives::H256}; use crate::signature::{ValidatorPublicKey, ValidatorSignature}; -use leanvm::{AggregateSignature, WireKeys, XmssGroup, aggregate, xmss}; +use leanvm::{ClaimSelection, EthereumProof, SignatureClaims, XmssClaimGroup, aggregate, xmss}; use std::sync::{Mutex, MutexGuard}; use thiserror::Error; use tracing::error; @@ -189,34 +189,51 @@ pub enum VerificationError { /// /// Getting this structure wrong is not caught at decode: it changes the digest /// the proof is checked against, so it surfaces as a verification failure. -fn wire_keys(components: &[SignerSet]) -> Result { - let mut groups: Vec = Vec::with_capacity(components.len()); +fn wire_keys(components: &[SignerSet]) -> Result { + let mut groups: Vec = Vec::with_capacity(components.len()); for component in components { let keys = component.public_keys.iter().map(|pk| pk.as_inner().clone()); - match groups.iter_mut().find(|(slot, ..)| *slot == component.slot) { - Some((_, message, group_keys)) => { - if *message != component.message.0 { + match groups + .iter_mut() + .find(|group| group.epoch == component.slot) + { + Some(group) => { + if group.message != component.message.0 { return Err(ConflictingMessages { slot: component.slot, }); } - group_keys.extend(keys); + group.keys.extend(keys); } - None => groups.push((component.slot, component.message.0, keys.collect())), + None => groups.push(XmssClaimGroup { + epoch: component.slot, + message: component.message.0, + keys: keys.collect(), + }), } } - for (_, _, keys) in &mut groups { - sort_dedup(keys); + for group in &mut groups { + sort_dedup(&mut group.keys); } - groups.sort_unstable_by_key(|(slot, ..)| *slot); - Ok((groups, Vec::new())) + groups.sort_unstable_by_key(|group| group.epoch); + Ok(SignatureClaims { + xmss: groups, + sphincs: Vec::new(), + }) } /// [`wire_keys`] for a single claim, which cannot conflict with itself. -fn one_group(message: &H256, slot: u32, public_keys: &[ValidatorPublicKey]) -> WireKeys { +fn one_group(message: &H256, slot: u32, public_keys: &[ValidatorPublicKey]) -> SignatureClaims { let mut keys: Vec<_> = public_keys.iter().map(|pk| pk.as_inner().clone()).collect(); sort_dedup(&mut keys); - (vec![(slot, message.0, keys)], Vec::new()) + SignatureClaims { + xmss: vec![XmssClaimGroup { + epoch: slot, + message: message.0, + keys, + }], + sphincs: Vec::new(), + } } /// A group's keys as leanVM's signer set requires them: strictly sorted, so the @@ -250,19 +267,19 @@ fn decompress_children( children: Vec<(Vec, ByteList512KiB)>, message: &H256, slot: u32, -) -> Result, AggregationError> { +) -> Result, AggregationError> { children .into_iter() .enumerate() .map(|(index, (pubkeys, proof_bytes))| { let keys = one_group(message, slot, &pubkeys); - AggregateSignature::from_bytes_without_pubkeys(proof_bytes.iter().as_slice(), keys) + EthereumProof::from_bytes_without_pubkeys(proof_bytes.iter().as_slice(), keys) .map_err(|_| AggregationError::ChildDeserializationFailed(index)) }) .collect() } -fn compress_to_byte_list(sig: &AggregateSignature) -> Result { +fn compress_to_byte_list(sig: &EthereumProof) -> Result { let serialized = sig.to_bytes_without_pubkeys(); let len = serialized.len(); ByteList512KiB::try_from(serialized).map_err(|_| AggregationError::ProofTooBig(len)) @@ -323,7 +340,8 @@ pub fn aggregate_signatures( let _permit = acquire_prover(); - let proof = aggregate(&[], raw_xmss, vec![], None, LOG_INV_RATE).map_err(aggregation_failed)?; + let proof = + aggregate(&[], raw_xmss, vec![], &[], None, LOG_INV_RATE).map_err(aggregation_failed)?; compress_to_byte_list(&proof) } @@ -373,7 +391,7 @@ pub fn aggregate_mixed( let _permit = acquire_prover(); - let proof = aggregate(&children_native, raw_xmss, vec![], None, LOG_INV_RATE) + let proof = aggregate(&children_native, raw_xmss, vec![], &[], None, LOG_INV_RATE) .map_err(aggregation_failed)?; compress_to_byte_list(&proof) @@ -410,7 +428,7 @@ pub fn aggregate_proofs( let _permit = acquire_prover(); - let proof = aggregate(&children_native, vec![], vec![], None, LOG_INV_RATE) + let proof = aggregate(&children_native, vec![], vec![], &[], None, LOG_INV_RATE) .map_err(aggregation_failed)?; compress_to_byte_list(&proof) @@ -440,7 +458,7 @@ pub fn verify_aggregated_signature( } let keys = one_group(message, slot, &public_keys); - let sig = AggregateSignature::from_bytes_without_pubkeys(proof_data.iter().as_slice(), keys) + let sig = EthereumProof::from_bytes_without_pubkeys(proof_data.iter().as_slice(), keys) .map_err(|_| VerificationError::DeserializationFailed)?; sig.verify() @@ -484,19 +502,19 @@ pub fn merge_type_1s_into_type_2( return Ok(dummy); } - let type_1s_native: Vec = type_1s + let type_1s_native: Vec = type_1s .iter() .enumerate() .map(|(index, (claim, proof_bytes))| { let keys = one_group(&claim.message, claim.slot, &claim.public_keys); - AggregateSignature::from_bytes_without_pubkeys(proof_bytes.iter().as_slice(), keys) + EthereumProof::from_bytes_without_pubkeys(proof_bytes.iter().as_slice(), keys) .map_err(|_| AggregationError::ChildDeserializationFailed(index)) }) .collect::>()?; let _permit = acquire_prover(); - let merged = aggregate(&type_1s_native, vec![], vec![], None, LOG_INV_RATE) + let merged = aggregate(&type_1s_native, vec![], vec![], &[], None, LOG_INV_RATE) .map_err(aggregation_failed)?; compress_to_byte_list(&merged) @@ -517,7 +535,7 @@ pub fn verify_type_2_signature( } let keys = wire_keys(components)?; - let sig = AggregateSignature::from_bytes_without_pubkeys(proof_data, keys) + let sig = EthereumProof::from_bytes_without_pubkeys(proof_data, keys) .map_err(|_| VerificationError::DeserializationFailed)?; sig.verify() @@ -549,7 +567,7 @@ pub fn split_type_2_by_message( } let keys = wire_keys(components)?; - let type_2 = AggregateSignature::from_bytes_without_pubkeys(proof_data, keys) + let type_2 = EthereumProof::from_bytes_without_pubkeys(proof_data, keys) .map_err(|_| AggregationError::DeserializationFailed)?; // A slot carries one message, so a message that appears at all appears in @@ -557,18 +575,27 @@ pub fn split_type_2_by_message( let mut matches = type_2 .xmss_signers() .iter() - .filter(|(_, group_message, _)| *group_message == message.0); + .filter(|group| group.message == message.0); let group = match (matches.next(), matches.next()) { (Some(group), None) => group.clone(), (None, _) => return Err(AggregationError::UnknownMessage), (Some(_), Some(_)) => return Err(AggregationError::MultipleMessages), }; - let declare: WireKeys = (vec![group], Vec::new()); + let kept = SignatureClaims { + xmss: vec![group], + sphincs: Vec::new(), + }; + // No blobs: ethlambda makes no LeanDA claim, so the selection publishes the + // one signature group and no DA roots. + let declare = ClaimSelection { + signatures: &kept, + da_commitments: &[], + }; let _permit = acquire_prover(); - let component = aggregate(&[type_2], vec![], vec![], Some(&declare), LOG_INV_RATE) + let component = aggregate(&[type_2], vec![], vec![], &[], Some(declare), LOG_INV_RATE) .map_err(aggregation_failed)?; compress_to_byte_list(&component) @@ -649,18 +676,19 @@ mod tests { SignerSet::new(msg_a, 4, vec![pk(2)]), SignerSet::new(msg_b, 9, vec![pk(1), pk(2)]), ]; - let (groups, sphincs) = wire_keys(&components).expect("one message per slot"); + let claims = wire_keys(&components).expect("one message per slot"); + let groups = &claims.xmss; - assert!(sphincs.is_empty(), "ethlambda signs XMSS only"); - let slots: Vec = groups.iter().map(|(slot, ..)| *slot).collect(); + assert!(claims.sphincs.is_empty(), "ethlambda signs XMSS only"); + let slots: Vec = groups.iter().map(|group| group.epoch).collect(); assert_eq!(slots, vec![4, 9], "groups sorted by slot"); - assert_eq!(groups[0].1, msg_a.0); - assert_eq!(groups[1].1, msg_b.0); - assert_eq!(groups[0].2.len(), 1); + assert_eq!(groups[0].message, msg_a.0); + assert_eq!(groups[1].message, msg_b.0); + assert_eq!(groups[0].keys.len(), 1); // Keys 1, 2, 3 unioned across the two slot-9 claims, deduplicated. - assert_eq!(groups[1].2.len(), 3); + assert_eq!(groups[1].keys.len(), 3); assert!( - groups[1].2.windows(2).all(|w| w[0] < w[1]), + groups[1].keys.windows(2).all(|w| w[0] < w[1]), "keys strictly sorted" ); } diff --git a/docs/benchmarking.md b/docs/benchmarking.md index e21126b6..f103a72b 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -79,7 +79,7 @@ otherwise, because a mis-attributed report is worse than no report. Block-building benchmark — synthetic workload (mock crypto) validators=8 warmup_slots=8 iterations=10 proofs_per_data=1 seed=42 enable_proposer_aggregation=false max_attestations_per_block=3 - ethlambda/v0.1.0/aarch64-apple-darwin/rustc-v1.97.1 leanvm=5a4f55c1 os=macos arch=aarch64 threads=14 + ethlambda/v0.1.0/aarch64-apple-darwin/rustc-v1.97.1 leanvm=362a7c9b os=macos arch=aarch64 threads=14 iter compact select_payloads stf_simulate overhead wall root 1 0.000ms 0.002ms 0.015ms 0.068ms 0.085ms 0x7282cc99 diff --git a/docs/keygen.md b/docs/keygen.md index 9e74263c..2c231778 100644 --- a/docs/keygen.md +++ b/docs/keygen.md @@ -82,7 +82,7 @@ hash_function: BLAKE2s encoding: TargetSum pubkey_bytes: 32 lifetime: 4294967296 -leanvm_rev: 5a4f55c1138759f43f78483a7b70fde973e4a1ee +leanvm_rev: 362a7c9b58ca29f8d57fb788ad6064b01e87deaa log_num_active_epochs: 18 num_active_epochs: 262144 num_validators: 3