Skip to main content

miden_core/mast/node/
external.rs

1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4use miden_formatting::{
5    hex::ToHex,
6    prettier::{Document, PrettyPrint, const_text, nl, text},
7};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use super::{MastForestContributor, MastNodeExt};
12use crate::{
13    Felt, Word,
14    mast::{
15        DecoratorId, DecoratorStore, MastForest, MastForestError, MastNodeFingerprint, MastNodeId,
16    },
17    utils::LookupByIdx,
18};
19
20// EXTERNAL NODE
21// ================================================================================================
22
23/// Node for referencing procedures not present in a given [`MastForest`] (hence "external").
24///
25/// External nodes can be used to verify the integrity of a program's hash while keeping parts of
26/// the program secret. They also allow a program to refer to a well-known procedure that was not
27/// compiled with the program (e.g. a procedure in the core library).
28///
29/// The hash of an external node is the hash of the procedure it represents, such that an external
30/// node can be swapped with the actual subtree that it represents without changing the MAST root.
31#[derive(Clone, Debug, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
34pub struct ExternalNode {
35    digest: Word,
36    decorator_store: DecoratorStore,
37}
38
39// PRETTY PRINTING
40// ================================================================================================
41
42impl ExternalNode {
43    pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
44        ExternalNodePrettyPrint { node: self, mast_forest }
45    }
46
47    pub(super) fn to_pretty_print<'a>(
48        &'a self,
49        mast_forest: &'a MastForest,
50    ) -> impl PrettyPrint + 'a {
51        ExternalNodePrettyPrint { node: self, mast_forest }
52    }
53}
54
55struct ExternalNodePrettyPrint<'a> {
56    node: &'a ExternalNode,
57    mast_forest: &'a MastForest,
58}
59
60impl ExternalNodePrettyPrint<'_> {
61    /// Concatenates the provided decorators in a single line. If the list of decorators is not
62    /// empty, prepends `prepend` and appends `append` to the decorator document.
63    fn concatenate_decorators(
64        &self,
65        decorator_ids: &[DecoratorId],
66        prepend: Document,
67        append: Document,
68    ) -> Document {
69        let decorators = decorator_ids
70            .iter()
71            .map(|&decorator_id| self.mast_forest[decorator_id].render())
72            .reduce(|acc, doc| acc + const_text(" ") + doc)
73            .unwrap_or_default();
74
75        if decorators.is_empty() {
76            decorators
77        } else {
78            prepend + decorators + append
79        }
80    }
81
82    fn single_line_pre_decorators(&self) -> Document {
83        self.concatenate_decorators(
84            self.node.before_enter(self.mast_forest),
85            Document::Empty,
86            const_text(" "),
87        )
88    }
89
90    fn single_line_post_decorators(&self) -> Document {
91        self.concatenate_decorators(
92            self.node.after_exit(self.mast_forest),
93            const_text(" "),
94            Document::Empty,
95        )
96    }
97
98    fn multi_line_pre_decorators(&self) -> Document {
99        self.concatenate_decorators(self.node.before_enter(self.mast_forest), Document::Empty, nl())
100    }
101
102    fn multi_line_post_decorators(&self) -> Document {
103        self.concatenate_decorators(self.node.after_exit(self.mast_forest), nl(), Document::Empty)
104    }
105}
106
107impl PrettyPrint for ExternalNodePrettyPrint<'_> {
108    fn render(&self) -> Document {
109        let external = const_text("external")
110            + const_text(".")
111            + text(self.node.digest.as_bytes().to_hex_with_prefix());
112
113        let single_line = self.single_line_pre_decorators()
114            + external.clone()
115            + self.single_line_post_decorators();
116        let multi_line =
117            self.multi_line_pre_decorators() + external + self.multi_line_post_decorators();
118
119        single_line | multi_line
120    }
121}
122
123impl fmt::Display for ExternalNodePrettyPrint<'_> {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        use crate::prettier::PrettyPrint;
126        self.pretty_print(f)
127    }
128}
129
130// MAST NODE TRAIT IMPLEMENTATION
131// ================================================================================================
132
133impl MastNodeExt for ExternalNode {
134    /// Returns the commitment to the MAST node referenced by this external node.
135    ///
136    /// The hash of an external node is the hash of the procedure it represents, such that an
137    /// external node can be swapped with the actual subtree that it represents without changing
138    /// the MAST root.
139    fn digest(&self) -> Word {
140        self.digest
141    }
142
143    /// Returns the decorators to be executed before this node is executed.
144    fn before_enter<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
145        self.decorator_store.before_enter(forest)
146    }
147
148    /// Returns the decorators to be executed after this node is executed.
149    fn after_exit<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
150        self.decorator_store.after_exit(forest)
151    }
152
153    fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
154        Box::new(ExternalNode::to_display(self, mast_forest))
155    }
156
157    fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
158        Box::new(ExternalNode::to_pretty_print(self, mast_forest))
159    }
160
161    fn has_children(&self) -> bool {
162        false
163    }
164
165    fn append_children_to(&self, _target: &mut Vec<MastNodeId>) {
166        // No children for external nodes
167    }
168
169    fn for_each_child<F>(&self, _f: F)
170    where
171        F: FnMut(MastNodeId),
172    {
173        // ExternalNode has no children
174    }
175
176    fn domain(&self) -> Felt {
177        panic!("Can't fetch domain for an `External` node.")
178    }
179
180    type Builder = ExternalNodeBuilder;
181
182    fn to_builder(self, forest: &MastForest) -> Self::Builder {
183        // Extract decorators from decorator_store if in Owned state
184        match self.decorator_store {
185            DecoratorStore::Owned { before_enter, after_exit, .. } => {
186                let mut builder = ExternalNodeBuilder::new(self.digest);
187                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
188                builder
189            },
190            DecoratorStore::Linked { id } => {
191                // Extract decorators from forest storage when in Linked state
192                let before_enter = forest.before_enter_decorators(id).to_vec();
193                let after_exit = forest.after_exit_decorators(id).to_vec();
194                let mut builder = ExternalNodeBuilder::new(self.digest);
195                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
196                builder
197            },
198        }
199    }
200
201    #[cfg(debug_assertions)]
202    fn verify_node_in_forest(&self, forest: &MastForest) {
203        if let Some(id) = self.decorator_store.linked_id() {
204            // Verify that this node is the one stored at the given ID in the forest
205            let self_ptr = self as *const Self;
206            let forest_node = &forest.nodes[id];
207            let forest_node_ptr = match forest_node {
208                crate::mast::MastNode::External(external) => {
209                    external as *const ExternalNode as *const ()
210                },
211                _ => panic!("Node type mismatch at {id:?}"),
212            };
213            let self_as_void = self_ptr as *const ();
214            debug_assert_eq!(
215                self_as_void, forest_node_ptr,
216                "Node pointer mismatch: expected node at {id:?} to be self"
217            );
218        }
219    }
220}
221
222// ARBITRARY IMPLEMENTATION
223// ================================================================================================
224
225#[cfg(all(feature = "arbitrary", test))]
226impl proptest::prelude::Arbitrary for ExternalNode {
227    type Parameters = ();
228
229    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
230        use proptest::prelude::*;
231
232        use crate::Felt;
233
234        // Generate a random Word to use as the procedure hash/digest
235        any::<[u64; 4]>()
236            .prop_map(|[a, b, c, d]| {
237                let word = Word::from([Felt::new_unchecked(a), Felt::new_unchecked(b), Felt::new_unchecked(c), Felt::new_unchecked(d)]);
238                ExternalNodeBuilder::new(word).build()
239            })
240            .no_shrink()  // Pure random values, no meaningful shrinking pattern
241            .boxed()
242    }
243
244    type Strategy = proptest::prelude::BoxedStrategy<Self>;
245}
246
247// ------------------------------------------------------------------------------------------------
248/// Builder for creating [`ExternalNode`] instances with decorators.
249#[derive(Debug)]
250pub struct ExternalNodeBuilder {
251    digest: Word,
252    before_enter: Vec<DecoratorId>,
253    after_exit: Vec<DecoratorId>,
254}
255
256impl ExternalNodeBuilder {
257    /// Creates a new builder for an ExternalNode with the specified procedure hash.
258    pub fn new(digest: Word) -> Self {
259        Self {
260            digest,
261            before_enter: Vec::new(),
262            after_exit: Vec::new(),
263        }
264    }
265
266    /// Builds the ExternalNode with the specified decorators.
267    pub fn build(self) -> ExternalNode {
268        ExternalNode {
269            digest: self.digest,
270            decorator_store: DecoratorStore::new_owned_with_decorators(
271                self.before_enter,
272                self.after_exit,
273            ),
274        }
275    }
276}
277
278impl MastForestContributor for ExternalNodeBuilder {
279    fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
280        // Determine the node ID that will be assigned
281        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
282
283        // Store node-level decorators in the centralized NodeToDecoratorIds for efficient access
284        forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
285
286        // Create the node in the forest with Linked variant from the start
287        // Move the data directly without intermediate cloning
288        let node_id = forest
289            .nodes
290            .push(
291                ExternalNode {
292                    digest: self.digest,
293                    decorator_store: DecoratorStore::Linked { id: future_node_id },
294                }
295                .into(),
296            )
297            .map_err(|_| MastForestError::TooManyNodes)?;
298
299        Ok(node_id)
300    }
301
302    fn fingerprint_for_node(
303        &self,
304        forest: &MastForest,
305        _hash_by_node_id: &impl LookupByIdx<MastNodeId, MastNodeFingerprint>,
306    ) -> Result<MastNodeFingerprint, MastForestError> {
307        // ExternalNode has no children, so we don't need hash_by_node_id
308        // Use the fingerprint_from_parts helper function with empty children array
309        crate::mast::node_fingerprint::fingerprint_from_parts(
310            forest,
311            _hash_by_node_id,
312            &self.before_enter,
313            &self.after_exit,
314            &[],         // ExternalNode has no children
315            self.digest, // ExternalNodeBuilder stores the digest directly
316        )
317    }
318
319    fn remap_children(self, _remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
320        // ExternalNode has no children to remap, so return self unchanged
321        self
322    }
323
324    fn with_before_enter(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
325        self.before_enter = decorators.into();
326        self
327    }
328
329    fn with_after_exit(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
330        self.after_exit = decorators.into();
331        self
332    }
333
334    fn append_before_enter(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
335        self.before_enter.extend(decorators);
336    }
337
338    fn append_after_exit(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
339        self.after_exit.extend(decorators);
340    }
341
342    fn with_digest(mut self, digest: Word) -> Self {
343        self.digest = digest;
344        self
345    }
346}
347
348impl ExternalNodeBuilder {
349    /// Add this node to a forest using relaxed validation.
350    ///
351    /// This method is used during deserialization where nodes may reference child nodes
352    /// that haven't been added to the forest yet. The child node IDs have already been
353    /// validated against the expected final node count during the `try_into_mast_node_builder`
354    /// step, so we can safely skip validation here.
355    ///
356    /// Note: This is not part of the `MastForestContributor` trait because it's only
357    /// intended for internal use during deserialization.
358    pub(in crate::mast) fn add_to_forest_relaxed(
359        self,
360        forest: &mut MastForest,
361    ) -> Result<MastNodeId, MastForestError> {
362        // Determine the node ID that will be assigned
363        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
364
365        // Create the node in the forest with Linked variant from the start
366        // Move the data directly without intermediate cloning
367        let node_id = forest
368            .nodes
369            .push(
370                ExternalNode {
371                    digest: self.digest,
372                    decorator_store: DecoratorStore::Linked { id: future_node_id },
373                }
374                .into(),
375            )
376            .map_err(|_| MastForestError::TooManyNodes)?;
377
378        Ok(node_id)
379    }
380}
381
382#[cfg(any(test, feature = "arbitrary"))]
383impl proptest::prelude::Arbitrary for ExternalNodeBuilder {
384    type Parameters = ExternalNodeBuilderParams;
385    type Strategy = proptest::strategy::BoxedStrategy<Self>;
386
387    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
388        use proptest::prelude::*;
389
390        (
391            any::<[u64; 4]>().prop_map(|[a, b, c, d]| {
392                Word::new([
393                    Felt::new_unchecked(a),
394                    Felt::new_unchecked(b),
395                    Felt::new_unchecked(c),
396                    Felt::new_unchecked(d),
397                ])
398            }),
399            proptest::collection::vec(
400                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
401                0..=params.max_decorators,
402            ),
403            proptest::collection::vec(
404                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
405                0..=params.max_decorators,
406            ),
407        )
408            .prop_map(|(digest, before_enter, after_exit)| {
409                Self::new(digest).with_before_enter(before_enter).with_after_exit(after_exit)
410            })
411            .boxed()
412    }
413}
414
415/// Parameters for generating ExternalNodeBuilder instances
416#[cfg(any(test, feature = "arbitrary"))]
417#[derive(Clone, Debug)]
418pub struct ExternalNodeBuilderParams {
419    pub max_decorators: usize,
420    pub max_decorator_id_u32: u32,
421}
422
423#[cfg(any(test, feature = "arbitrary"))]
424impl Default for ExternalNodeBuilderParams {
425    fn default() -> Self {
426        Self {
427            max_decorators: 4,
428            max_decorator_id_u32: 10,
429        }
430    }
431}