88//! to the dealer's broadcast. The set of recipients does not have to be all nodes.
99
1010use crate :: nodes:: { Nodes , PartyId } ;
11+ use crate :: threshold_schnorr:: merkle:: { NestedMerkleProof , NestedMerkleTree } ;
1112use crate :: threshold_schnorr:: reed_solomon:: { ErasureCoder , Shard } ;
1213use crate :: threshold_schnorr:: EG ;
1314use crate :: types:: get_uniform_value;
1415use fastcrypto:: error:: FastCryptoError :: { InvalidInput , InvalidMessage , NotEnoughWeight } ;
1516use fastcrypto:: error:: FastCryptoResult ;
16- use fastcrypto:: hash:: Blake2b256 ;
1717use fastcrypto:: merkle;
18- use fastcrypto:: merkle:: MerkleTree ;
1918use itertools:: Itertools ;
2019use serde:: { Deserialize , Serialize } ;
2120use std:: collections:: { BTreeMap , BTreeSet } ;
@@ -33,11 +32,13 @@ pub struct Avid {
3332/// The dealer's per-party dispersal message. One [AuthenticatedShards] per recipient.
3433pub type Dispersal = BTreeMap < PartyId , AuthenticatedShards > ;
3534
36- /// One disperser's shards for a recipient's payload with a Merkle proof against the `top_root`.
35+ /// One disperser's shards for a recipient's payload with a two-level Merkle proof against the
36+ /// dispersal's `top_root` (row proof to the recipient's row root, top proof binding that row root
37+ /// to `top_root`).
3738#[ derive( Clone , Debug , Serialize , Deserialize ) ]
3839pub struct AuthenticatedShards {
3940 pub ( crate ) shards : Vec < Shard > ,
40- pub ( crate ) proof : merkle :: MerkleProof ,
41+ pub ( crate ) proof : NestedMerkleProof ,
4142}
4243
4344/// An endorsement of a dispersal's `top_root`.
@@ -96,13 +97,11 @@ impl Avid {
9697 payloads_by_recipient : & BTreeMap < PartyId , Vec < u8 > > ,
9798 mutate : impl FnOnce ( & mut BTreeMap < PartyId , Vec < Vec < Shard > > > ) ,
9899 ) -> FastCryptoResult < BTreeMap < PartyId , Dispersal > > {
99- let code = & self . coder ;
100-
101100 // RS-encode each recipient's payload and bucket the shards by disperser.
102101 let mut shards_by_recipient: BTreeMap < PartyId , Vec < Vec < Shard > > > = payloads_by_recipient
103102 . iter ( )
104103 . map ( |( & i, payload) | {
105- let shards = code . encode ( payload) ?;
104+ let shards = self . coder . encode ( payload) ?;
106105 let by_disperser = self . nodes . collect_to_nodes ( shards. into_iter ( ) ) ?;
107106 Ok ( ( i, by_disperser) )
108107 } )
@@ -111,14 +110,9 @@ impl Avid {
111110 #[ cfg( test) ]
112111 mutate ( & mut shards_by_recipient) ;
113112
114- // Build a single Merkle tree whose leaves are the (recipient, disperser) cells, laid out
115- // recipient-major in sorted order — see [Self::leaf_index].
116- let leaves: Vec < Vec < Shard > > = shards_by_recipient
117- . values ( )
118- . flat_map ( |by_disperser| by_disperser. iter ( ) . cloned ( ) )
119- . collect ( ) ;
120- let tree = MerkleTree :: < Blake2b256 > :: build_from_unserialized ( leaves. iter ( ) )
121- . expect ( "Fails only if serialization fails" ) ;
113+ // Two-level Merkle commitment: per-recipient row trees + a top tree over the row roots.
114+ let rows: Vec < Vec < Vec < Shard > > > = shards_by_recipient. values ( ) . cloned ( ) . collect ( ) ;
115+ let tree = NestedMerkleTree :: < Vec < Shard > > :: new ( & rows) ?;
122116
123117 Ok ( self
124118 . nodes
@@ -133,7 +127,7 @@ impl Avid {
133127 AuthenticatedShards {
134128 shards : by_disperser[ j as usize ] . clone ( ) ,
135129 proof : tree
136- . get_proof ( self . leaf_index ( j , recipient_idx ) )
130+ . get_proof ( recipient_idx , j as usize )
137131 . expect ( "valid leaf index" ) ,
138132 } ,
139133 )
@@ -163,11 +157,7 @@ impl Avid {
163157 . map ( |( recipient_idx, ( _, shards) ) | {
164158 shards
165159 . proof
166- . compute_root (
167- & bcs:: to_bytes ( & shards. shards ) . map_err ( |_| InvalidInput ) ?,
168- self . leaf_index ( my_id, recipient_idx) ,
169- )
170- . ok_or ( InvalidMessage )
160+ . derive_top_root ( & shards. shards , recipient_idx, my_id as usize )
171161 . tap_err ( |err| warn ! ( "avid echo: implied root failed at leaf {my_id}: {err:?}" ) )
172162 } )
173163 . collect :: < FastCryptoResult < Vec < _ > > > ( ) ?;
@@ -190,29 +180,29 @@ impl Avid {
190180 pending_recipients : & BTreeSet < PartyId > ,
191181 receiver : PartyId ,
192182 ) -> FastCryptoResult < VerifiedEcho > {
193- if echo. authenticated_shards . shards . len ( ) != self . nodes . weight_of ( sender) ? as usize {
183+ let auth = & echo. authenticated_shards ;
184+ if auth. shards . len ( ) != self . nodes . weight_of ( sender) ? as usize {
194185 return Err ( InvalidMessage ) ;
195186 }
196187 let receiver_idx = pending_recipients
197188 . iter ( )
198189 . position ( |& id| id == receiver)
199190 . ok_or ( InvalidInput ) ?;
200- echo. authenticated_shards
201- . proof
202- . verify_proof_with_unserialized_leaf (
203- certified_top_root,
204- & echo. authenticated_shards . shards ,
205- self . leaf_index ( sender, receiver_idx) ,
206- ) ?;
191+ auth. proof . verify (
192+ certified_top_root,
193+ & auth. shards ,
194+ receiver_idx,
195+ sender as usize ,
196+ ) ?;
207197 Ok ( VerifiedEcho { echo, sender } )
208198 }
209199
210200 /// 3b. Reconstruct the caller's payload from a quorum of [VerifiedEcho]s, or raise a
211201 /// [Complaint]. Rejects duplicate dispersers, requires `≥ W − 2f` weight (the RS-decode
212202 /// minimum). With well-formed inputs returns `Ok(Ok(payload))` iff the shards decode to a
213- /// payload that passes `payload_ok` and re-encoding it reproduces every supplied echo's
214- /// shards exactly (so the dealer's cells form a valid codeword). Otherwise
215- /// `Ok(Err(Complaint))` over the shards.
203+ /// payload that passes `payload_ok` and re-encoding it rebuilds a row tree whose root
204+ /// matches the dispersal's `recipient_root` (so the dealer's cells form a valid
205+ /// codeword). Otherwise `Ok(Err(Complaint))` over the shards.
216206 pub fn decode_or_complain (
217207 & self ,
218208 echoes : & [ VerifiedEcho ] ,
@@ -238,18 +228,28 @@ impl Avid {
238228 Ok ( p) if payload_ok ( & p) => p,
239229 _ => return Ok ( Err ( Complaint { shards } ) ) ,
240230 } ;
241- // Re-encode and check every supplied echo's shards lie on the same codeword. Each echo's
242- // shards were already pinned to the certified `top_root` by [Self::verify_echo], so any
243- // mismatch here proves the dealer committed cells that are not a valid codeword.
244- let re_encoded = self
231+
232+ // Re-encode the payload and rebuild the row's Merkle tree.
233+ let re_encoded: Vec < Vec < Shard > > = self
245234 . nodes
246235 . collect_to_nodes ( self . coder . encode ( & payload) ?. into_iter ( ) ) ?;
247- if shards
248- . iter ( )
249- . any ( |( id, auth) | auth. shards != re_encoded[ * id as usize ] )
236+
237+ // Take the expected recipient root from any verified echo's proof, since they all share the same top root and row index.
238+ let ( sender, authenticated_shards) =
239+ shards. iter ( ) . next ( ) . expect ( "non-empty by check above" ) ;
240+ let expected_recipient_root = authenticated_shards
241+ . proof
242+ . row_proof
243+ . compute_root (
244+ & bcs:: to_bytes ( & authenticated_shards. shards ) . map_err ( |_| InvalidInput ) ?,
245+ * sender as usize ,
246+ )
247+ . ok_or ( InvalidMessage ) ?;
248+ if NestedMerkleTree :: < Vec < Shard > > :: compute_row_root ( & re_encoded) ? != expected_recipient_root
250249 {
251250 return Ok ( Err ( Complaint { shards } ) ) ;
252251 }
252+
253253 Ok ( Ok ( payload) )
254254 }
255255
@@ -265,14 +265,9 @@ impl Avid {
265265 let Some ( accuser_idx) = pending_recipients. iter ( ) . position ( |& id| id == accuser_id) else {
266266 return Ok ( false ) ;
267267 } ;
268- let shards_verify = complaint. shards . iter ( ) . all ( |( & disperser, shards) | {
269- shards
270- . proof
271- . verify_proof_with_unserialized_leaf (
272- top_root,
273- & shards. shards ,
274- self . leaf_index ( disperser, accuser_idx) ,
275- )
268+ let shards_verify = complaint. shards . iter ( ) . all ( |( & disperser, auth) | {
269+ auth. proof
270+ . verify ( top_root, & auth. shards , accuser_idx, disperser as usize )
276271 . is_ok ( )
277272 } ) ;
278273 if !shards_verify
@@ -308,13 +303,6 @@ impl Avid {
308303 fn required_weight ( & self ) -> u16 {
309304 self . nodes . total_weight ( ) - 2 * self . f
310305 }
311-
312- /// Position of disperser `sender`'s cell for the recipient at `recipient_idx` (its index in
313- /// the dispersal's sorted recipient set) in the flat Merkle tree built by
314- /// [Self::disperse_with_mutation].
315- fn leaf_index ( & self , sender : PartyId , recipient_idx : usize ) -> usize {
316- recipient_idx * self . nodes . num_nodes ( ) + sender as usize
317- }
318306}
319307
320308impl EchoBuilder {
0 commit comments