commonware_storage/merkle/mod.rs
1//! Shared types for Merkle-family data structures (MMR, MMB).
2//!
3//! This module provides generic `Position<F>` and `Location<F>` types parameterized by a
4//! [`Family`] marker trait. Each Merkle family (e.g. MMR, MMB) implements the trait with
5//! its own constants and conversion formulas, while the shared arithmetic, codec, and comparison
6//! logic lives here.
7
8pub mod batch;
9#[cfg(test)]
10pub(crate) mod conformance;
11pub mod hasher;
12#[cfg(feature = "std")]
13mod persisted;
14#[cfg(feature = "std")]
15pub use persisted::{compact, full};
16mod location;
17pub mod mem;
18pub mod mmb;
19pub mod mmr;
20pub mod path;
21mod position;
22mod proof;
23mod read;
24#[cfg(feature = "std")]
25pub mod storage;
26#[cfg(feature = "std")]
27pub mod verification;
28
29use bytes::BufMut;
30use commonware_codec::{Buf, EncodeSize, Read, Write};
31use commonware_cryptography::Digest;
32use core::fmt::Debug;
33pub use location::{Location, LocationRangeExt};
34pub use position::Position;
35pub use proof::{
36 ElementPlan, MAX_PROOF_DIGESTS_PER_ELEMENT, Proof, RangePlan, multi_proof_positions,
37};
38pub use read::Readable;
39use thiserror::Error;
40
41/// Safe upper bound on the number of pinned nodes for any u64-backed Merkle family.
42///
43/// Pinned nodes are produced by [`Family::nodes_to_pin`], whose default
44/// implementation returns the peaks of the sub-structure at the prune
45/// location. A structure with `n` leaves has at most `popcount(n)` peaks, and
46/// since [`Location`] is backed by a `u64`, `n <= u64::MAX` yields at most 64
47/// peaks.
48pub const MAX_PINNED_NODES: usize = 64;
49
50/// Defines the strategy used to fold peaks into the final root digest.
51#[derive(Copy, Clone, Debug, PartialEq, Eq)]
52pub enum Bagging {
53 /// Peaks are bagged forward from oldest to newest, placing the smallest peak at the top.
54 ForwardFold,
55 /// Peaks are bagged backward from newest to oldest, placing the largest peak at the top.
56 BackwardFold,
57}
58
59impl Bagging {
60 /// Domain tag committed by every root folded under this policy.
61 ///
62 /// Tags are unique across policies. Changing one changes every root folded under that policy.
63 pub const fn domain(self) -> u8 {
64 match self {
65 Self::ForwardFold => 0,
66 Self::BackwardFold => 1,
67 }
68 }
69}
70
71/// Marker trait for Merkle-family data structures.
72///
73/// Provides the per-family constants and conversion functions that differentiate
74/// MMR from MMB (or other future Merkle structures). Families capture structural topology
75/// only; bagging is owned by the [`hasher::Hasher`] instance and supplied by the consumer.
76pub trait Family: Copy + Debug + Default + Send + Sync + 'static {
77 /// Domain tag committed by every root of this family.
78 ///
79 /// Tags are unique across families. Changing one changes every root of that family.
80 const DOMAIN: u8;
81
82 /// Maximum valid node count / size.
83 const MAX_NODES: Position<Self>;
84
85 /// Maximum valid leaf count.
86 const MAX_LEAVES: Location<Self>;
87
88 /// Convert a leaf location to its node position, or equivalently, convert a leaf count to the
89 /// corresponding total node count (size).
90 ///
91 /// The public, guaranteed domain is `loc <= MAX_LEAVES`. Some implementations may also accept
92 /// slightly larger temporary values for internal probing (for example, when checking the next
93 /// size boundary), but callers must not rely on that behavior.
94 fn location_to_position(loc: Location<Self>) -> Position<Self>;
95
96 /// Convert a node position to its leaf location, or `None` if the position is not a leaf.
97 /// Equivalently, convert a total node count (size) to the corresponding leaf count, returning
98 /// `None` if the size is not valid.
99 ///
100 /// The caller guarantees `pos <= MAX_NODES`.
101 fn position_to_location(pos: Position<Self>) -> Option<Location<Self>>;
102
103 /// Whether `size` is a valid tree size for this Merkle structure.
104 fn is_valid_size(size: Position<Self>) -> bool;
105
106 /// Returns the largest valid size that is no greater than `size`.
107 fn to_nearest_size(size: Position<Self>) -> Position<Self>;
108
109 /// Return the peaks of a structure with the given `size` as `(position, height)` pairs
110 /// in canonical oldest-to-newest order (suitable for
111 /// [`Hasher::root`](crate::merkle::hasher::Hasher::root)).
112 fn peaks(size: Position<Self>) -> impl Iterator<Item = (Position<Self>, u32)> + Send;
113
114 /// Count the number of oldest peaks that are entirely inactive.
115 ///
116 /// A peak is considered entirely inactive if all of its leaves strictly precede the
117 /// `inactivity_floor`. These peaks can be safely bagged forward into the `grafted_root`.
118 fn inactive_peaks(size: Location<Self>, inactivity_floor: Location<Self>) -> usize {
119 let mut inactive_count = 0;
120 let mut leaf_capacity_sum = 0u64;
121 for (_, height) in Self::peaks(Self::location_to_position(size)) {
122 let capacity = 1u64.checked_shl(height).expect("height excessively large");
123 leaf_capacity_sum = leaf_capacity_sum
124 .checked_add(capacity)
125 .expect("capacity overflow");
126 if leaf_capacity_sum <= *inactivity_floor {
127 inactive_count += 1;
128 } else {
129 break;
130 }
131 }
132 inactive_count
133 }
134
135 /// Compute positions of nodes that must be pinned when pruning to `prune_loc`.
136 ///
137 /// Pinned nodes are the minimal set of pruned digests required to continue growing the
138 /// structure and recomputing its root after pruning. The default implementation returns the
139 /// peaks of the sub-structure at `prune_loc`, which is sufficient for both root computation and
140 /// re-merkleization of retained leaves. Implementations may override this if their family
141 /// requires a different canonical pinned-node set for the pruning boundary.
142 ///
143 /// # Panics
144 ///
145 /// Implementations panic if `prune_loc` is invalid (i.e., exceeds
146 /// [`MAX_LEAVES`](Self::MAX_LEAVES)). Callers must validate inputs before calling.
147 fn nodes_to_pin(prune_loc: Location<Self>) -> impl Iterator<Item = Position<Self>> + Send {
148 let prune_pos = Self::location_to_position(prune_loc);
149 Self::peaks(prune_pos)
150 .filter(move |&(pos, _)| pos < prune_pos)
151 .map(|(pos, _)| pos)
152 }
153
154 /// Return the positions of the left and right children of the node at `pos` with the
155 /// given `height`. The caller guarantees `height > 0` (leaves have no children).
156 fn children(pos: Position<Self>, height: u32) -> (Position<Self>, Position<Self>);
157
158 /// Return the heights of the internal nodes that lie between `size_for(N)` and
159 /// `size_for(N+1)`, where `N` is the given leaf count. These are the nodes created
160 /// when the `N`-th leaf is appended.
161 fn parent_heights(leaves: Location<Self>) -> impl Iterator<Item = u32>;
162
163 /// Return the height of the node at `pos`.
164 ///
165 /// # Panics
166 ///
167 /// Implementations may panic if `pos` does not correspond to a node that exists in some valid
168 /// instance of this family (i.e., it is not a position that would appear in a structure of any
169 /// size).
170 fn pos_to_height(pos: Position<Self>) -> u32;
171}
172
173/// Pending-chunk slot for Merkle families that do not carry a pending chunk (e.g. MMR).
174#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
175pub struct Unused;
176
177impl Write for Unused {
178 #[inline]
179 fn write(&self, _: &mut impl BufMut) {}
180}
181
182impl EncodeSize for Unused {
183 #[inline]
184 fn encode_size(&self) -> usize {
185 0
186 }
187}
188
189impl Read for Unused {
190 type Cfg = ();
191
192 #[inline]
193 fn read_cfg(_: &mut impl Buf, _: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
194 Ok(Self)
195 }
196}
197
198impl<D: Digest> TryFrom<Option<D>> for Unused {
199 type Error = commonware_codec::Error;
200
201 #[inline]
202 fn try_from(value: Option<D>) -> Result<Self, Self::Error> {
203 match value {
204 None => Ok(Self),
205 Some(_) => Err(commonware_codec::Error::Invalid(
206 "PendingChunk",
207 "pending chunk present for family without pending chunks",
208 )),
209 }
210 }
211}
212
213#[cfg(feature = "arbitrary")]
214impl arbitrary::Arbitrary<'_> for Unused {
215 fn arbitrary(_: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
216 Ok(Self)
217 }
218}
219
220/// The pending-chunk slot carried by a [Graftable] family's proof and witness types.
221pub trait PendingChunk<D: Digest>:
222 Clone
223 + Debug
224 + Eq
225 + Write
226 + EncodeSize
227 + Read<Cfg = ()>
228 + Send
229 + Sync
230 + TryFrom<Option<D>, Error: Debug>
231{
232 /// View the slot as an `Option<&D>`.
233 fn as_ref(&self) -> Option<&D> {
234 None
235 }
236}
237
238impl<D: Digest> PendingChunk<D> for Option<D> {
239 #[inline]
240 fn as_ref(&self) -> Option<&D> {
241 Self::as_ref(self)
242 }
243}
244
245impl<D: Digest> PendingChunk<D> for Unused {}
246
247/// Extension of [`Family`] with methods needed for grafting bitmap chunks onto a Merkle structure.
248/// Grafting combines an activity bitmap with an ops Merkle structure by hashing bitmap chunks
249/// together with ops subtree roots. These methods provide the coordinate conversions and
250/// chunk-to-peak mappings required by that process.
251pub trait Graftable: Family {
252 /// The pending-chunk slot type for this family's proofs and witnesses.
253 type PendingChunk<D: Digest>: PendingChunk<D>;
254
255 /// Return the nodes that collectively cover the leaf range of a bitmap chunk in a structure of
256 /// the given `size`.
257 ///
258 /// A chunk at index `chunk_idx` with grafting height `grafting_height` covers leaves
259 /// `[chunk_idx << grafting_height, (chunk_idx + 1) << grafting_height)`. The returned nodes
260 /// partition that range: each node's leaf range is entirely within the chunk, and together they
261 /// cover it exactly.
262 ///
263 /// Results are returned in oldest-to-newest (left-to-right) order.
264 ///
265 /// # Panics
266 ///
267 /// Panics if `size` is not a valid size or if the chunk's leaf range exceeds the structure's
268 /// leaf count.
269 fn chunk_peaks(
270 size: Position<Self>,
271 chunk_idx: u64,
272 grafting_height: u32,
273 ) -> impl Iterator<Item = (Position<Self>, u32)> + Send;
274
275 /// Return the deterministic position of the node at `height` whose leftmost leaf is at
276 /// `leaf_start`.
277 ///
278 /// For some families, this position corresponds to a node that physically exists in any
279 /// structure containing those leaves. For others (e.g. MMB with delayed merging), it may be a
280 /// "virtual" position that no actual node occupies, but is still deterministic and unique for
281 /// the given leaf range and height.
282 ///
283 /// Used by grafting to map grafted-structure positions to ops-structure positions for domain
284 /// separation in hash pre-images.
285 ///
286 /// # Panics
287 ///
288 /// Panics if `height` is excessively large (e.g., `>= 63`), or if the resulting position
289 /// computation overflows the bounds of the underlying numeric types.
290 fn subtree_root_position(leaf_start: Location<Self>, height: u32) -> Position<Self>;
291
292 /// Return the location of the leftmost leaf covered by the node at `pos` with `height`. For a
293 /// leaf (height 0), returns its own location.
294 ///
295 /// # Panics
296 ///
297 /// Panics if `height` is excessively large (e.g., `>= 63`), or if an invalid combination of
298 /// `pos` and `height` results in arithmetic underflow/overflow.
299 fn leftmost_leaf(pos: Position<Self>, height: u32) -> Location<Self>;
300
301 /// Return the minimum leaf count at which the node at `pos` with `height` exists in the
302 /// structure.
303 ///
304 /// For families without delayed merging (e.g. MMR), a node exists as soon as all leaves
305 /// in its span have been appended. For families with delayed merging (e.g. MMB), the
306 /// node is created some number of leaf insertions _after_ its last leaf, so the birth
307 /// size is larger. The MMB override accounts for this delay.
308 ///
309 /// This is used by the grafted-tree pruning logic to determine when a chunk-pair's
310 /// parent has been born in the ops tree, which controls when it is safe to prune the
311 /// pair's individual grafted leaves.
312 ///
313 /// # Panics
314 ///
315 /// Panics if `height` is excessively large (e.g., `>= 63`), or if arithmetic overflows.
316 fn peak_birth_size(pos: Position<Self>, height: u32) -> u64 {
317 let leftmost = *Self::leftmost_leaf(pos, height);
318 let width = 1u64.checked_shl(height).expect("height excessively large");
319 leftmost.checked_add(width).expect("birth size overflow")
320 }
321}
322
323/// Errors that can occur when interacting with a Merkle-family data structure.
324#[derive(Debug, Error)]
325pub enum Error<F: Family> {
326 /// The position does not correspond to a leaf node.
327 #[error("{0} is not a leaf")]
328 NonLeaf(Position<F>),
329
330 /// The position exceeds the valid range.
331 #[error("{0} > MAX_NODES")]
332 PositionOverflow(Position<F>),
333
334 /// The location exceeds the valid range.
335 #[error("{0} > MAX_LEAVES")]
336 LocationOverflow(Location<F>),
337
338 /// The range is empty but must contain at least one element.
339 #[error("range is empty")]
340 Empty,
341
342 /// The end of a range is out of bounds.
343 #[error("range end out of bounds: {0}")]
344 RangeOutOfBounds(Location<F>),
345
346 /// The requested size is invalid.
347 #[error("invalid size: {0}")]
348 InvalidSize(u64),
349
350 /// A requested leaf location exceeds the current leaf count.
351 #[error("leaf location out of bounds: {0}")]
352 LeafOutOfBounds(Location<F>),
353
354 /// A requested leaf location repeats the location before it.
355 #[error("duplicate leaf location: {0}")]
356 DuplicateLocation(Location<F>),
357
358 /// A requested leaf location is less than the location before it.
359 #[error("unsorted leaf location: {0}")]
360 UnsortedLocation(Location<F>),
361
362 /// A required node was not available (e.g. pruned).
363 #[error("element pruned: {0}")]
364 ElementPruned(Position<F>),
365
366 /// A required digest was compressed into a synthetic accumulator (e.g. a backward-fold
367 /// suffix accumulator) when the source proof was constructed, so it is not individually
368 /// available. Unlike [`Error::ElementPruned`], the digest still exists in the source
369 /// structure; the caller can recover by supplying it through an explicit witness slice.
370 #[error("digest hidden behind synthetic accumulator: {0}")]
371 CompressedDigest(Position<F>),
372
373 /// The provided pinned node list does not match the expected pruning boundary.
374 #[error("invalid pinned nodes")]
375 InvalidPinnedNodes,
376
377 /// The structure is at none of the states recorded by the batch's chain, so applying the
378 /// batch would combine it with a different history.
379 #[error("stale batch: no matching chain state (size {actual}, fork size {expected})")]
380 StaleBatch {
381 /// The base size when the batch chain was forked.
382 expected: Position<F>,
383 /// The current structure size.
384 actual: Position<F>,
385 },
386
387 /// An unapplied ancestor batch was dropped before this batch was merkleized, so this batch
388 /// lacks that ancestor's changes. Keep an ancestor alive until it is applied or until the
389 /// descendants that will be applied have been merkleized.
390 #[error("ancestor dropped: expected size {expected}, actual size {actual}")]
391 AncestorDropped {
392 /// The size the structure would have after applying the batch and its ancestors.
393 expected: Position<F>,
394 /// The structure's size when the batch was rejected.
395 actual: Position<F>,
396 },
397
398 /// The proof is invalid.
399 #[error("invalid proof")]
400 InvalidProof,
401
402 /// The requested inactive peak count cannot be represented by the current peak list.
403 #[error("inactive peak count {requested} exceeds peak count {peaks}")]
404 InvalidInactivePeaks {
405 /// Requested inactive peak count.
406 requested: usize,
407 /// Number of available peaks.
408 peaks: usize,
409 },
410
411 /// The root does not match the computed root.
412 #[error("root mismatch")]
413 RootMismatch,
414
415 /// A metadata error occurred.
416 #[cfg(feature = "std")]
417 #[error("metadata error: {0}")]
418 Metadata(#[from] crate::metadata::Error),
419
420 /// A journal error occurred.
421 #[cfg(feature = "std")]
422 #[error("journal error: {0}")]
423 Journal(#[from] crate::journal::Error),
424
425 /// A required node is missing.
426 #[error("missing node: {0}")]
427 MissingNode(Position<F>),
428
429 /// Data is corrupted.
430 #[error("data corrupted: {0}")]
431 DataCorrupted(&'static str),
432
433 /// Bit offset is out of bounds.
434 #[error("bit offset {0} out of bounds (size: {1})")]
435 BitOutOfBounds(u64, u64),
436}