diff --git a/Cargo.lock b/Cargo.lock index 0f7c1d75..763931ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -652,7 +652,7 @@ dependencies = [ [[package]] name = "backend" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "mt-air", "mt-fiat-shamir", @@ -3393,6 +3393,25 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indenter" version = "0.3.4" @@ -3644,7 +3663,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lean-multisig" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "backend", "clap", @@ -3652,16 +3671,20 @@ dependencies = [ "leansig_wrapper", "rand 0.10.1", "rec_aggregation", + "serde_json", "sub_protocols", + "system-info", "utils", + "zk-alloc", ] [[package]] name = "lean_compiler" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "backend", + "include_dir", "lean_vm", "pest", "pest_derive", @@ -3674,7 +3697,7 @@ dependencies = [ [[package]] name = "lean_prover" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "backend", "itertools 0.14.0", @@ -3683,6 +3706,7 @@ dependencies = [ "pest", "pest_derive", "rand 0.10.1", + "serde", "sub_protocols", "tracing", "utils", @@ -3691,7 +3715,7 @@ dependencies = [ [[package]] name = "lean_vm" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "backend", "itertools 0.14.0", @@ -3747,7 +3771,7 @@ dependencies = [ [[package]] name = "leansig_wrapper" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "backend", "ethereum_ssz", @@ -4782,7 +4806,7 @@ dependencies = [ [[package]] name = "mt-air" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "mt-field", "mt-poly", @@ -4791,7 +4815,7 @@ dependencies = [ [[package]] name = "mt-fiat-shamir" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "mt-field", "mt-koala-bear", @@ -4799,16 +4823,17 @@ dependencies = [ "mt-utils", "rayon", "serde", + "tracing", ] [[package]] name = "mt-field" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "itertools 0.14.0", "mt-utils", - "num-bigint 0.4.6", + "num-bigint 0.3.3", "paste", "rand 0.10.1", "rayon", @@ -4819,12 +4844,12 @@ dependencies = [ [[package]] name = "mt-koala-bear" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "itertools 0.14.0", "mt-field", "mt-utils", - "num-bigint 0.4.6", + "num-bigint 0.3.3", "paste", "rand 0.10.1", "rayon", @@ -4835,7 +4860,7 @@ dependencies = [ [[package]] name = "mt-poly" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "itertools 0.14.0", "mt-field", @@ -4843,12 +4868,13 @@ dependencies = [ "rand 0.10.1", "rayon", "serde", + "system-info", ] [[package]] name = "mt-sumcheck" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "mt-air", "mt-fiat-shamir", @@ -4861,7 +4887,7 @@ dependencies = [ [[package]] name = "mt-symetric" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "mt-field", "mt-koala-bear", @@ -4871,7 +4897,7 @@ dependencies = [ [[package]] name = "mt-utils" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "serde", ] @@ -4879,7 +4905,7 @@ dependencies = [ [[package]] name = "mt-whir" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "itertools 0.14.0", "mt-fiat-shamir", @@ -4891,6 +4917,7 @@ dependencies = [ "mt-utils", "rand 0.10.1", "rayon", + "system-info", "tracing", ] @@ -5209,6 +5236,16 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "object" version = "0.37.3" @@ -6341,14 +6378,17 @@ dependencies = [ [[package]] name = "rec_aggregation" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "backend", + "include_dir", "lean_compiler", "lean_prover", "lean_vm", "leansig_wrapper", "lz4_flex", + "objc2", + "objc2-foundation", "postcard", "rand 0.10.1", "serde", @@ -6356,6 +6396,7 @@ dependencies = [ "sub_protocols", "tracing", "utils", + "zk-alloc", ] [[package]] @@ -7386,7 +7427,7 @@ dependencies = [ [[package]] name = "sub_protocols" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "backend", "lean_vm", @@ -7477,6 +7518,15 @@ dependencies = [ "libc", ] +[[package]] +name = "system-info" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" +dependencies = [ + "libc", + "rayon", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -8021,7 +8071,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b1#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" dependencies = [ "backend", "tracing", @@ -8984,6 +9034,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zk-alloc" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=f66d4a9#f66d4a974eced803574eb0ea43d812e523c8d7ad" +dependencies = [ + "libc", + "system-info", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/crates/common/crypto/Cargo.toml b/crates/common/crypto/Cargo.toml index 7cb62ee0..52664f53 100644 --- a/crates/common/crypto/Cargo.toml +++ b/crates/common/crypto/Cargo.toml @@ -12,9 +12,9 @@ version.workspace = true [dependencies] ethlambda-types.workspace = true -lean-multisig = { git = "https://github.com/leanEthereum/leanMultisig.git", rev = "5eba3b1" } +lean-multisig = { git = "https://github.com/leanEthereum/leanMultisig.git", rev = "f66d4a9" } # leansig_wrapper provides XmssPublicKey/XmssSignature types used by lean-multisig's public API -leansig_wrapper = { git = "https://github.com/leanEthereum/leanMultisig.git", rev = "5eba3b1" } +leansig_wrapper = { git = "https://github.com/leanEthereum/leanMultisig.git", rev = "f66d4a9" } leansig.workspace = true thiserror.workspace = true diff --git a/crates/common/crypto/src/lib.rs b/crates/common/crypto/src/lib.rs index b8fc1c2a..6dafde66 100644 --- a/crates/common/crypto/src/lib.rs +++ b/crates/common/crypto/src/lib.rs @@ -6,8 +6,8 @@ use ethlambda_types::{ signature::{ValidatorPublicKey, ValidatorSignature}, }; use lean_multisig::{ - AggregatedXMSS, ProofError, setup_prover, setup_verifier, xmss_aggregate, - xmss_verify_aggregation, + AggregatedXMSS, AggregationError as LeanAggregationError, ProofError, setup_prover, + setup_verifier, xmss_aggregate, xmss_verify_aggregation, }; use leansig_wrapper::{XmssPublicKey as LeanSigPubKey, XmssSignature as LeanSigSignature}; use thiserror::Error; @@ -43,6 +43,9 @@ pub enum AggregationError { #[error("need at least 2 children for recursive aggregation, got {0}")] InsufficientChildren(usize), + + #[error("aggregation failed: {0}")] + Aggregation(#[from] LeanAggregationError), } /// Error type for signature verification operations. @@ -75,6 +78,12 @@ pub enum VerificationError { /// # Returns /// /// The serialized aggregated proof as `ByteListMiB`, or an error if aggregation fails. +/// +/// # Errors +/// +/// Returns [`AggregationError::CountMismatch`] / [`AggregationError::EmptyInput`] on +/// malformed input, and [`AggregationError::Aggregation`] if any raw signature is +/// cryptographically invalid for `message`/`slot`. pub fn aggregate_signatures( public_keys: Vec, signatures: Vec, @@ -104,7 +113,7 @@ pub fn aggregate_signatures( // log_inv_rate=2 matches the devnet-4 cross-client convention (zeam, ream, // grandine, lantern's c-leanvm-xmss all use 2). Ethlambda previously // hardcoded 1, which produced proofs incompatible with every other client. - let (_sorted_pubkeys, aggregate) = xmss_aggregate(&[], raw_xmss, &message.0, slot, 2); + let (_sorted_pubkeys, aggregate) = xmss_aggregate(&[], raw_xmss, &message.0, slot, 2)?; serialize_aggregate(aggregate) } @@ -118,11 +127,11 @@ pub fn aggregate_signatures( /// Requires at least one raw signature OR at least 2 children. A lone child proof /// is already valid and needs no further aggregation. /// -/// # Panics +/// # Errors /// -/// Panics if any deserialized child proof is cryptographically invalid (e.g., was -/// produced for a different message or slot). This is an upstream constraint of -/// `xmss_aggregate`. +/// Returns [`AggregationError::Aggregation`] if any raw signature or deserialized +/// child proof is cryptographically invalid (e.g., was produced for a different +/// message or slot). pub fn aggregate_mixed( children: Vec<(Vec, ByteListMiB)>, raw_public_keys: Vec, @@ -160,7 +169,7 @@ pub fn aggregate_mixed( .collect(); let (_sorted_pubkeys, aggregate) = - xmss_aggregate(&children_refs, raw_xmss, &message.0, slot, 2); + xmss_aggregate(&children_refs, raw_xmss, &message.0, slot, 2)?; serialize_aggregate(aggregate) } @@ -173,6 +182,13 @@ pub fn aggregate_mixed( /// /// This is used during block building to compact multiple proofs sharing the same /// `AttestationData` into a single merged proof (leanSpec PR #510). +/// +/// # Errors +/// +/// Returns [`AggregationError::InsufficientChildren`] if fewer than 2 children are +/// given, [`AggregationError::ChildDeserializationFailed`] if a child cannot be +/// decompressed, and [`AggregationError::Aggregation`] if a decompressed child proof +/// is cryptographically invalid for `message`/`slot`. pub fn aggregate_proofs( children: Vec<(Vec, ByteListMiB)>, message: &H256, @@ -190,7 +206,7 @@ pub fn aggregate_proofs( let children_refs: Vec<(&[LeanSigPubKey], AggregatedXMSS)> = pks_list.iter().map(Vec::as_slice).zip(aggs).collect(); - let (_sorted_pubkeys, aggregate) = xmss_aggregate(&children_refs, vec![], &message.0, slot, 2); + let (_sorted_pubkeys, aggregate) = xmss_aggregate(&children_refs, vec![], &message.0, slot, 2)?; serialize_aggregate(aggregate) } @@ -206,7 +222,7 @@ fn deserialize_children( .map(|(i, (pubkeys, proof_data))| { let lean_pks: Vec = pubkeys.into_iter().map(|pk| pk.into_inner()).collect(); - let aggregate = AggregatedXMSS::deserialize(proof_data.iter().as_slice()) + let aggregate = AggregatedXMSS::decompress(proof_data.iter().as_slice()) .ok_or(AggregationError::ChildDeserializationFailed(i))?; Ok((lean_pks, aggregate)) }) @@ -215,7 +231,7 @@ fn deserialize_children( /// Serialize an `AggregatedXMSS` into the `ByteListMiB` wire format. fn serialize_aggregate(aggregate: AggregatedXMSS) -> Result { - let serialized = aggregate.serialize(); + let serialized = aggregate.compress(); let serialized_len = serialized.len(); ByteListMiB::try_from(serialized).map_err(|_| AggregationError::ProofTooBig(serialized_len)) } @@ -250,7 +266,7 @@ pub fn verify_aggregated_signature( .collect(); // Deserialize the aggregate proof - let aggregate = AggregatedXMSS::deserialize(proof_data.iter().as_slice()) + let aggregate = AggregatedXMSS::decompress(proof_data.iter().as_slice()) .ok_or(VerificationError::DeserializationFailed)?; // Verify using lean-multisig @@ -406,4 +422,32 @@ mod tests { "Verification should have failed with wrong slot" ); } + + /// A child proof that is valid for one message must not be re-aggregable under a + /// different message. Since the f66d4a9 bump, `xmss_aggregate` returns an error in + /// this case instead of panicking, so the failure surfaces as + /// `AggregationError::Aggregation` rather than unwinding the thread. + #[test] + #[ignore = "too slow"] + fn test_aggregate_proofs_wrong_message_errors_without_panic() { + let message = H256::from([42u8; 32]); + let wrong_message = H256::from([43u8; 32]); + let slot = 10u32; + let activation_epoch = 5u32; + + let (pk, sig) = generate_keypair_and_sign(1, activation_epoch, slot, &message); + + // Produce a valid child proof bound to `message`/`slot`. + let child = aggregate_signatures(vec![pk.clone()], vec![sig], &message, slot).unwrap(); + + // Feed it as two children but claim a different message: the upstream + // child-validity check must reject this and we must get an error, not a panic. + let children = vec![(vec![pk.clone()], child.clone()), (vec![pk], child)]; + let result = aggregate_proofs(children, &wrong_message, slot); + + assert!( + matches!(result, Err(AggregationError::Aggregation(_))), + "expected AggregationError::Aggregation, got {result:?}" + ); + } }