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 crate::prettier::PrettyPrint for ExternalNodePrettyPrint<'_> {
108    fn render(&self) -> crate::prettier::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 {:?} to be self",
217                id
218            );
219        }
220    }
221}
222
223// ARBITRARY IMPLEMENTATION
224// ================================================================================================
225
226#[cfg(all(feature = "arbitrary", test))]
227impl proptest::prelude::Arbitrary for ExternalNode {
228    type Parameters = ();
229
230    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
231        use proptest::prelude::*;
232
233        use crate::Felt;
234
235        // Generate a random Word to use as the procedure hash/digest
236        any::<[u64; 4]>()
237            .prop_map(|[a, b, c, d]| {
238                let word = Word::from([Felt::new(a), Felt::new(b), Felt::new(c), Felt::new(d)]);
239                ExternalNodeBuilder::new(word).build()
240            })
241            .no_shrink()  // Pure random values, no meaningful shrinking pattern
242            .boxed()
243    }
244
245    type Strategy = proptest::prelude::BoxedStrategy<Self>;
246}
247
248// ------------------------------------------------------------------------------------------------
249/// Builder for creating [`ExternalNode`] instances with decorators.
250#[derive(Debug)]
251pub struct ExternalNodeBuilder {
252    digest: Word,
253    before_enter: Vec<DecoratorId>,
254    after_exit: Vec<DecoratorId>,
255}
256
257impl ExternalNodeBuilder {
258    /// Creates a new builder for an ExternalNode with the specified procedure hash.
259    pub fn new(digest: Word) -> Self {
260        Self {
261            digest,
262            before_enter: Vec::new(),
263            after_exit: Vec::new(),
264        }
265    }
266
267    /// Builds the ExternalNode with the specified decorators.
268    pub fn build(self) -> ExternalNode {
269        ExternalNode {
270            digest: self.digest,
271            decorator_store: DecoratorStore::new_owned_with_decorators(
272                self.before_enter,
273                self.after_exit,
274            ),
275        }
276    }
277}
278
279impl MastForestContributor for ExternalNodeBuilder {
280    fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
281        // Determine the node ID that will be assigned
282        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
283
284        // Store node-level decorators in the centralized NodeToDecoratorIds for efficient access
285        forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
286
287        // Create the node in the forest with Linked variant from the start
288        // Move the data directly without intermediate cloning
289        let node_id = forest
290            .nodes
291            .push(
292                ExternalNode {
293                    digest: self.digest,
294                    decorator_store: DecoratorStore::Linked { id: future_node_id },
295                }
296                .into(),
297            )
298            .map_err(|_| MastForestError::TooManyNodes)?;
299
300        Ok(node_id)
301    }
302
303    fn fingerprint_for_node(
304        &self,
305        forest: &MastForest,
306        _hash_by_node_id: &impl LookupByIdx<MastNodeId, MastNodeFingerprint>,
307    ) -> Result<MastNodeFingerprint, MastForestError> {
308        // ExternalNode has no children, so we don't need hash_by_node_id
309        // Use the fingerprint_from_parts helper function with empty children array
310        crate::mast::node_fingerprint::fingerprint_from_parts(
311            forest,
312            _hash_by_node_id,
313            &self.before_enter,
314            &self.after_exit,
315            &[],         // ExternalNode has no children
316            self.digest, // ExternalNodeBuilder stores the digest directly
317        )
318    }
319
320    fn remap_children(self, _remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
321        // ExternalNode has no children to remap, so return self unchanged
322        self
323    }
324
325    fn with_before_enter(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
326        self.before_enter = decorators.into();
327        self
328    }
329
330    fn with_after_exit(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
331        self.after_exit = decorators.into();
332        self
333    }
334
335    fn append_before_enter(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
336        self.before_enter.extend(decorators);
337    }
338
339    fn append_after_exit(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
340        self.after_exit.extend(decorators);
341    }
342
343    fn with_digest(mut self, digest: crate::Word) -> Self {
344        self.digest = digest;
345        self
346    }
347}
348
349impl ExternalNodeBuilder {
350    /// Add this node to a forest using relaxed validation.
351    ///
352    /// This method is used during deserialization where nodes may reference child nodes
353    /// that haven't been added to the forest yet. The child node IDs have already been
354    /// validated against the expected final node count during the `try_into_mast_node_builder`
355    /// step, so we can safely skip validation here.
356    ///
357    /// Note: This is not part of the `MastForestContributor` trait because it's only
358    /// intended for internal use during deserialization.
359    pub(in crate::mast) fn add_to_forest_relaxed(
360        self,
361        forest: &mut MastForest,
362    ) -> Result<MastNodeId, MastForestError> {
363        // Determine the node ID that will be assigned
364        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
365
366        // Create the node in the forest with Linked variant from the start
367        // Move the data directly without intermediate cloning
368        let node_id = forest
369            .nodes
370            .push(
371                ExternalNode {
372                    digest: self.digest,
373                    decorator_store: DecoratorStore::Linked { id: future_node_id },
374                }
375                .into(),
376            )
377            .map_err(|_| MastForestError::TooManyNodes)?;
378
379        Ok(node_id)
380    }
381}
382
383#[cfg(any(test, feature = "arbitrary"))]
384impl proptest::prelude::Arbitrary for ExternalNodeBuilder {
385    type Parameters = ExternalNodeBuilderParams;
386    type Strategy = proptest::strategy::BoxedStrategy<Self>;
387
388    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
389        use proptest::prelude::*;
390
391        (
392            any::<[u64; 4]>().prop_map(|[a, b, c, d]| {
393                miden_crypto::Word::new([
394                    miden_crypto::Felt::new(a),
395                    miden_crypto::Felt::new(b),
396                    miden_crypto::Felt::new(c),
397                    miden_crypto::Felt::new(d),
398                ])
399            }),
400            proptest::collection::vec(
401                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
402                0..=params.max_decorators,
403            ),
404            proptest::collection::vec(
405                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
406                0..=params.max_decorators,
407            ),
408        )
409            .prop_map(|(digest, before_enter, after_exit)| {
410                Self::new(digest).with_before_enter(before_enter).with_after_exit(after_exit)
411            })
412            .boxed()
413    }
414}
415
416/// Parameters for generating ExternalNodeBuilder instances
417#[cfg(any(test, feature = "arbitrary"))]
418#[derive(Clone, Debug)]
419pub struct ExternalNodeBuilderParams {
420    pub max_decorators: usize,
421    pub max_decorator_id_u32: u32,
422}
423
424#[cfg(any(test, feature = "arbitrary"))]
425impl Default for ExternalNodeBuilderParams {
426    fn default() -> Self {
427        Self {
428            max_decorators: 4,
429            max_decorator_id_u32: 10,
430        }
431    }
432}