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, text},
7};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use super::{MastForestContributor, MastNodeExt};
12use crate::{
13    Felt, Word,
14    mast::{MastForest, MastForestError, MastNodeId},
15    utils::LookupByIdx,
16};
17
18// EXTERNAL NODE
19// ================================================================================================
20
21/// Node for referencing procedures not present in a given [`MastForest`] (hence "external").
22///
23/// External nodes can be used to verify the integrity of a program's hash while keeping parts of
24/// the program secret. They also allow a program to refer to a well-known procedure that was not
25/// compiled with the program (e.g. a procedure in the core library).
26///
27/// The hash of an external node is the hash of the procedure it represents, such that an external
28/// node can be swapped with the actual subtree that it represents without changing the MAST root.
29#[derive(Clone, Debug, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
32pub struct ExternalNode {
33    digest: Word,
34}
35
36// PRETTY PRINTING
37// ================================================================================================
38
39impl ExternalNode {
40    pub(super) fn to_display<'a>(&'a self, _mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
41        self.clone()
42    }
43
44    pub(super) fn to_pretty_print<'a>(
45        &'a self,
46        _mast_forest: &'a MastForest,
47    ) -> impl PrettyPrint + 'a {
48        self.clone()
49    }
50}
51
52impl PrettyPrint for ExternalNode {
53    fn render(&self) -> Document {
54        const_text("external") + const_text(".") + text(self.digest.as_bytes().to_hex_with_prefix())
55    }
56}
57
58impl fmt::Display for ExternalNode {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        use crate::prettier::PrettyPrint;
61        self.pretty_print(f)
62    }
63}
64
65// MAST NODE TRAIT IMPLEMENTATION
66// ================================================================================================
67
68impl MastNodeExt for ExternalNode {
69    /// Returns the commitment to the MAST node referenced by this external node.
70    ///
71    /// The hash of an external node is the hash of the procedure it represents, such that an
72    /// external node can be swapped with the actual subtree that it represents without changing
73    /// the MAST root.
74    fn digest(&self) -> Word {
75        self.digest
76    }
77
78    fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
79        Box::new(ExternalNode::to_display(self, mast_forest))
80    }
81
82    fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
83        Box::new(ExternalNode::to_pretty_print(self, mast_forest))
84    }
85
86    fn has_children(&self) -> bool {
87        false
88    }
89
90    fn append_children_to(&self, _target: &mut Vec<MastNodeId>) {
91        // No children for external nodes
92    }
93
94    fn for_each_child<F>(&self, _f: F)
95    where
96        F: FnMut(MastNodeId),
97    {
98        // ExternalNode has no children
99    }
100
101    fn domain(&self) -> Felt {
102        panic!("Can't fetch domain for an `External` node.")
103    }
104
105    type Builder = ExternalNodeBuilder;
106
107    fn to_builder(self, _forest: &MastForest) -> Self::Builder {
108        ExternalNodeBuilder::new(self.digest)
109    }
110}
111
112// ARBITRARY IMPLEMENTATION
113// ================================================================================================
114
115#[cfg(all(feature = "arbitrary", test))]
116impl proptest::prelude::Arbitrary for ExternalNode {
117    type Parameters = ();
118
119    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
120        use proptest::prelude::*;
121
122        use crate::Felt;
123
124        // Generate a random Word to use as the procedure hash/digest
125        any::<[u64; 4]>()
126            .prop_map(|[a, b, c, d]| {
127                let word = Word::from([Felt::new_unchecked(a), Felt::new_unchecked(b), Felt::new_unchecked(c), Felt::new_unchecked(d)]);
128                ExternalNodeBuilder::new(word).build()
129            })
130            .no_shrink()  // Pure random values, no meaningful shrinking pattern
131            .boxed()
132    }
133
134    type Strategy = proptest::prelude::BoxedStrategy<Self>;
135}
136
137// ------------------------------------------------------------------------------------------------
138/// Builder for creating [`ExternalNode`] instances.
139#[derive(Debug)]
140pub struct ExternalNodeBuilder {
141    digest: Word,
142}
143
144impl ExternalNodeBuilder {
145    /// Creates a new builder for an ExternalNode with the specified procedure hash.
146    pub fn new(digest: Word) -> Self {
147        Self { digest }
148    }
149
150    /// Builds the ExternalNode.
151    pub fn build(self) -> ExternalNode {
152        ExternalNode { digest: self.digest }
153    }
154}
155
156impl MastForestContributor for ExternalNodeBuilder {
157    fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
158        // Create the node in the forest with Linked variant from the start
159        // Move the data directly without intermediate cloning
160        let node_id = forest
161            .nodes
162            .push(ExternalNode { digest: self.digest }.into())
163            .map_err(|_| MastForestError::TooManyNodes)?;
164
165        Ok(node_id)
166    }
167
168    fn fingerprint_for_node(
169        &self,
170        _forest: &MastForest,
171        _hash_by_node_id: &impl LookupByIdx<MastNodeId, Word>,
172    ) -> Result<Word, MastForestError> {
173        Ok(self.digest)
174    }
175
176    fn remap_children(self, _remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
177        // ExternalNode has no children to remap, so return self unchanged
178        self
179    }
180
181    fn with_digest(mut self, digest: Word) -> Self {
182        self.digest = digest;
183        self
184    }
185}
186
187impl ExternalNodeBuilder {
188    /// Add this node to a forest using relaxed validation.
189    ///
190    /// This method is used during deserialization where nodes may reference child nodes
191    /// that haven't been added to the forest yet. The child node IDs have already been
192    /// validated against the expected final node count during the `try_into_mast_node_builder`
193    /// step, so we can safely skip validation here.
194    ///
195    /// Note: This is not part of the `MastForestContributor` trait because it's only
196    /// intended for internal use during deserialization.
197    pub(in crate::mast) fn add_to_forest_relaxed(
198        self,
199        forest: &mut MastForest,
200    ) -> Result<MastNodeId, MastForestError> {
201        // Create the node in the forest with Linked variant from the start
202        // Move the data directly without intermediate cloning
203        let node_id = forest
204            .nodes
205            .push(ExternalNode { digest: self.digest }.into())
206            .map_err(|_| MastForestError::TooManyNodes)?;
207
208        Ok(node_id)
209    }
210}
211
212#[cfg(any(test, feature = "arbitrary"))]
213impl proptest::prelude::Arbitrary for ExternalNodeBuilder {
214    type Parameters = ExternalNodeBuilderParams;
215    type Strategy = proptest::strategy::BoxedStrategy<Self>;
216
217    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
218        use proptest::prelude::*;
219
220        let _ = params;
221        any::<[u64; 4]>()
222            .prop_map(|[a, b, c, d]| {
223                Word::new([
224                    Felt::new_unchecked(a),
225                    Felt::new_unchecked(b),
226                    Felt::new_unchecked(c),
227                    Felt::new_unchecked(d),
228                ])
229            })
230            .prop_map(Self::new)
231            .boxed()
232    }
233}
234
235/// Parameters for generating ExternalNodeBuilder instances
236#[cfg(any(test, feature = "arbitrary"))]
237#[derive(Clone, Debug, Default)]
238pub struct ExternalNodeBuilderParams {}