@@ -738,6 +738,70 @@ impl<F: Family, D: Digest> Proof<F, D> {
738738 )
739739 . ok_or ( ReconstructionError :: InvalidProof )
740740 }
741+
742+ /// Authenticate the proven range without reconstructing the full generic Merkle root.
743+ ///
744+ /// This consumes only the sibling digests needed to rebuild the proven range peaks, then returns
745+ /// those peak digests in `collected`. It deliberately does not consume peak-bagging witnesses
746+ /// such as prefix peaks, after peaks, or backward-fold suffix accumulators.
747+ ///
748+ /// Current QMDB grafted proofs use this path because their final root is rebuilt by the wrapper
749+ /// from the collected range peaks plus grafted prefix/suffix witnesses. Including generic
750+ /// peak-bagging witnesses here would create proof bytes that the wrapper root ignores, making
751+ /// those bytes malleable.
752+ pub ( crate ) fn reconstruct_range_collecting < H , E > (
753+ & self ,
754+ hasher : & H ,
755+ elements : & [ E ] ,
756+ start_loc : Location < F > ,
757+ collected : & mut Vec < ( Position < F > , D ) > ,
758+ ) -> Result < ( ) , ReconstructionError >
759+ where
760+ H : Hasher < F , Digest = D > ,
761+ E : AsRef < [ u8 ] > ,
762+ {
763+ if elements. is_empty ( ) {
764+ return Err ( ReconstructionError :: MissingElements ) ;
765+ }
766+ if !start_loc. is_valid_index ( ) {
767+ return Err ( ReconstructionError :: InvalidStartLoc ) ;
768+ }
769+ let end_loc = start_loc
770+ . checked_add ( elements. len ( ) as u64 )
771+ . ok_or ( ReconstructionError :: InvalidEndLoc ) ?;
772+ if end_loc > self . leaves {
773+ return Err ( ReconstructionError :: InvalidEndLoc ) ;
774+ }
775+
776+ let range = start_loc..end_loc;
777+ let bp = Blueprint :: new_using_policy (
778+ self . leaves ,
779+ self . inactive_peaks ,
780+ Bagging :: ForwardFold ,
781+ range,
782+ )
783+ . map_err ( |_| ReconstructionError :: InvalidSize ) ?;
784+
785+ let mut sibling_cursor = 0usize ;
786+ let mut elements_iter = elements. iter ( ) ;
787+ for peak in & bp. range_peaks {
788+ let peak_digest = peak. reconstruct_digest (
789+ hasher,
790+ & bp. range ,
791+ & mut elements_iter,
792+ & self . digests ,
793+ & mut sibling_cursor,
794+ Some ( collected) ,
795+ ) ?;
796+ collected. push ( ( peak. pos , peak_digest) ) ;
797+ }
798+
799+ if elements_iter. next ( ) . is_some ( ) || sibling_cursor != self . digests . len ( ) {
800+ return Err ( ReconstructionError :: ExtraDigests ) ;
801+ }
802+
803+ Ok ( ( ) )
804+ }
741805}
742806
743807/// A perfect binary subtree within a peak, identified by its root position, height,
@@ -1222,6 +1286,52 @@ where
12221286 )
12231287}
12241288
1289+ /// Return the node positions needed by [`build_range_collection_proof`].
1290+ #[ cfg( feature = "std" ) ]
1291+ pub ( crate ) fn range_collection_nodes < F : Family > (
1292+ leaves : Location < F > ,
1293+ inactive_peaks : usize ,
1294+ range : Range < Location < F > > ,
1295+ ) -> Result < Vec < Position < F > > , super :: Error < F > > {
1296+ let bp = Blueprint :: new_using_policy ( leaves, inactive_peaks, Bagging :: ForwardFold , range) ?;
1297+ let mut fetch_nodes = Vec :: new ( ) ;
1298+ for peak in & bp. range_peaks {
1299+ peak. collect_siblings ( & bp. range , & mut fetch_nodes) ;
1300+ }
1301+ Ok ( fetch_nodes)
1302+ }
1303+
1304+ /// Build a proof containing only the sibling digests needed to authenticate the requested range.
1305+ ///
1306+ /// The resulting proof cannot reconstruct a complete generic Merkle root on its own. It is intended
1307+ /// for wrappers that rebuild a custom root from separately supplied peak witnesses, such as current
1308+ /// QMDB grafted proofs.
1309+ #[ cfg( feature = "std" ) ]
1310+ pub ( crate ) fn build_range_collection_proof < F , D , E > (
1311+ leaves : Location < F > ,
1312+ inactive_peaks : usize ,
1313+ range : Range < Location < F > > ,
1314+ get_node : impl Fn ( Position < F > ) -> Option < D > ,
1315+ element_pruned : impl Fn ( Position < F > ) -> E ,
1316+ ) -> Result < Proof < F , D > , E >
1317+ where
1318+ F : Family ,
1319+ D : Digest ,
1320+ E : From < super :: Error < F > > ,
1321+ {
1322+ let fetch_nodes = range_collection_nodes ( leaves, inactive_peaks, range) ?;
1323+ let mut digests = Vec :: with_capacity ( fetch_nodes. len ( ) ) ;
1324+ for pos in fetch_nodes {
1325+ digests. push ( get_node ( pos) . ok_or_else ( || element_pruned ( pos) ) ?) ;
1326+ }
1327+
1328+ Ok ( Proof {
1329+ leaves,
1330+ inactive_peaks,
1331+ digests,
1332+ } )
1333+ }
1334+
12251335/// Returns the positions of the minimal set of nodes whose digests are required to prove the
12261336/// inclusion of the elements at the specified `locations`, using the provided root bagging.
12271337#[ cfg( any( feature = "std" , test) ) ]
0 commit comments