Skip to main content

moloch_core/
proof.rs

1//! Proof types for Moloch.
2//!
3//! Proofs allow verifying that an event exists in the chain without
4//! having to download the entire chain.
5
6use serde::{Deserialize, Serialize};
7
8use crate::crypto::{hash_pair, Hash};
9use crate::error::{Error, Result};
10use crate::event::EventId;
11
12/// Position of a sibling in a merkle proof.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum Position {
16    /// Sibling is on the left.
17    Left,
18    /// Sibling is on the right.
19    Right,
20}
21
22/// A node in a merkle proof path.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct ProofNode {
25    /// Hash of the sibling node.
26    pub hash: Hash,
27    /// Position of the sibling relative to the path.
28    pub position: Position,
29}
30
31/// Proof that an event is included in a specific block.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct BlockInclusionProof {
34    /// The event being proved.
35    pub event_id: EventId,
36    /// Block height containing the event.
37    pub block_height: u64,
38    /// Merkle path from event to block's events_root.
39    pub path: Vec<ProofNode>,
40    /// The block's events_root (for verification).
41    pub events_root: Hash,
42}
43
44impl BlockInclusionProof {
45    /// Verify this proof.
46    pub fn verify(&self) -> Result<bool> {
47        let mut current = self.event_id.0;
48
49        for node in &self.path {
50            current = match node.position {
51                Position::Left => hash_pair(node.hash, current),
52                Position::Right => hash_pair(current, node.hash),
53            };
54        }
55
56        Ok(current == self.events_root)
57    }
58}
59
60/// MMR (Merkle Mountain Range) proof for chain inclusion.
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct MmrProof {
63    /// Position of the leaf in the MMR.
64    pub leaf_pos: u64,
65    /// The leaf hash.
66    pub leaf_hash: Hash,
67    /// Siblings needed to reconstruct path to peak.
68    pub siblings: Vec<ProofNode>,
69    /// The peak this leaf belongs to.
70    pub peak_hash: Hash,
71    /// Other peaks (for bagging to root).
72    pub peaks: Vec<Hash>,
73    /// The MMR root.
74    pub root: Hash,
75    /// MMR size when proof was generated.
76    pub mmr_size: u64,
77}
78
79impl MmrProof {
80    /// Verify this MMR proof.
81    pub fn verify(&self) -> Result<bool> {
82        // First, verify path to peak
83        let mut current = self.leaf_hash;
84        for node in &self.siblings {
85            current = match node.position {
86                Position::Left => hash_pair(node.hash, current),
87                Position::Right => hash_pair(current, node.hash),
88            };
89        }
90
91        if current != self.peak_hash {
92            return Ok(false);
93        }
94
95        // Then verify bagging of peaks
96        let computed_root = bag_peaks(&self.peaks)?;
97        Ok(computed_root == self.root)
98    }
99}
100
101/// Bag peaks into a single root (right to left).
102fn bag_peaks(peaks: &[Hash]) -> Result<Hash> {
103    if peaks.is_empty() {
104        return Ok(Hash::ZERO);
105    }
106
107    let mut root = *peaks.last().unwrap();
108    for peak in peaks.iter().rev().skip(1) {
109        root = hash_pair(*peak, root);
110    }
111    Ok(root)
112}
113
114/// Complete inclusion proof (block + MMR).
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct InclusionProof {
117    /// Proof of inclusion in the block.
118    pub block_proof: BlockInclusionProof,
119    /// Proof of block inclusion in the chain (via MMR).
120    pub chain_proof: MmrProof,
121}
122
123impl InclusionProof {
124    /// Verify the complete inclusion proof.
125    pub fn verify(&self) -> Result<bool> {
126        // Verify event is in block
127        if !self.block_proof.verify()? {
128            return Ok(false);
129        }
130
131        // Verify the block proof's events_root matches the MMR leaf
132        // (The MMR stores events_root for each block)
133        if self.block_proof.events_root != self.chain_proof.leaf_hash {
134            return Ok(false);
135        }
136
137        // Verify block is in chain
138        self.chain_proof.verify()
139    }
140
141    /// Get the event ID this proof is for.
142    pub fn event_id(&self) -> EventId {
143        self.block_proof.event_id
144    }
145
146    /// Get the block height.
147    pub fn block_height(&self) -> u64 {
148        self.block_proof.block_height
149    }
150
151    /// Get the MMR root this proof is against.
152    pub fn mmr_root(&self) -> Hash {
153        self.chain_proof.root
154    }
155}
156
157/// Proof that one chain state is a prefix of another.
158///
159/// This is used for auditing: an auditor can verify that the chain
160/// they observed previously is consistent with the current chain.
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162pub struct ConsistencyProof {
163    /// Old MMR size (number of leaves).
164    pub old_size: u64,
165    /// New MMR size.
166    pub new_size: u64,
167    /// Old MMR root.
168    pub old_root: Hash,
169    /// New MMR root.
170    pub new_root: Hash,
171    /// Proof nodes (old peaks that can be derived from new tree).
172    pub proof: Vec<Hash>,
173}
174
175impl ConsistencyProof {
176    /// Verify this consistency proof.
177    pub fn verify(&self) -> Result<bool> {
178        if self.old_size > self.new_size {
179            return Err(Error::invalid_proof("old_size > new_size"));
180        }
181
182        if self.old_size == self.new_size {
183            return Ok(self.old_root == self.new_root);
184        }
185
186        // For a valid MMR consistency proof:
187        // - All old peaks must be present in the new tree
188        // - We can reconstruct old_root from proof + new tree structure
189        //
190        // This is a simplified check - full implementation would
191        // reconstruct the old peaks from the proof nodes.
192        //
193        // For now, we verify structure is plausible.
194        if self.proof.is_empty() {
195            return Ok(false);
196        }
197
198        // The first proof element should help us find old peaks
199        // Full verification requires MMR structure knowledge
200        Ok(true) // Placeholder - real impl needs MMR crate
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use crate::crypto::hash;
208
209    #[test]
210    fn test_block_inclusion_proof_single_event() {
211        let event_hash = hash(b"event1");
212        let event_id = EventId(event_hash);
213
214        // Single event = root is the event itself
215        let proof = BlockInclusionProof {
216            event_id,
217            block_height: 0,
218            path: vec![],
219            events_root: event_hash,
220        };
221
222        assert!(proof.verify().unwrap());
223    }
224
225    #[test]
226    fn test_block_inclusion_proof_two_events() {
227        let e1 = hash(b"event1");
228        let e2 = hash(b"event2");
229        let root = hash_pair(e1, e2);
230
231        // Prove e1 is in tree
232        let proof = BlockInclusionProof {
233            event_id: EventId(e1),
234            block_height: 0,
235            path: vec![ProofNode {
236                hash: e2,
237                position: Position::Right,
238            }],
239            events_root: root,
240        };
241
242        assert!(proof.verify().unwrap());
243
244        // Prove e2 is in tree
245        let proof2 = BlockInclusionProof {
246            event_id: EventId(e2),
247            block_height: 0,
248            path: vec![ProofNode {
249                hash: e1,
250                position: Position::Left,
251            }],
252            events_root: root,
253        };
254
255        assert!(proof2.verify().unwrap());
256    }
257
258    #[test]
259    fn test_block_inclusion_proof_four_events() {
260        let e1 = hash(b"event1");
261        let e2 = hash(b"event2");
262        let e3 = hash(b"event3");
263        let e4 = hash(b"event4");
264
265        let h12 = hash_pair(e1, e2);
266        let h34 = hash_pair(e3, e4);
267        let root = hash_pair(h12, h34);
268
269        // Prove e3 is in tree (position 2)
270        let proof = BlockInclusionProof {
271            event_id: EventId(e3),
272            block_height: 5,
273            path: vec![
274                ProofNode {
275                    hash: e4,
276                    position: Position::Right,
277                },
278                ProofNode {
279                    hash: h12,
280                    position: Position::Left,
281                },
282            ],
283            events_root: root,
284        };
285
286        assert!(proof.verify().unwrap());
287    }
288
289    #[test]
290    fn test_invalid_proof_fails() {
291        let e1 = hash(b"event1");
292        let e2 = hash(b"event2");
293        let root = hash_pair(e1, e2);
294
295        // Wrong sibling
296        let proof = BlockInclusionProof {
297            event_id: EventId(e1),
298            block_height: 0,
299            path: vec![ProofNode {
300                hash: hash(b"wrong"),
301                position: Position::Right,
302            }],
303            events_root: root,
304        };
305
306        assert!(!proof.verify().unwrap());
307    }
308
309    #[test]
310    fn test_bag_peaks() {
311        let p1 = hash(b"peak1");
312        let p2 = hash(b"peak2");
313        let p3 = hash(b"peak3");
314
315        // Single peak
316        assert_eq!(bag_peaks(&[p1]).unwrap(), p1);
317
318        // Two peaks: hash(p1, p2)
319        let expected_2 = hash_pair(p1, p2);
320        assert_eq!(bag_peaks(&[p1, p2]).unwrap(), expected_2);
321
322        // Three peaks: hash(p1, hash(p2, p3))
323        let h23 = hash_pair(p2, p3);
324        let expected_3 = hash_pair(p1, h23);
325        assert_eq!(bag_peaks(&[p1, p2, p3]).unwrap(), expected_3);
326    }
327
328    #[test]
329    fn test_mmr_proof_single_leaf() {
330        let leaf = hash(b"leaf");
331
332        let proof = MmrProof {
333            leaf_pos: 0,
334            leaf_hash: leaf,
335            siblings: vec![],
336            peak_hash: leaf,
337            peaks: vec![leaf],
338            root: leaf,
339            mmr_size: 1,
340        };
341
342        assert!(proof.verify().unwrap());
343    }
344}