1use crate::hashing::{hash_domain, DOMAIN_MERKLE_PARENT};
2
3#[derive(Debug, Clone)]
7pub struct PositionAwareTree {
8 root: [u8; 32],
9 leaves: Vec<[u8; 32]>,
10 levels: Vec<Vec<[u8; 32]>>,
11}
12
13pub 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 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 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 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 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 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 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 let proof = tree.get_proof(0).unwrap();
205 assert_eq!(proof.len(), 7);
206 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}