Skip to main content

telltale_runtime/heap/
merkle.rs

1//! # Merkle Tree Utilities
2//!
3//! Merkle tree construction and verification for heap state.
4//!
5//! ## Overview
6//!
7//! This module provides Merkle tree operations for heap state verification:
8//! - Compute Merkle root from heap resources
9//! - Generate inclusion proofs
10//! - Verify inclusion proofs
11//!
12//! ## Lean Correspondence
13//!
14//! Merkle tree operations are currently Rust-only.
15
16use super::heap_impl::Heap;
17use super::resource::{Resource, ResourceId};
18use sha2::{Digest, Sha256};
19
20/// Direction in a Merkle proof path.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Direction {
23    /// Sibling is on the left
24    Left,
25    /// Sibling is on the right
26    Right,
27}
28
29/// A single step in a Merkle proof.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct ProofStep {
32    /// Direction: is this node a left or right sibling?
33    pub direction: Direction,
34    /// The sibling hash at this level
35    pub sibling_hash: [u8; 32],
36}
37
38/// A Merkle inclusion proof.
39///
40/// The proof consists of sibling hashes from leaf to root.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct MerkleProof {
43    /// The leaf hash being proven
44    pub leaf_hash: [u8; 32],
45    /// Proof path from leaf to root
46    pub path: Vec<ProofStep>,
47    /// The expected root hash
48    pub root: [u8; 32],
49}
50
51impl MerkleProof {
52    /// Verify this proof.
53    pub fn verify(&self) -> bool {
54        let computed_root =
55            self.path
56                .iter()
57                .fold(self.leaf_hash, |current, step| match step.direction {
58                    Direction::Left => hash_pair(&step.sibling_hash, &current),
59                    Direction::Right => hash_pair(&current, &step.sibling_hash),
60                });
61        computed_root == self.root
62    }
63}
64
65/// Hash two child hashes to get parent hash.
66fn hash_pair(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
67    let mut hasher = Sha256::new();
68    hasher.update(left);
69    hasher.update(right);
70    hasher.finalize().into()
71}
72
73/// Compute hash of a (ResourceId, Resource) leaf.
74fn hash_leaf(rid: &ResourceId, resource: &Resource) -> [u8; 32] {
75    let mut hasher = Sha256::new();
76    hasher.update(rid.hash());
77    hasher.update(resource.to_bytes());
78    hasher.finalize().into()
79}
80
81/// Empty tree root (hash of empty).
82fn empty_root() -> [u8; 32] {
83    Sha256::digest([]).into()
84}
85
86/// Merkle tree structure for efficient proof generation.
87#[derive(Debug, Clone)]
88pub struct MerkleTree {
89    /// Root hash
90    pub root: [u8; 32],
91    /// Leaf hashes in order
92    leaves: Vec<[u8; 32]>,
93    /// Internal nodes (for proof generation)
94    /// Stored level by level, bottom up
95    levels: Vec<Vec<[u8; 32]>>,
96}
97
98impl MerkleTree {
99    /// Build a Merkle tree from a list of leaf hashes.
100    pub fn from_leaves(leaves: Vec<[u8; 32]>) -> Self {
101        if leaves.is_empty() {
102            return Self {
103                root: empty_root(),
104                leaves: Vec::new(),
105                levels: Vec::new(),
106            };
107        }
108
109        let mut levels = vec![leaves.clone()];
110        let mut current_level = leaves.clone();
111
112        while current_level.len() > 1 {
113            // Pad to even if needed
114            if current_level.len() % 2 == 1 {
115                // Safety: current_level is non-empty (len > 1 checked above)
116                current_level.push(*current_level.last().expect("non-empty after len check"));
117            }
118
119            // Compute next level
120            let next_level: Vec<[u8; 32]> = current_level
121                .chunks(2)
122                .map(|pair| hash_pair(&pair[0], &pair[1]))
123                .collect();
124
125            levels.push(next_level.clone());
126            current_level = next_level;
127        }
128
129        let root = current_level[0];
130
131        Self {
132            root,
133            leaves,
134            levels,
135        }
136    }
137
138    /// Build a Merkle tree from a heap.
139    pub fn from_heap(heap: &Heap) -> Self {
140        let leaves: Vec<[u8; 32]> = heap
141            .active_resources()
142            .map(|(rid, resource)| hash_leaf(rid, resource))
143            .collect();
144        Self::from_leaves(leaves)
145    }
146
147    /// Get the number of leaves.
148    pub fn size(&self) -> usize {
149        self.leaves.len()
150    }
151
152    /// Generate an inclusion proof for a leaf at the given index.
153    pub fn prove(&self, index: usize) -> Option<MerkleProof> {
154        if index >= self.leaves.len() {
155            return None;
156        }
157
158        let leaf_hash = self.leaves[index];
159        let mut path = Vec::new();
160        let mut current_index = index;
161
162        for level in &self.levels[..self.levels.len().saturating_sub(1)] {
163            let sibling_index = if current_index % 2 == 0 {
164                current_index + 1
165            } else {
166                current_index - 1
167            };
168
169            // Handle odd-length levels (last element duplicated)
170            let sibling_hash = if sibling_index < level.len() {
171                level[sibling_index]
172            } else {
173                level[current_index]
174            };
175
176            let direction = if current_index % 2 == 0 {
177                Direction::Right
178            } else {
179                Direction::Left
180            };
181
182            path.push(ProofStep {
183                direction,
184                sibling_hash,
185            });
186
187            current_index /= 2;
188        }
189
190        Some(MerkleProof {
191            leaf_hash,
192            path,
193            root: self.root,
194        })
195    }
196}
197
198/// Commitment to heap state (roots + counter).
199#[derive(Debug, Clone, PartialEq, Eq)]
200pub struct HeapCommitment {
201    /// Merkle root of resources
202    pub resource_root: [u8; 32],
203    /// Merkle root of nullifiers
204    pub nullifier_root: [u8; 32],
205    /// Allocation counter
206    pub counter: u64,
207}
208
209impl HeapCommitment {
210    /// Compute commitment from a heap.
211    pub fn from_heap(heap: &Heap) -> Self {
212        let resource_leaves: Vec<[u8; 32]> = heap
213            .active_resources()
214            .map(|(rid, resource)| hash_leaf(rid, resource))
215            .collect();
216        let resource_tree = MerkleTree::from_leaves(resource_leaves);
217
218        let nullifier_leaves: Vec<[u8; 32]> = heap.consumed_ids().map(|rid| rid.hash()).collect();
219        let nullifier_tree = MerkleTree::from_leaves(nullifier_leaves);
220
221        Self {
222            resource_root: resource_tree.root,
223            nullifier_root: nullifier_tree.root,
224            counter: heap.alloc_counter(),
225        }
226    }
227
228    /// Hash this commitment to a single value.
229    pub fn hash(&self) -> [u8; 32] {
230        let mut hasher = Sha256::new();
231        hasher.update(self.resource_root);
232        hasher.update(self.nullifier_root);
233        hasher.update(self.counter.to_le_bytes());
234        hasher.finalize().into()
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_empty_tree() {
244        let tree = MerkleTree::from_leaves(vec![]);
245        assert_eq!(tree.root, empty_root());
246        assert_eq!(tree.size(), 0);
247    }
248
249    #[test]
250    fn test_single_leaf() {
251        let leaf = Sha256::digest(b"hello").into();
252        let tree = MerkleTree::from_leaves(vec![leaf]);
253        assert_eq!(tree.root, leaf);
254        assert_eq!(tree.size(), 1);
255    }
256
257    #[test]
258    fn test_two_leaves() {
259        let leaf1: [u8; 32] = Sha256::digest(b"hello").into();
260        let leaf2: [u8; 32] = Sha256::digest(b"world").into();
261        let tree = MerkleTree::from_leaves(vec![leaf1, leaf2]);
262
263        let expected_root = hash_pair(&leaf1, &leaf2);
264        assert_eq!(tree.root, expected_root);
265        assert_eq!(tree.size(), 2);
266    }
267
268    #[test]
269    fn test_four_leaves() {
270        let leaves: Vec<[u8; 32]> = (0_u8..4).map(|i| Sha256::digest([i]).into()).collect();
271        let tree = MerkleTree::from_leaves(leaves.clone());
272
273        let h01 = hash_pair(&leaves[0], &leaves[1]);
274        let h23 = hash_pair(&leaves[2], &leaves[3]);
275        let expected_root = hash_pair(&h01, &h23);
276
277        assert_eq!(tree.root, expected_root);
278        assert_eq!(tree.size(), 4);
279    }
280
281    #[test]
282    fn test_proof_generation_and_verification() {
283        let leaves: Vec<[u8; 32]> = (0_u8..4).map(|i| Sha256::digest([i]).into()).collect();
284        let tree = MerkleTree::from_leaves(leaves);
285
286        for i in 0..4 {
287            let proof = tree.prove(i).expect("should generate proof");
288            assert!(proof.verify(), "proof for leaf {} should verify", i);
289        }
290    }
291
292    #[test]
293    fn test_proof_for_out_of_bounds() {
294        let leaves: Vec<[u8; 32]> = (0_u8..4).map(|i| Sha256::digest([i]).into()).collect();
295        let tree = MerkleTree::from_leaves(leaves);
296
297        assert!(tree.prove(4).is_none());
298        assert!(tree.prove(100).is_none());
299    }
300
301    #[test]
302    fn test_odd_number_of_leaves() {
303        let leaves: Vec<[u8; 32]> = (0_u8..3).map(|i| Sha256::digest([i]).into()).collect();
304        let tree = MerkleTree::from_leaves(leaves);
305
306        // Should still work with 3 leaves
307        for i in 0..3 {
308            let proof = tree.prove(i).expect("should generate proof");
309            assert!(proof.verify(), "proof for leaf {} should verify", i);
310        }
311    }
312
313    #[test]
314    fn test_heap_merkle_root() {
315        let heap = Heap::new();
316        let (_, heap) = heap.alloc_channel("Alice", "Bob");
317        let (_, heap) = heap.alloc_message("Alice", "Bob", "Hello", vec![], 0);
318
319        let root = MerkleTree::from_heap(&heap).root;
320        assert_ne!(root, empty_root());
321    }
322
323    #[test]
324    fn test_heap_commitment() {
325        let heap = Heap::new();
326        let (rid, heap) = heap.alloc_channel("Alice", "Bob");
327        let heap = heap.consume(&rid).unwrap();
328
329        let commitment = HeapCommitment::from_heap(&heap);
330
331        // Resource root should be empty (resource consumed)
332        // Nullifier root should not be empty (has consumed ID)
333        assert_eq!(commitment.counter, 1);
334    }
335
336    #[test]
337    fn test_commitment_determinism() {
338        let heap1 = Heap::new();
339        let (_, heap1) = heap1.alloc_channel("Alice", "Bob");
340
341        let heap2 = Heap::new();
342        let (_, heap2) = heap2.alloc_channel("Alice", "Bob");
343
344        let c1 = HeapCommitment::from_heap(&heap1);
345        let c2 = HeapCommitment::from_heap(&heap2);
346
347        assert_eq!(c1, c2);
348        assert_eq!(c1.hash(), c2.hash());
349    }
350}