Skip to main content

spine/
lib.rs

1//! `spine` — the Merkle Spine: the structural core.
2//!
3//! The structural engine shared by every tree built above it, **epoch-free** and
4//! **topology-agnostic**: **canonicalization** (collapse + promotion, in [`mr`]),
5//! the perfect-subtree **decomposition** and grouping combinator ([`topology`]),
6//! the [`Hasher`] seam, the **abstract inclusion verifier** (proof pinned against
7//! a consumer-supplied skeleton, in [`proof`]), the [`LeafProof`], the general
8//! structural [`Seal`] (peak storage), and the opaque **metadata channel**
9//! ([`Meta`]). It depends on nothing.
10//!
11//! The spine carries no **commitment topology**: how perfect subtrees are bagged
12//! into one root, and what an inclusion proof points at, are owned by each
13//! consumer (the append-only log's mountain range; the mutable tree's rebalanced
14//! fold). The seam between them is the [`SkeletonStep`] interface — the consumer
15//! computes its concrete skeleton, the spine verifier pins a proof against it.
16//!
17//! Activation, the committed epoch timeline, the null-run-extents, the binding
18//! root, and coupling are **not** here — they are the `polydigest` combinator's
19//! facet, which lifts this structural engine across N algorithms over one
20//! shared data substrate. The spine names no epoch concept.
21
22pub mod hasher;
23pub mod mr;
24pub mod proof;
25pub mod subtree;
26pub mod topology;
27
28pub(crate) mod error;
29pub(crate) mod leaf_proof;
30pub(crate) mod metadata;
31pub(crate) mod seal;
32
33pub use error::{Error, Result};
34pub use hasher::Hasher;
35pub use leaf_proof::LeafProof;
36pub use metadata::Meta;
37pub use mr::{count_leaves, evaluate, nary_mr, within_subtree_path};
38pub use proof::{
39    InclusionProof, ProofStep, constant_time_eq, reconstruct_inclusion_root, verify_inclusion,
40    verify_inclusion_path_structure,
41};
42pub use seal::{RunExtent, Seal};
43pub use subtree::Subtree;
44pub use topology::{
45    ARITY_RANGE, BagFn, SkeletonFn, SkeletonStep, fold_frontier, frontier_for_size,
46};
47
48/// Dynamically generate a null digest constant using the hasher.
49#[must_use]
50pub fn null_digest(hasher: &dyn Hasher) -> Vec<u8> {
51    hasher.hash(b"null")
52}
53
54#[cfg(test)]
55mod tests {
56    use sha2::{Digest, Sha256};
57
58    use super::*;
59    use crate::subtree::Subtree;
60
61    #[derive(Debug)]
62    struct Sha256Hasher;
63
64    impl Hasher for Sha256Hasher {
65        fn leaf(&self, data: &[u8]) -> Vec<u8> {
66            Sha256::digest(data).to_vec()
67        }
68
69        fn node(&self, children: &[&[u8]]) -> Vec<u8> {
70            let mut h = Sha256::new();
71            for child in children {
72                h.update(child);
73            }
74            h.finalize().to_vec()
75        }
76
77        fn empty(&self) -> Vec<u8> {
78            Sha256::digest(b"").to_vec()
79        }
80
81        fn hash(&self, data: &[u8]) -> Vec<u8> {
82            Sha256::digest(data).to_vec()
83        }
84
85        fn clone_box(&self) -> Box<dyn Hasher> {
86            Box::new(Sha256Hasher)
87        }
88    }
89
90    /// A child-tree root carried as `Subtree::Leaf` is indistinguishable from
91    /// a raw-payload leaf carrying the same bytes — the opacity contract.
92    /// The spine sees identical structure regardless of origin.
93    #[test]
94    fn leaf_carrying_child_root_is_opaque() {
95        let hasher = Sha256Hasher;
96        let child_root = evaluate(
97            &hasher,
98            &Subtree::Node(vec![
99                Subtree::Leaf(b"a".to_vec()),
100                Subtree::Leaf(b"b".to_vec()),
101            ]),
102        );
103
104        // A leaf wrapping a child root is structurally identical to a raw-payload
105        // leaf carrying the same bytes — no origin tag exists to distinguish them.
106        let via_child_root = Subtree::Leaf(child_root.clone());
107        let via_raw_payload = Subtree::Leaf(child_root.clone());
108        assert_eq!(via_child_root, via_raw_payload);
109
110        // The spine evaluates both identically: same digest, no branching on origin.
111        assert_eq!(
112            evaluate(&hasher, &via_child_root),
113            evaluate(&hasher, &via_raw_payload)
114        );
115    }
116}