Skip to main content

lys_core/merkle/
tree.rs

1//! [`AppendOnlyTree`] — the append-only Merkle log over a SHA-256
2//! `ct_merkle::MemoryBackedTree`.
3//!
4//! Only [`AppendOnlyTree::append`] mutates the tree. There is no API that
5//! deletes, replaces, reorders, or otherwise touches past leaves; the type
6//! exists precisely to make the append-only invariant a property of the
7//! public surface, not just of the underlying RFC 6962 construction.
8//!
9//! ct-merkle's `prove_inclusion` and `prove_consistency` panic on
10//! out-of-range arguments. The wrapper methods pre-check the arguments and
11//! return [`TrustError::MerkleTree`] instead, keeping the no-panic invariant
12//! of this crate intact.
13//!
14//! The tree is generic over the leaf type `L: Serialize`. Leaves are
15//! converted to a deterministic byte representation via `serialize_leaf`
16//! before being pushed into the underlying ct-merkle tree, so the original
17//! `L` is not stored on the tree and the trust crate stays domain-agnostic.
18//!
19//! The [`RawLeaf`] marker selects the parallel raw-byte encoding:
20//! `AppendOnlyTree<RawLeaf>` hashes leaf bytes verbatim
21//! (`SHA-256(0x00 ‖ bytes)` per RFC 6962) via [`AppendOnlyTree::append_raw`]
22//! and never gains the postcard methods, so the two leaf encodings cannot
23//! be mixed in one tree.
24//!
25//! [`HashTree`] is the raw-leaf tree without the leaves: it holds only the
26//! RFC 6962 leaf and interior hashes, is fed leaf hashes rather than bytes,
27//! and proves inclusion and consistency byte-identically to
28//! `AppendOnlyTree<RawLeaf>` over the same leaves. It is the tree for a log
29//! whose leaves live in storage: its memory is at most two hashes per leaf,
30//! and no leaf is hashed by it.
31
32use std::fmt;
33use std::marker::PhantomData;
34
35use ct_merkle::mem_backed_tree::MemoryBackedTree;
36use serde::Serialize;
37use sha2::{Digest, Sha256};
38
39use crate::error::{TrustError, TrustResult};
40use crate::merkle::leaf::{SerializedLeaf, serialize_leaf};
41use crate::merkle::proof::{ConsistencyProof, InclusionProof, RootHash};
42
43/// Append-only Merkle transparency log over any `L: Serialize` leaf.
44///
45/// Each [`Self::append`] serializes the leaf with `postcard` and pushes the
46/// resulting bytes into a `ct_merkle::MemoryBackedTree<Sha256, _>`. The
47/// original `L` is not stored; the tree retains the serialized bytes plus
48/// the internal Merkle nodes.
49///
50/// The empty tree's root is RFC 6962's deterministic zero-leaf root —
51/// `SHA-256("")` paired with `num_leaves = 0`. After `n` appends, [`Self::root`]
52/// returns the SHA-256 Merkle Tree Hash of the `n` leaves with `num_leaves =
53/// n`. Two trees built from the same `Serialize` sequence in the same order
54/// produce the same root.
55///
56/// # The leaf encoding is a FROZEN WIRE CONTRACT
57///
58/// Leaves are hashed via their `postcard` encoding, which is derived
59/// entirely from `L`'s shape: fields, field declaration order, and enum
60/// variant declaration order. Once leaves of `L` exist in a persisted or
61/// published tree, that shape must **never** change — reordering or adding
62/// fields, or reordering enum variants, silently alters the bytes of
63/// historical leaves, so every previously published root and proof stops
64/// verifying with no error and no version signal. Schema evolution
65/// requires a new versioned leaf type or an explicitly versioned envelope.
66/// When long-lived verifiability matters, prefer a leaf type that pins the
67/// payload as pre-encoded bytes (e.g. a struct holding `Vec<u8>`) so the
68/// hashed bytes are under explicit consumer control. See the
69/// [`leaf`](super::leaf) module docs for the full contract.
70pub struct AppendOnlyTree<L> {
71    inner: MemoryBackedTree<Sha256, SerializedLeaf>,
72    // `fn(L)` is the standard "leaf-type tag" marker — it tracks `L` in the
73    // type signature without imposing variance constraints on the wrapper
74    // and without requiring `L: Debug`/`Clone` on the wrapper's derives.
75    _marker: PhantomData<fn(L)>,
76}
77
78impl<L> AppendOnlyTree<L> {
79    /// Builds a new empty tree.
80    ///
81    /// The empty tree's [`Self::root`] is the deterministic zero-leaf root
82    /// (`SHA-256("")` with `num_leaves = 0`) defined by RFC 6962 and
83    /// implemented by ct-merkle.
84    pub fn new() -> Self {
85        Self {
86            inner: MemoryBackedTree::new(),
87            _marker: PhantomData,
88        }
89    }
90
91    /// Returns the number of leaves in the tree.
92    pub fn len(&self) -> u64 {
93        self.inner.len()
94    }
95
96    /// Returns `true` if no leaves have been appended.
97    pub fn is_empty(&self) -> bool {
98        self.inner.is_empty()
99    }
100
101    /// Returns the current root hash, capturing both the SHA-256 Merkle Tree
102    /// Hash and the current leaf count.
103    pub fn root(&self) -> RootHash {
104        RootHash::from_inner(self.inner.root())
105    }
106
107    /// Builds an inclusion proof for the leaf at `leaf_index`.
108    ///
109    /// # Errors
110    ///
111    /// Returns [`TrustError::MerkleTree`] if `leaf_index` is greater than or
112    /// equal to the current tree length, or if the index does not fit in a
113    /// `usize` on this target. Both conditions are pre-checked so the
114    /// underlying ct-merkle call (which would otherwise panic) is never made
115    /// with an invalid argument.
116    pub fn prove_inclusion(&self, leaf_index: u64) -> TrustResult<InclusionProof> {
117        check_inclusion_index(self.inner.len(), leaf_index)?;
118        let idx = usize::try_from(leaf_index).map_err(|_err| TrustError::MerkleTree {
119            reason: format!("inclusion proof leaf index {leaf_index} does not fit in usize"),
120        })?;
121        Ok(InclusionProof::from_inner(self.inner.prove_inclusion(idx)))
122    }
123
124    /// Builds a consistency proof showing that the tree of size `old_size` is
125    /// a prefix of the current tree (which must be exactly `new_size` leaves).
126    ///
127    /// # Errors
128    ///
129    /// Returns [`TrustError::MerkleTree`] if `new_size` does not equal the
130    /// current tree length, `old_size` is zero, `old_size` exceeds `new_size`,
131    /// or the resulting `num_additions` does not fit in a `usize`. These
132    /// checks ensure the underlying ct-merkle call (which would otherwise
133    /// panic when `num_additions >= self.len()`) is never made with an
134    /// invalid argument.
135    pub fn prove_consistency(&self, old_size: u64, new_size: u64) -> TrustResult<ConsistencyProof> {
136        check_consistency_sizes(self.inner.len(), old_size, new_size)?;
137        // old_size > 0 and old_size <= new_size = self.len(), so this never
138        // wraps around.
139        let num_additions = new_size - old_size;
140        let num_additions_usize =
141            usize::try_from(num_additions).map_err(|_err| TrustError::MerkleTree {
142                reason: format!(
143                    "consistency proof num_additions {num_additions} does not fit in usize"
144                ),
145            })?;
146        Ok(ConsistencyProof::from_inner(
147            self.inner.prove_consistency(num_additions_usize),
148        ))
149    }
150}
151
152/// Type-level marker for trees whose leaves are raw bytes hashed verbatim
153/// (leaf hash = `SHA-256(0x00 ‖ bytes)` per RFC 6962).
154///
155/// Uninhabited: never a value, and it deliberately does NOT implement
156/// `Serialize`, so the postcard methods ([`AppendOnlyTree::append`],
157/// [`AppendOnlyTree::reconstruct_from_leaves`]) do not exist on
158/// `AppendOnlyTree<RawLeaf>` — the two leaf encodings cannot be mixed in
159/// one tree even by accident.
160///
161/// **Invariant:** for every leaf appended via
162/// [`AppendOnlyTree::append_raw`],
163/// `leaf_hash = SHA-256(0x00 ‖ leaf-bytes)`; a third party reproduces it
164/// with `(printf '\x00'; cat leaf-file) | shasum -a 256`.
165pub enum RawLeaf {}
166
167impl AppendOnlyTree<RawLeaf> {
168    /// Appends raw bytes verbatim and returns the new tree size.
169    ///
170    /// The bytes are hashed exactly as supplied — no postcard, no length
171    /// prefix — so the RFC 6962 leaf hash is `SHA-256(0x00 ‖ leaf_bytes)`
172    /// (see [`raw_leaf_hash`](super::leaf::raw_leaf_hash)). Infallible:
173    /// there is no serialization step to fail.
174    pub fn append_raw(&mut self, leaf_bytes: &[u8]) -> u64 {
175        self.inner
176            .push(SerializedLeaf::from_raw_bytes(leaf_bytes.to_vec()));
177        self.inner.len()
178    }
179
180    /// Rebuilds a raw-leaf tree from leaves in their original append order.
181    ///
182    /// The reconstruction path for restart and prefix rebuilds, mirroring
183    /// [`AppendOnlyTree::reconstruct_from_leaves`] for the raw encoding.
184    /// Because it reuses [`Self::append_raw`], the rebuilt tree reproduces
185    /// the original root bit-for-bit.
186    pub fn reconstruct_from_raw_leaves<I>(leaves: I) -> Self
187    where
188        I: IntoIterator,
189        I::Item: AsRef<[u8]>,
190    {
191        let mut tree = Self::new();
192        for leaf in leaves {
193            tree.append_raw(leaf.as_ref());
194        }
195        tree
196    }
197}
198
199impl<L: Serialize> AppendOnlyTree<L> {
200    /// Appends `leaf` to the tree and returns the new tree size.
201    ///
202    /// The leaf is serialized via `postcard` before being hashed into the
203    /// tree. The first append returns `1`, the next `2`, and so on.
204    ///
205    /// # Errors
206    ///
207    /// Returns [`TrustError::MerkleTree`] if the leaf cannot be serialized.
208    /// No other failure modes exist at this scale of tree (ct-merkle's
209    /// internal limits are well beyond practical use).
210    pub fn append(&mut self, leaf: L) -> TrustResult<u64> {
211        let serialized = serialize_leaf(&leaf)?;
212        self.inner.push(serialized);
213        Ok(self.inner.len())
214    }
215
216    /// Rebuilds a tree from a sequence of leaves in their original append
217    /// order.
218    ///
219    /// This is the mechanism for reconstructing in-memory tree state from
220    /// persistent storage on restart. Because reconstruction reuses
221    /// [`Self::append`], it goes through the same serialization path and
222    /// therefore reproduces the original root hash bit-for-bit.
223    ///
224    /// # Errors
225    ///
226    /// Returns [`TrustError::MerkleTree`] if any leaf fails to serialize.
227    pub fn reconstruct_from_leaves(leaves: Vec<L>) -> TrustResult<Self> {
228        let mut tree = Self::new();
229        for leaf in leaves {
230            tree.append(leaf)?;
231        }
232        Ok(tree)
233    }
234}
235
236impl<L> Default for AppendOnlyTree<L> {
237    fn default() -> Self {
238        Self::new()
239    }
240}
241
242impl<L> fmt::Debug for AppendOnlyTree<L> {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        f.debug_struct("AppendOnlyTree")
245            .field("num_leaves", &self.inner.len())
246            .finish()
247    }
248}
249
250/// Refuses an inclusion proof for an index outside a tree of `len` leaves.
251fn check_inclusion_index(len: u64, leaf_index: u64) -> TrustResult<()> {
252    if leaf_index >= len {
253        return Err(TrustError::MerkleTree {
254            reason: format!(
255                "inclusion proof requested for leaf index {leaf_index} but tree has {len} leaves"
256            ),
257        });
258    }
259    Ok(())
260}
261
262/// Refuses a consistency proof unless `0 < old_size <= new_size == len`.
263fn check_consistency_sizes(len: u64, old_size: u64, new_size: u64) -> TrustResult<()> {
264    if new_size != len {
265        return Err(TrustError::MerkleTree {
266            reason: format!(
267                "consistency proof requires new_size to equal current tree length: \
268                 new_size={new_size}, current_len={len}"
269            ),
270        });
271    }
272    if old_size == 0 {
273        return Err(TrustError::MerkleTree {
274            reason: "consistency proof requires old_size > 0; \
275                     RFC 6962 has no consistency proof from an empty tree"
276                .to_string(),
277        });
278    }
279    if old_size > new_size {
280        return Err(TrustError::MerkleTree {
281            reason: format!(
282                "consistency proof old_size must be <= new_size: \
283                 old_size={old_size}, new_size={new_size}"
284            ),
285        });
286    }
287    Ok(())
288}
289
290/// RFC 6962 interior-node domain byte.
291const NODE_PREFIX: u8 = 0x01;
292
293/// RFC 6962 interior node: `SHA-256(0x01 ‖ left ‖ right)`.
294fn node_hash(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
295    let mut hasher = Sha256::new();
296    hasher.update([NODE_PREFIX]);
297    hasher.update(left);
298    hasher.update(right);
299    hasher.finalize().into()
300}
301
302/// RFC 6962's split of `n >= 2` leaves: the largest power of two below `n`.
303fn split_point(size: u64) -> u64 {
304    1 << (u64::BITS - 1 - (size - 1).leading_zeros())
305}
306
307/// An append-only RFC 6962 tree that holds hashes and nothing else.
308///
309/// It is fed leaf hashes, `SHA-256(0x00 ‖ leaf-bytes)` computed by whoever
310/// read the leaf, and never sees a leaf's bytes: a log builds it by streaming
311/// its stored leaves through, one at a time, and extends it on append with the
312/// leaf hash it already computed, so no leaf is hashed twice.
313///
314/// # Invariants
315///
316/// - Level `h` holds the root of every complete perfect subtree of height `h`,
317///   left to right: entry `i` is the hash of leaves `i·2^h .. (i+1)·2^h`.
318///   Level 0 is the leaf hashes. So a tree of `n` leaves holds
319///   `2n − popcount(n)` hashes, fewer than two per leaf.
320/// - Every RFC 6962 subtree `MTH(D[a:b])` is either one of those perfect
321///   subtrees or the hash of a perfect left subtree and a smaller right one,
322///   so roots and proofs are computed from the stored hashes alone, and are
323///   byte-identical to a raw-leaf [`AppendOnlyTree`]'s over the same leaves.
324/// - Nothing is removed or replaced: [`Self::push_leaf_hash`] is the only
325///   mutation.
326#[derive(Clone, Default)]
327pub struct HashTree {
328    size: u64,
329    levels: Vec<Vec<[u8; 32]>>,
330}
331
332impl HashTree {
333    /// Builds a new empty tree.
334    pub fn new() -> Self {
335        Self::default()
336    }
337
338    /// Builds an empty tree with room for `leaves` leaves and their interior
339    /// hashes, so a tree streamed to a known size allocates once per level and
340    /// holds no spare capacity. A size beyond this target's address space
341    /// reserves nothing and the tree grows as it is fed.
342    pub fn with_capacity(leaves: u64) -> Self {
343        let mut levels = Vec::new();
344        let mut width = leaves;
345        while width > 0 {
346            levels.push(Vec::with_capacity(
347                usize::try_from(width).unwrap_or_default(),
348            ));
349            width >>= 1;
350        }
351        Self { size: 0, levels }
352    }
353
354    /// Returns the number of leaves in the tree.
355    pub fn len(&self) -> u64 {
356        self.size
357    }
358
359    /// Returns `true` if no leaf has been appended.
360    pub fn is_empty(&self) -> bool {
361        self.size == 0
362    }
363
364    /// The bytes of hash storage the tree holds, spare capacity included.
365    /// Never more than two 32-byte hashes per leaf for a tree built by
366    /// [`Self::with_capacity`] to its size.
367    pub fn retained_bytes(&self) -> usize {
368        self.levels
369            .iter()
370            .map(|level| level.capacity() * std::mem::size_of::<[u8; 32]>())
371            .sum()
372    }
373
374    /// Appends a leaf by its RFC 6962 leaf hash and returns the new tree size.
375    ///
376    /// The new leaf completes one perfect subtree per trailing set bit of the
377    /// old size; each is hashed once, here, and kept.
378    pub fn push_leaf_hash(&mut self, leaf_hash: [u8; 32]) -> u64 {
379        let mut node = leaf_hash;
380        let mut height = 0;
381        loop {
382            if self.levels.len() == height {
383                self.levels.push(Vec::new());
384            }
385            let Some(level) = self.levels.get_mut(height) else {
386                break;
387            };
388            level.push(node);
389            if level.len() % 2 == 1 {
390                break;
391            }
392            let [.., left, right] = level.as_slice() else {
393                break;
394            };
395            node = node_hash(left, right);
396            height += 1;
397        }
398        self.size = self.size.saturating_add(1);
399        self.size
400    }
401
402    /// Returns the current root hash with the current leaf count.
403    ///
404    /// # Errors
405    ///
406    /// Returns [`TrustError::MerkleTree`] if a hash the tree must hold is
407    /// missing, which only a broken invariant can cause.
408    pub fn root(&self) -> TrustResult<RootHash> {
409        let root = if self.size == 0 {
410            Sha256::digest([]).into()
411        } else {
412            self.subtree(0, self.size)?
413        };
414        Ok(RootHash::from_parts(root, self.size))
415    }
416
417    /// Builds the RFC 6962 inclusion proof `PATH(leaf_index, D[n])`.
418    ///
419    /// # Errors
420    ///
421    /// Returns [`TrustError::MerkleTree`] if `leaf_index` is not below the
422    /// tree length, with the same reason [`AppendOnlyTree::prove_inclusion`]
423    /// gives.
424    pub fn prove_inclusion(&self, leaf_index: u64) -> TrustResult<InclusionProof> {
425        check_inclusion_index(self.size, leaf_index)?;
426        let mut path = Vec::new();
427        let (mut start, mut size, mut index) = (0, self.size, leaf_index);
428        while size > 1 {
429            let split = split_point(size);
430            if index < split {
431                path.push(self.subtree(start + split, size - split)?);
432                size = split;
433            } else {
434                path.push(self.subtree(start, split)?);
435                start += split;
436                index -= split;
437                size -= split;
438            }
439        }
440        InclusionProof::try_from_bytes(path.iter().rev().flatten().copied().collect())
441    }
442
443    /// Builds the RFC 6962 consistency proof `PROOF(old_size, D[new_size])`.
444    ///
445    /// # Errors
446    ///
447    /// Returns [`TrustError::MerkleTree`] unless `0 < old_size <= new_size`
448    /// and `new_size` is the tree length, with the same reasons
449    /// [`AppendOnlyTree::prove_consistency`] gives.
450    pub fn prove_consistency(&self, old_size: u64, new_size: u64) -> TrustResult<ConsistencyProof> {
451        check_consistency_sizes(self.size, old_size, new_size)?;
452        let mut path = Vec::new();
453        let (mut start, mut size, mut old, mut whole) = (0, new_size, old_size, true);
454        while old != size {
455            let split = split_point(size);
456            if old <= split {
457                path.push(self.subtree(start + split, size - split)?);
458                size = split;
459            } else {
460                path.push(self.subtree(start, split)?);
461                start += split;
462                old -= split;
463                size -= split;
464                whole = false;
465            }
466        }
467        if !whole {
468            path.push(self.subtree(start, size)?);
469        }
470        ConsistencyProof::try_from_bytes(path.iter().rev().flatten().copied().collect())
471    }
472
473    /// `MTH(D[start : start + size])` for a subtree RFC 6962's recursion
474    /// reaches, whose `start` is a multiple of the largest power of two not
475    /// above `size`.
476    fn subtree(&self, start: u64, size: u64) -> TrustResult<[u8; 32]> {
477        if size.is_power_of_two() {
478            let height = size.trailing_zeros();
479            let node = if start % size == 0 {
480                self.stored(height, start >> height)
481            } else {
482                None
483            };
484            return node.ok_or_else(|| TrustError::MerkleTree {
485                reason: format!(
486                    "hash tree of {} leaves holds no node for leaves {start}..{}",
487                    self.size,
488                    start.saturating_add(size)
489                ),
490            });
491        }
492        if size == 0 {
493            return Err(TrustError::MerkleTree {
494                reason: format!("hash tree asked for an empty subtree at leaf {start}"),
495            });
496        }
497        let split = split_point(size);
498        let left = self.subtree(start, split)?;
499        let right = self.subtree(start + split, size - split)?;
500        Ok(node_hash(&left, &right))
501    }
502
503    /// The stored root of the `index`-th perfect subtree of height `height`.
504    fn stored(&self, height: u32, index: u64) -> Option<[u8; 32]> {
505        let level = self.levels.get(usize::try_from(height).ok()?)?;
506        level.get(usize::try_from(index).ok()?).copied()
507    }
508}
509
510impl fmt::Debug for HashTree {
511    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512        f.debug_struct("HashTree")
513            .field("num_leaves", &self.size)
514            .finish_non_exhaustive()
515    }
516}
517
518#[cfg(test)]
519#[path = "tree_tests.rs"]
520mod tests;
521
522#[cfg(test)]
523#[path = "hash_tree_tests.rs"]
524mod hash_tree_tests;