miden_core/mast/node/
external.rs

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