miden_core/mast/
node_fingerprint.rs

1use alloc::vec::Vec;
2
3use miden_crypto::hash::{
4    Digest,
5    blake::{Blake3_256, Blake3Digest},
6};
7
8use crate::{
9    LookupByIdx, Operation, Word,
10    mast::{DecoratorId, MastForest, MastForestError, MastNode, MastNodeId, node::MastNodeExt},
11};
12
13// MAST NODE EQUALITY
14// ================================================================================================
15
16pub type DecoratorFingerprint = Blake3Digest<32>;
17
18/// Represents the hash used to test for equality between [`MastNode`]s.
19///
20/// The decorator root will be `None` if and only if there are no decorators attached to the node,
21/// and all children have no decorator roots (meaning that there are no decorators in all the
22/// descendants).
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24pub struct MastNodeFingerprint {
25    mast_root: Word,
26    decorator_root: Option<DecoratorFingerprint>,
27}
28
29// ------------------------------------------------------------------------------------------------
30/// Constructors
31impl MastNodeFingerprint {
32    /// Creates a new [`MastNodeFingerprint`] from the given MAST root with an empty decorator root.
33    pub fn new(mast_root: Word) -> Self {
34        Self { mast_root, decorator_root: None }
35    }
36
37    /// Creates a new [`MastNodeFingerprint`] from the given MAST root and the given
38    /// [`DecoratorFingerprint`].
39    pub fn with_decorator_root(mast_root: Word, decorator_root: DecoratorFingerprint) -> Self {
40        Self {
41            mast_root,
42            decorator_root: Some(decorator_root),
43        }
44    }
45
46    /// Creates a [`MastNodeFingerprint`] from a [`MastNode`].
47    ///
48    /// The `hash_by_node_id` map must contain all children of the node for efficient lookup of
49    /// their fingerprints. This function returns an error if a child of the given `node` is not in
50    /// this map.
51    pub fn from_mast_node(
52        forest: &MastForest,
53        hash_by_node_id: &impl LookupByIdx<MastNodeId, MastNodeFingerprint>,
54        node: &MastNode,
55    ) -> Result<MastNodeFingerprint, MastForestError> {
56        match node {
57            MastNode::Block(node) => {
58                let mut bytes_to_hash = Vec::new();
59
60                // Hash before_enter decorators first
61                for decorator_id in node.before_enter() {
62                    bytes_to_hash.extend(forest[*decorator_id].fingerprint().as_bytes());
63                }
64
65                // Hash op-indexed decorators
66                for (idx, decorator_id) in node.indexed_decorator_iter() {
67                    bytes_to_hash.extend(idx.to_le_bytes());
68                    bytes_to_hash.extend(forest[decorator_id].fingerprint().as_bytes());
69                }
70
71                // Hash after_exit decorators last
72                for decorator_id in node.after_exit() {
73                    bytes_to_hash.extend(forest[*decorator_id].fingerprint().as_bytes());
74                }
75
76                // Add any `Assert`, `U32assert2` and `MpVerify` opcodes present, since these are
77                // not included in the MAST root.
78                for (op_idx, op) in node.operations().enumerate() {
79                    if let Operation::U32assert2(inner_value)
80                    | Operation::Assert(inner_value)
81                    | Operation::MpVerify(inner_value) = op
82                    {
83                        let op_idx: u32 = op_idx
84                            .try_into()
85                            .expect("there are more than 2^{32}-1 operations in basic block");
86
87                        // we include the opcode to differentiate between `Assert` and `U32assert2`
88                        bytes_to_hash.push(op.op_code());
89                        // we include the operation index to distinguish between basic blocks that
90                        // would have the same assert instructions, but in a different order
91                        bytes_to_hash.extend(op_idx.to_le_bytes());
92                        let inner_value = u64::from(*inner_value);
93                        bytes_to_hash.extend(inner_value.to_le_bytes());
94                    }
95                }
96
97                if bytes_to_hash.is_empty() {
98                    Ok(MastNodeFingerprint::new(node.digest()))
99                } else {
100                    let decorator_root = Blake3_256::hash(&bytes_to_hash);
101                    Ok(MastNodeFingerprint::with_decorator_root(node.digest(), decorator_root))
102                }
103            },
104            other_node => {
105                let mut children = Vec::new();
106                other_node.for_each_child(|child_id| children.push(child_id));
107                fingerprint_from_parts(
108                    forest,
109                    hash_by_node_id,
110                    other_node.before_enter(),
111                    other_node.after_exit(),
112                    &children,
113                    other_node.digest(),
114                )
115            },
116        }
117    }
118}
119
120// ------------------------------------------------------------------------------------------------
121/// Accessors
122impl MastNodeFingerprint {
123    pub fn mast_root(&self) -> &Word {
124        &self.mast_root
125    }
126}
127
128fn fingerprint_from_parts(
129    forest: &MastForest,
130    hash_by_node_id: &impl LookupByIdx<MastNodeId, MastNodeFingerprint>,
131    before_enter_ids: &[DecoratorId],
132    after_exit_ids: &[DecoratorId],
133    children_ids: &[MastNodeId],
134    node_digest: Word,
135) -> Result<MastNodeFingerprint, MastForestError> {
136    let pre_decorator_hash_bytes =
137        before_enter_ids.iter().flat_map(|&id| forest[id].fingerprint().as_bytes());
138    let post_decorator_hash_bytes =
139        after_exit_ids.iter().flat_map(|&id| forest[id].fingerprint().as_bytes());
140
141    let children_decorator_roots = children_ids
142        .iter()
143        .filter_map(|child_id| {
144            hash_by_node_id
145                .get(*child_id)
146                .ok_or(MastForestError::ChildFingerprintMissing(*child_id))
147                .map(|child_fingerprint| child_fingerprint.decorator_root)
148                .transpose()
149        })
150        .collect::<Result<Vec<DecoratorFingerprint>, MastForestError>>()?;
151
152    // Reminder: the `MastNodeFingerprint`'s decorator root will be `None` if and only if there are
153    // no decorators attached to the node, and all children have no decorator roots (meaning
154    // that there are no decorators in all the descendants).
155    if pre_decorator_hash_bytes.clone().next().is_none()
156        && post_decorator_hash_bytes.clone().next().is_none()
157        && children_decorator_roots.is_empty()
158    {
159        Ok(MastNodeFingerprint::new(node_digest))
160    } else {
161        let decorator_bytes_to_hash: Vec<u8> = pre_decorator_hash_bytes
162            .chain(post_decorator_hash_bytes)
163            .chain(
164                children_decorator_roots
165                    .into_iter()
166                    .flat_map(|decorator_root| decorator_root.as_bytes()),
167            )
168            .collect();
169
170        let decorator_root = Blake3_256::hash(&decorator_bytes_to_hash);
171        Ok(MastNodeFingerprint::with_decorator_root(node_digest, decorator_root))
172    }
173}
174
175// TESTS
176// ================================================================================================
177
178#[cfg(test)]
179mod tests {
180    use alloc::collections::BTreeMap;
181
182    use super::*;
183    use crate::{
184        Decorator, Felt, Operation,
185        mast::{BasicBlockNode, MastNode},
186    };
187
188    /// Creates a basic block with the given operations
189    fn basic_block_with_ops(ops: Vec<Operation>) -> MastNode {
190        BasicBlockNode::new(ops, Vec::new()).unwrap().into()
191    }
192
193    /// Creates a decorator and returns its ID
194    fn add_trace_decorator(forest: &mut MastForest, value: u8) -> DecoratorId {
195        forest.add_decorator(Decorator::Trace(value.into())).unwrap()
196    }
197
198    #[test]
199    fn basic_block_fingerprint_different_before_decorators() {
200        let mut forest = MastForest::new();
201        let deco1 = add_trace_decorator(&mut forest, 1);
202        let deco2 = add_trace_decorator(&mut forest, 2);
203
204        // Create two identical basic blocks with different before_enter decorators
205        let mut block1 = basic_block_with_ops(vec![Operation::Add, Operation::Mul]);
206        let mut block2 = basic_block_with_ops(vec![Operation::Add, Operation::Mul]);
207
208        block1.append_before_enter(&[deco1]);
209        block2.append_before_enter(&[deco2]);
210
211        // Compute fingerprints
212        let empty_map = BTreeMap::new();
213        let fp1 = MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block1).unwrap();
214        let fp2 = MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block2).unwrap();
215
216        // Fingerprints should be different
217        assert_ne!(
218            fp1, fp2,
219            "Basic blocks with different before_enter decorators should have different fingerprints"
220        );
221    }
222
223    #[test]
224    fn basic_block_fingerprint_different_after_decorators() {
225        let mut forest = MastForest::new();
226        let deco1 = add_trace_decorator(&mut forest, 1);
227        let deco2 = add_trace_decorator(&mut forest, 2);
228
229        // Create two identical basic blocks with different after_exit decorators
230        let mut block1 = basic_block_with_ops(vec![Operation::Add, Operation::Mul]);
231        let mut block2 = basic_block_with_ops(vec![Operation::Add, Operation::Mul]);
232
233        block1.append_after_exit(&[deco1]);
234        block2.append_after_exit(&[deco2]);
235
236        // Compute fingerprints
237        let empty_map = BTreeMap::new();
238        let fp1 = MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block1).unwrap();
239        let fp2 = MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block2).unwrap();
240
241        // Fingerprints should be different
242        assert_ne!(
243            fp1, fp2,
244            "Basic blocks with different after_exit decorators should have different fingerprints"
245        );
246    }
247
248    #[test]
249    fn basic_block_fingerprint_different_assert_opcodes_no_decorators() {
250        let forest = MastForest::new();
251        let error_code = Felt::new(42);
252
253        // Create three basic blocks with different assert opcodes but no decorators
254        let block_assert = basic_block_with_ops(vec![Operation::Assert(error_code)]);
255        let block_u32assert2 = basic_block_with_ops(vec![Operation::U32assert2(error_code)]);
256        let block_mpverify = basic_block_with_ops(vec![Operation::MpVerify(error_code)]);
257
258        // Compute fingerprints
259        let empty_map = BTreeMap::new();
260        let fp_assert =
261            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_assert).unwrap();
262        let fp_u32assert2 =
263            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_u32assert2).unwrap();
264        let fp_mpverify =
265            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_mpverify).unwrap();
266
267        // All fingerprints should be different since the opcodes are different
268        assert_ne!(
269            fp_assert, fp_u32assert2,
270            "Basic blocks with Assert vs U32assert2 should have different fingerprints"
271        );
272        assert_ne!(
273            fp_assert, fp_mpverify,
274            "Basic blocks with Assert vs MpVerify should have different fingerprints"
275        );
276        assert_ne!(
277            fp_u32assert2, fp_mpverify,
278            "Basic blocks with U32assert2 vs MpVerify should have different fingerprints"
279        );
280    }
281
282    #[test]
283    fn basic_block_fingerprint_different_assert_values_no_decorators() {
284        let forest = MastForest::new();
285        let error_code_1 = Felt::new(42);
286        let error_code_2 = Felt::new(123);
287
288        // Create basic blocks with same assert opcode but different inner values, no decorators
289        let block_assert_1 = basic_block_with_ops(vec![Operation::Assert(error_code_1)]);
290        let block_assert_2 = basic_block_with_ops(vec![Operation::Assert(error_code_2)]);
291
292        let block_u32assert2_1 = basic_block_with_ops(vec![Operation::U32assert2(error_code_1)]);
293        let block_u32assert2_2 = basic_block_with_ops(vec![Operation::U32assert2(error_code_2)]);
294
295        let block_mpverify_1 = basic_block_with_ops(vec![Operation::MpVerify(error_code_1)]);
296        let block_mpverify_2 = basic_block_with_ops(vec![Operation::MpVerify(error_code_2)]);
297
298        // Compute fingerprints
299        let empty_map = BTreeMap::new();
300        let fp_assert_1 =
301            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_assert_1).unwrap();
302        let fp_assert_2 =
303            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_assert_2).unwrap();
304
305        let fp_u32assert2_1 =
306            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_u32assert2_1).unwrap();
307        let fp_u32assert2_2 =
308            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_u32assert2_2).unwrap();
309
310        let fp_mpverify_1 =
311            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_mpverify_1).unwrap();
312        let fp_mpverify_2 =
313            MastNodeFingerprint::from_mast_node(&forest, &empty_map, &block_mpverify_2).unwrap();
314
315        // All fingerprints should be different since the inner values are different
316        assert_ne!(
317            fp_assert_1, fp_assert_2,
318            "Basic blocks with Assert operations with different error codes should have different fingerprints"
319        );
320        assert_ne!(
321            fp_u32assert2_1, fp_u32assert2_2,
322            "Basic blocks with U32assert2 operations with different error codes should have different fingerprints"
323        );
324        assert_ne!(
325            fp_mpverify_1, fp_mpverify_2,
326            "Basic blocks with MpVerify operations with different error codes should have different fingerprints"
327        );
328    }
329}