Skip to main content

qssm_utils/
merkle.rs

1use crate::hashing::{hash_domain, DOMAIN_MERKLE_PARENT};
2
3/// Binary Merkle tree over power-of-two leaves. **Proof order**: siblings from leaf level
4/// toward the root (deepest first): index `0` is the leaf’s immediate sibling, then the
5/// next level, up to the sibling below the root.
6#[derive(Debug, Clone)]
7pub struct PositionAwareTree {
8    root: [u8; 32],
9    leaves: Vec<[u8; 32]>,
10    levels: Vec<Vec<[u8; 32]>>,
11}
12
13/// Binary Merkle parent (neutral domain — not MS-specific).
14pub fn merkle_parent(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
15    let mut buf = [0u8; 64];
16    buf[..32].copy_from_slice(left);
17    buf[32..].copy_from_slice(right);
18    hash_domain(DOMAIN_MERKLE_PARENT, &[&buf])
19}
20
21impl PositionAwareTree {
22    /// Pads `leaves` with deterministic padding digests until length is a power of two (min 1).
23    pub fn new(mut leaves: Vec<[u8; 32]>) -> Result<Self, MerkleError> {
24        if leaves.is_empty() {
25            return Err(MerkleError::EmptyLeaves);
26        }
27        let target = leaves.len().next_power_of_two();
28        let pad = hash_domain(DOMAIN_MERKLE_PARENT, &[b"pad"]);
29        while leaves.len() < target {
30            leaves.push(pad);
31        }
32        let mut levels = vec![leaves.clone()];
33        let mut cur = leaves;
34        while cur.len() > 1 {
35            let mut next = Vec::with_capacity(cur.len() / 2);
36            for pair in cur.chunks_exact(2) {
37                next.push(merkle_parent(&pair[0], &pair[1]));
38            }
39            levels.push(next.clone());
40            cur = next;
41        }
42        let root = cur.first().copied().ok_or(MerkleError::EmptyLeaves)?;
43        let leaves = levels.first().cloned().ok_or(MerkleError::EmptyLeaves)?;
44        Ok(Self {
45            root,
46            leaves,
47            levels,
48        })
49    }
50
51    pub fn get_root(&self) -> [u8; 32] {
52        self.root
53    }
54
55    /// `index` is into the **padded** leaf vector (same order as `new` input + padding).
56    pub fn get_proof(&self, index: usize) -> Result<Vec<[u8; 32]>, MerkleError> {
57        let width = self.leaves.len();
58        if index >= width {
59            return Err(MerkleError::IndexOutOfBounds);
60        }
61        let mut proof = Vec::new();
62        let mut idx = index;
63        for level in &self.levels[..self.levels.len() - 1] {
64            let sibling = idx ^ 1;
65            let sib = level
66                .get(sibling)
67                .copied()
68                .ok_or(MerkleError::IndexOutOfBounds)?;
69            proof.push(sib);
70            idx /= 2;
71        }
72        Ok(proof)
73    }
74}
75
76#[non_exhaustive]
77#[derive(Debug, Clone, Copy, thiserror::Error)]
78pub enum MerkleError {
79    #[error("merkle tree requires at least one leaf")]
80    EmptyLeaves,
81    #[error("leaf index out of bounds")]
82    IndexOutOfBounds,
83}
84
85#[cfg(test)]
86#[allow(clippy::needless_range_loop, clippy::manual_is_multiple_of)]
87mod tests {
88    use super::*;
89    use crate::hashing::blake3_hash;
90
91    fn leaf(n: u8) -> [u8; 32] {
92        blake3_hash(&[n])
93    }
94
95    /// Helper: manually verify a proof by walking siblings up to the root.
96    fn verify_proof(
97        root: &[u8; 32],
98        leaf: &[u8; 32],
99        mut index: usize,
100        proof: &[[u8; 32]],
101    ) -> bool {
102        let mut cur = *leaf;
103        for sib in proof {
104            cur = if index % 2 == 0 {
105                merkle_parent(&cur, sib)
106            } else {
107                merkle_parent(sib, &cur)
108            };
109            index /= 2;
110        }
111        cur == *root
112    }
113
114    #[test]
115    fn single_leaf_root_is_leaf() {
116        let l = leaf(42);
117        let tree = PositionAwareTree::new(vec![l]).unwrap();
118        assert_eq!(tree.get_root(), l);
119        let proof = tree.get_proof(0).unwrap();
120        assert!(proof.is_empty());
121    }
122
123    #[test]
124    fn two_leaf_roundtrip() {
125        let leaves = vec![leaf(0), leaf(1)];
126        let tree = PositionAwareTree::new(leaves.clone()).unwrap();
127        let expected_root = merkle_parent(&leaves[0], &leaves[1]);
128        assert_eq!(tree.get_root(), expected_root);
129        for i in 0..2 {
130            let proof = tree.get_proof(i).unwrap();
131            assert!(verify_proof(&tree.get_root(), &leaves[i], i, &proof));
132        }
133    }
134
135    #[test]
136    fn three_leaf_pads_to_four() {
137        let leaves = vec![leaf(0), leaf(1), leaf(2)];
138        let tree = PositionAwareTree::new(leaves.clone()).unwrap();
139        // Should pad to 4 leaves; proof length = log2(4) = 2
140        let proof = tree.get_proof(0).unwrap();
141        assert_eq!(proof.len(), 2);
142        for i in 0..3 {
143            let proof = tree.get_proof(i).unwrap();
144            assert!(verify_proof(&tree.get_root(), &leaves[i], i, &proof));
145        }
146    }
147
148    #[test]
149    fn five_leaf_pads_to_eight() {
150        let leaves: Vec<[u8; 32]> = (0u8..5).map(leaf).collect();
151        let tree = PositionAwareTree::new(leaves.clone()).unwrap();
152        // Should pad to 8 leaves; proof length = log2(8) = 3
153        let proof = tree.get_proof(0).unwrap();
154        assert_eq!(proof.len(), 3);
155        for i in 0..5 {
156            let proof = tree.get_proof(i).unwrap();
157            assert!(verify_proof(&tree.get_root(), &leaves[i], i, &proof));
158        }
159    }
160
161    #[test]
162    fn determinism() {
163        let leaves: Vec<[u8; 32]> = (0u8..7).map(leaf).collect();
164        let t1 = PositionAwareTree::new(leaves.clone()).unwrap();
165        let t2 = PositionAwareTree::new(leaves).unwrap();
166        assert_eq!(t1.get_root(), t2.get_root());
167    }
168
169    #[test]
170    fn empty_leaves_error() {
171        let err = PositionAwareTree::new(vec![]).unwrap_err();
172        assert!(matches!(err, MerkleError::EmptyLeaves));
173    }
174
175    #[test]
176    fn index_out_of_bounds_error() {
177        let tree = PositionAwareTree::new(vec![leaf(0), leaf(1)]).unwrap();
178        let err = tree.get_proof(2).unwrap_err();
179        assert!(matches!(err, MerkleError::IndexOutOfBounds));
180    }
181
182    #[test]
183    fn parent_non_commutativity() {
184        let a = leaf(0);
185        let b = leaf(1);
186        assert_ne!(merkle_parent(&a, &b), merkle_parent(&b, &a));
187    }
188
189    #[test]
190    fn power_of_two_no_padding() {
191        let leaves: Vec<[u8; 32]> = (0u8..4).map(leaf).collect();
192        let tree = PositionAwareTree::new(leaves.clone()).unwrap();
193        // proof length = log2(4) = 2
194        let proof = tree.get_proof(3).unwrap();
195        assert_eq!(proof.len(), 2);
196        assert!(verify_proof(&tree.get_root(), &leaves[3], 3, &proof));
197    }
198
199    #[test]
200    fn large_tree_128_leaves() {
201        let leaves: Vec<[u8; 32]> = (0u8..128).map(leaf).collect();
202        let tree = PositionAwareTree::new(leaves.clone()).unwrap();
203        // proof length = log2(128) = 7
204        let proof = tree.get_proof(0).unwrap();
205        assert_eq!(proof.len(), 7);
206        // spot-check a few indices
207        for &i in &[0, 1, 63, 64, 127] {
208            let proof = tree.get_proof(i).unwrap();
209            assert!(verify_proof(&tree.get_root(), &leaves[i], i, &proof));
210        }
211    }
212}