@@ -14,12 +14,6 @@ use primitives::HashTreeRoot as _;
1414/// Envelope carrying a block and the single merged proof binding every
1515/// signature it depends on.
1616///
17- /// `proof` holds the SSZ-encoded form of a [`TypeTwoMultiSignature`]
18- /// container whose only field is a `ByteList512KiB` holding the raw
19- /// `compress_without_pubkeys()` Type-2 merged proof bytes. On the wire the
20- /// container collapses to `[4-byte offset = 4][type2_wire]` — a thin
21- /// 4-byte prefix in front of the lean-multisig bytes (leanSpec PR #717).
22- ///
2317/// <div class="warning">
2418///
2519/// `HashTreeRoot` is intentionally not derived: consumers never hash a
@@ -33,80 +27,71 @@ pub struct SignedBlock {
3327 /// The block being signed.
3428 pub message : Block ,
3529
36- /// SSZ-encoded `TypeTwoMultiSignature` envelope. Use
37- /// [`SignedBlock::merged_proof_bytes`] to extract the raw
38- /// lean-multisig Type-2 bytes inside, or
39- /// [`SignedBlock::wrap_merged_proof`] when building an envelope from
40- /// the prover output.
30+ /// Single full-block proof covering attestations and the proposer signature.
31+ pub proof : MultiMessageAggregate ,
32+ }
33+
34+ // Manual Debug impl because the merged proof bytes are large and opaque.
35+ impl core:: fmt:: Debug for SignedBlock {
36+ fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
37+ f. debug_struct ( "SignedBlock" )
38+ . field ( "message" , & self . message )
39+ . field ( "proof" , & format_args ! ( "<{} bytes>" , self . proof. proof. len( ) ) )
40+ . finish ( )
41+ }
42+ }
43+
44+ /// 512 KiB byte-list cap shared by every block-level / Type-1 proof field.
45+ /// Matches leanSpec PR #717's `ByteList512KiB` SSZ container.
46+ pub type ByteList512KiB = ByteList < 524_288 > ;
47+
48+ /// A merged proof covering multiple messages with a single proof blob.
49+ ///
50+ /// The proof bytes use lean-multisig's compact public-key-free
51+ /// representation. SSZ encoding this container adds the offset required for
52+ /// its variable-length field.
53+ #[ derive( Debug , Default , Clone , PartialEq , Eq , SszEncode , SszDecode , HashTreeRoot ) ]
54+ pub struct MultiMessageAggregate {
55+ /// Serialized multi-message aggregate proof bytes.
4156 pub proof : ByteList512KiB ,
4257}
4358
44- impl SignedBlock {
45- /// Strip the SSZ-container offset header to return the raw
46- /// lean-multisig Type-2 merged proof bytes the verifier consumes.
47- pub fn merged_proof_bytes ( & self ) -> Result < & [ u8 ] , ProofEnvelopeError > {
48- let bytes = self . proof . iter ( ) . as_slice ( ) ;
49- if bytes. len ( ) < 4 {
50- return Err ( ProofEnvelopeError :: TruncatedEnvelope ) ;
51- }
52- let mut header = [ 0u8 ; 4 ] ;
53- header. copy_from_slice ( & bytes[ ..4 ] ) ;
54- let offset = u32:: from_le_bytes ( header) as usize ;
55- if offset != 4 {
56- return Err ( ProofEnvelopeError :: UnexpectedOffset ( offset) ) ;
57- }
58- Ok ( & bytes[ 4 ..] )
59+ impl MultiMessageAggregate {
60+ /// Build an aggregate from an already bounded proof byte list.
61+ pub fn new ( proof : ByteList512KiB ) -> Self {
62+ Self { proof }
63+ }
64+
65+ /// Copy raw lean-multisig proof bytes into the bounded SSZ container.
66+ pub fn from_bytes ( bytes : & [ u8 ] ) -> Result < Self , MultiMessageAggregateError > {
67+ let len = bytes. len ( ) ;
68+ ByteList512KiB :: try_from ( bytes. to_vec ( ) )
69+ . map ( Self :: new)
70+ . map_err ( |_| MultiMessageAggregateError :: ProofTooLarge ( len) )
5971 }
6072
61- /// Wrap raw lean-multisig Type-2 bytes into a `SignedBlock.proof`
62- /// envelope: prepend the 4-byte SSZ offset header so the wire matches
63- /// the spec's `TypeTwoMultiSignature { proof: ByteList512KiB }`
64- /// container.
65- pub fn wrap_merged_proof ( type2_wire : & [ u8 ] ) -> Result < ByteList512KiB , ProofEnvelopeError > {
66- let mut wrapped = Vec :: with_capacity ( 4 + type2_wire. len ( ) ) ;
67- wrapped. extend_from_slice ( & 4u32 . to_le_bytes ( ) ) ;
68- wrapped. extend_from_slice ( type2_wire) ;
69- let len = wrapped. len ( ) ;
70- ByteList512KiB :: try_from ( wrapped) . map_err ( |_| ProofEnvelopeError :: ExceedsCap ( len) )
73+ /// Return the raw lean-multisig proof bytes.
74+ pub fn proof_bytes ( & self ) -> & [ u8 ] {
75+ self . proof . iter ( ) . as_slice ( )
7176 }
7277}
7378
74- /// Errors returned by the [`SignedBlock`] proof-envelope helpers .
79+ /// Errors returned when constructing a [`MultiMessageAggregate`] .
7580#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
76- pub enum ProofEnvelopeError {
77- /// Envelope is shorter than the 4-byte SSZ offset header.
78- TruncatedEnvelope ,
79- /// Offset header is not the expected single-field value `4`.
80- UnexpectedOffset ( usize ) ,
81- /// Wrapped proof would exceed `ByteList512KiB`'s cap.
82- ExceedsCap ( usize ) ,
81+ pub enum MultiMessageAggregateError {
82+ /// Proof bytes exceed `ByteList512KiB`'s cap.
83+ ProofTooLarge ( usize ) ,
8384}
8485
85- impl core:: fmt:: Display for ProofEnvelopeError {
86+ impl core:: fmt:: Display for MultiMessageAggregateError {
8687 fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
8788 match self {
88- Self :: TruncatedEnvelope => f. write_str ( "block proof envelope truncated" ) ,
89- Self :: UnexpectedOffset ( o) => write ! ( f, "block proof envelope offset {o}, expected 4" ) ,
90- Self :: ExceedsCap ( n) => write ! ( f, "wrapped proof {n} bytes exceeds 512 KiB cap" ) ,
89+ Self :: ProofTooLarge ( n) => write ! ( f, "proof {n} bytes exceeds 512 KiB cap" ) ,
9190 }
9291 }
9392}
9493
95- impl std:: error:: Error for ProofEnvelopeError { }
96-
97- // Manual Debug impl because the merged proof bytes are large and opaque.
98- impl core:: fmt:: Debug for SignedBlock {
99- fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
100- f. debug_struct ( "SignedBlock" )
101- . field ( "message" , & self . message )
102- . field ( "proof" , & format_args ! ( "<{} bytes>" , self . proof. len( ) ) )
103- . finish ( )
104- }
105- }
106-
107- /// 512 KiB byte-list cap shared by every block-level / Type-1 proof field.
108- /// Matches leanSpec PR #717's `ByteList512KiB` SSZ container.
109- pub type ByteList512KiB = ByteList < 524_288 > ;
94+ impl std:: error:: Error for MultiMessageAggregateError { }
11095
11196// ============================================================================
11297// Type-1 multi-signature
@@ -118,10 +103,10 @@ pub type ByteList512KiB = ByteList<524_288>;
118103// from the surrounding block body (attestation `data` + slot for body
119104// components, block root + slot for the proposer component).
120105//
121- // `TypeTwoMultiSignature` has no Rust-side struct: the block carries the
122- // raw lean-multisig Type-2 bytes directly on `SignedBlock.proof`. Component
123- // participant bitfields come from `block.body.attestations[i].aggregation_bits`
124- // (and `block.proposer_index` for the trailing proposer entry).
106+ // `MultiMessageAggregate` carries the raw lean-multisig Type-2 bytes.
107+ // Component participant bitfields come from
108+ // `block.body.attestations[i].aggregation_bits` (and `block.proposer_index` for
109+ // the trailing proposer entry).
125110
126111/// Maximum number of distinct `AttestationData` entries permitted in a single
127112/// block. Canonical home for the cap shared across `ethlambda-blockchain`,
@@ -322,15 +307,27 @@ mod tests {
322307 } ;
323308 let signed = SignedBlock {
324309 message : block,
325- proof : ByteList512KiB :: default ( ) ,
310+ proof : MultiMessageAggregate :: default ( ) ,
326311 } ;
327312 let bytes = signed. to_ssz ( ) ;
328313 let decoded = SignedBlock :: from_ssz_bytes ( & bytes) . expect ( "decode" ) ;
329- assert_eq ! ( decoded. proof. len( ) , 0 ) ;
314+ assert_eq ! ( decoded. proof. proof . len( ) , 0 ) ;
330315 assert_eq ! ( decoded. message. slot, signed. message. slot) ;
331316 assert_eq ! (
332317 decoded. message. proposer_index,
333318 signed. message. proposer_index
334319 ) ;
335320 }
321+
322+ #[ test]
323+ fn multi_message_aggregate_ssz_wraps_proof_bytes ( ) {
324+ let proof_bytes: Vec < u8 > = ( 0 ..64 ) . collect ( ) ;
325+ let aggregate = MultiMessageAggregate :: from_bytes ( & proof_bytes) . unwrap ( ) ;
326+
327+ let encoded = aggregate. to_ssz ( ) ;
328+
329+ assert_eq ! ( & encoded[ ..4 ] , & 4u32 . to_le_bytes( ) ) ;
330+ assert_eq ! ( & encoded[ 4 ..] , proof_bytes) ;
331+ assert_eq ! ( aggregate. proof_bytes( ) , proof_bytes) ;
332+ }
336333}
0 commit comments