Skip to main content

miden_crypto/merkle/mmr/
proof.rs

1/// The representation of a single Merkle path.
2use alloc::vec::Vec;
3
4use super::{super::MerklePath, MmrError, forest::Forest};
5use crate::Word;
6
7// MMR PROOF
8// ================================================================================================
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct MmrPath {
12    /// The state of the MMR when the MMR path was created.
13    forest: Forest,
14
15    /// The position of the leaf value within the MMR.
16    position: usize,
17
18    /// The Merkle opening, starting from the value's sibling up to and excluding the root of the
19    /// responsible tree.
20    merkle_path: MerklePath,
21}
22
23impl MmrPath {
24    /// Creates a new `MmrPath` with the given forest, position, and merkle path.
25    pub fn new(forest: Forest, position: usize, merkle_path: MerklePath) -> Self {
26        Self { forest, position, merkle_path }
27    }
28
29    /// Returns the state of the MMR when the MMR path was created.
30    pub fn forest(&self) -> Forest {
31        self.forest
32    }
33
34    /// Returns the position of the leaf value within the MMR.
35    pub fn position(&self) -> usize {
36        self.position
37    }
38
39    /// Returns the Merkle opening, starting from the value's sibling up to and excluding the root
40    /// of the responsible tree.
41    pub fn merkle_path(&self) -> &MerklePath {
42        &self.merkle_path
43    }
44
45    /// Converts the leaf global position into a local position that can be used to verify the
46    /// Merkle path.
47    pub fn relative_pos(&self) -> usize {
48        self.forest
49            .leaf_relative_position(self.position)
50            .expect("position must be part of the forest")
51    }
52
53    /// Returns index of the MMR peak against which the Merkle path in this proof can be verified.
54    pub fn peak_index(&self) -> usize {
55        self.forest.tree_index(self.position)
56    }
57
58    /// Returns a new [MmrPath] adjusted for a smaller target forest.
59    ///
60    /// This is useful when receiving authenticated data from a larger MMR and needing to adjust
61    /// the path for a smaller MMR. The path is trimmed to include only the nodes relevant
62    /// for the target forest.
63    ///
64    /// # Errors
65    /// Returns an error if:
66    /// - The target forest does not include this path's position
67    /// - The target forest is larger than the current forest
68    pub fn with_forest(&self, target_forest: Forest) -> Result<MmrPath, MmrError> {
69        // Validate target forest includes the position
70        if target_forest.num_leaves() <= self.position {
71            return Err(MmrError::PositionNotFound(self.position));
72        }
73
74        // Validate target forest is not larger than current forest
75        if target_forest > self.forest {
76            return Err(MmrError::ForestOutOfBounds(
77                target_forest.num_leaves(),
78                self.forest.num_leaves(),
79            ));
80        }
81
82        // Get expected path length for the target forest
83        let target_path_len = target_forest
84            .leaf_to_corresponding_tree(self.position)
85            .expect("position is in target forest") as usize;
86
87        // Trim the merkle path to the target length
88        let trimmed_nodes: Vec<_> =
89            self.merkle_path.nodes().iter().take(target_path_len).copied().collect();
90        let trimmed_path = MerklePath::new(trimmed_nodes);
91
92        Ok(MmrPath::new(target_forest, self.position, trimmed_path))
93    }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct MmrProof {
98    /// The Merkle path data describing how to authenticate the leaf.
99    path: MmrPath,
100
101    /// The leaf value that was opened.
102    leaf: Word,
103}
104
105impl MmrProof {
106    /// Creates a new `MmrProof` with the given path and leaf.
107    pub fn new(path: MmrPath, leaf: Word) -> Self {
108        Self { path, leaf }
109    }
110
111    /// Returns the Merkle path data describing how to authenticate the leaf.
112    pub fn path(&self) -> &MmrPath {
113        &self.path
114    }
115
116    /// Returns the leaf value that was opened.
117    pub fn leaf(&self) -> Word {
118        self.leaf
119    }
120
121    /// Returns the state of the MMR when the proof was created.
122    pub fn forest(&self) -> Forest {
123        self.path.forest()
124    }
125
126    /// Returns the position of the leaf value within the MMR.
127    pub fn position(&self) -> usize {
128        self.path.position()
129    }
130
131    /// Returns the Merkle opening, starting from the value's sibling up to and excluding the root
132    /// of the responsible tree.
133    pub fn merkle_path(&self) -> &MerklePath {
134        self.path.merkle_path()
135    }
136
137    /// Converts the leaf global position into a local position that can be used to verify the
138    /// merkle_path.
139    pub fn relative_pos(&self) -> usize {
140        self.path.relative_pos()
141    }
142
143    /// Returns index of the MMR peak against which the Merkle path in this proof can be verified.
144    pub fn peak_index(&self) -> usize {
145        self.path.peak_index()
146    }
147
148    /// Returns a new [MmrProof] adjusted for a smaller target forest.
149    ///
150    /// This is useful when receiving authenticated data from a larger MMR and needing to adjust
151    /// the proof for a smaller MMR. The path is trimmed to include only the nodes relevant
152    /// for the target forest.
153    ///
154    /// # Errors
155    /// Returns an error if:
156    /// - The target forest does not include this proof's position
157    /// - The target forest is larger than the current forest
158    pub fn with_forest(&self, target_forest: Forest) -> Result<MmrProof, MmrError> {
159        let adjusted_path = self.path.with_forest(target_forest)?;
160        Ok(MmrProof::new(adjusted_path, self.leaf))
161    }
162}
163
164// TESTS
165// ================================================================================================
166
167#[cfg(test)]
168mod tests {
169    use super::{MerklePath, MmrPath, MmrProof};
170    use crate::{
171        Word,
172        merkle::{
173            int_to_node,
174            mmr::{Mmr, forest::Forest},
175        },
176    };
177
178    #[test]
179    fn test_peak_index() {
180        // --- single peak forest ---------------------------------------------
181        let forest = Forest::new(11).unwrap();
182
183        // the first 4 leaves belong to peak 0
184        for position in 0..8 {
185            let proof = make_dummy_proof(forest, position);
186            assert_eq!(proof.peak_index(), 0);
187        }
188
189        // --- forest with non-consecutive peaks ------------------------------
190        let forest = Forest::new(11).unwrap();
191
192        // the first 8 leaves belong to peak 0
193        for position in 0..8 {
194            let proof = make_dummy_proof(forest, position);
195            assert_eq!(proof.peak_index(), 0);
196        }
197
198        // the next 2 leaves belong to peak 1
199        for position in 8..10 {
200            let proof = make_dummy_proof(forest, position);
201            assert_eq!(proof.peak_index(), 1);
202        }
203
204        // the last leaf is the peak 2
205        let proof = make_dummy_proof(forest, 10);
206        assert_eq!(proof.peak_index(), 2);
207
208        // --- forest with consecutive peaks ----------------------------------
209        let forest = Forest::new(7).unwrap();
210
211        // the first 4 leaves belong to peak 0
212        for position in 0..4 {
213            let proof = make_dummy_proof(forest, position);
214            assert_eq!(proof.peak_index(), 0);
215        }
216
217        // the next 2 leaves belong to peak 1
218        for position in 4..6 {
219            let proof = make_dummy_proof(forest, position);
220            assert_eq!(proof.peak_index(), 1);
221        }
222
223        // the last leaf is the peak 2
224        let proof = make_dummy_proof(forest, 6);
225        assert_eq!(proof.peak_index(), 2);
226    }
227
228    fn make_dummy_proof(forest: Forest, position: usize) -> MmrProof {
229        let path = MmrPath::new(forest, position, MerklePath::default());
230        MmrProof::new(path, Word::empty())
231    }
232
233    #[test]
234    fn test_mmr_proof_with_forest() {
235        // Create an MMR with 5 leaves
236        let mut small_mmr = Mmr::new();
237        for i in 0..5 {
238            small_mmr.add(int_to_node(i)).unwrap();
239        }
240        let small_forest = small_mmr.forest();
241
242        // Clone and add 5 more leaves to create larger MMR
243        let mut large_mmr = small_mmr.clone();
244        for i in 5..10 {
245            large_mmr.add(int_to_node(i)).unwrap();
246        }
247
248        // Get proof for position 2 from the larger MMR
249        let large_proof = large_mmr.open(2).unwrap();
250        let small_path_len = small_forest.leaf_to_corresponding_tree(2).unwrap() as u8;
251
252        // Sanity check: larger MMR should have a longer path (otherwise we're not testing trimming)
253        assert!(large_proof.merkle_path().depth() > small_path_len);
254
255        // Adjust proof to smaller forest (should remove 1 node from the path)
256        let adjusted_proof = large_proof.with_forest(small_forest).unwrap();
257        assert_eq!(large_proof.merkle_path().depth() - adjusted_proof.merkle_path().depth(), 1);
258
259        // Verify the adjusted proof is valid in the smaller MMR
260        let peak_idx = adjusted_proof.peak_index();
261        let relative_pos = adjusted_proof.relative_pos();
262        let computed_root = adjusted_proof
263            .merkle_path()
264            .compute_root(relative_pos as u64, adjusted_proof.leaf())
265            .unwrap();
266        assert_eq!(computed_root, small_mmr.peaks().peaks()[peak_idx]);
267    }
268
269    #[test]
270    fn test_mmr_path_with_forest_errors() {
271        // Create a MMR with 7 leaves
272        let mut mmr = Mmr::new();
273        for i in 0..7 {
274            mmr.add(int_to_node(i)).unwrap();
275        }
276        let proof = mmr.open(2).unwrap();
277        let path = proof.path();
278
279        // Error: target forest doesn't include position
280        let small_forest = Forest::new(2).unwrap();
281        assert!(path.with_forest(small_forest).is_err());
282
283        // Error: target forest is larger than current
284        let large_forest = Forest::new(15).unwrap();
285        assert!(path.with_forest(large_forest).is_err());
286
287        // Same forest should work
288        assert!(path.with_forest(mmr.forest()).is_ok());
289    }
290}