Skip to main content

miden_core/mast/
mod.rs

1//! MAST forest: a collection of procedures represented as Merkle trees.
2//!
3//! # Deserializing from untrusted sources
4//!
5//! When loading a `MastForest` from bytes you don't fully trust (network, user upload, etc.),
6//! use [`UntrustedMastForest`] instead of calling `MastForest::read_from_bytes` directly:
7//!
8//! ```ignore
9//! use miden_core::mast::UntrustedMastForest;
10//!
11//! let forest = UntrustedMastForest::read_from_bytes(&bytes)?
12//!     .validate()?;
13//! ```
14//!
15//! [`UntrustedMastForest::read_from_bytes`] applies default parsing and validation budgets derived
16//! from the input size. Use [`UntrustedMastForest::read_from_bytes_with_options`] with
17//! [`UntrustedMastForestReadOptions`] to tune the wire byte budget. This limits allocations driven
18//! directly by wire counts while reading the payload. A separate validation helper budget is
19//! derived from it for later allocations needed to materialize and check hashless payloads.
20//!
21//! ```ignore
22//! use miden_core::mast::{UntrustedMastForest, UntrustedMastForestReadOptions};
23//!
24//! let options = UntrustedMastForestReadOptions::new()
25//!     .with_wire_byte_budget(bytes.len());
26//! let forest = UntrustedMastForest::read_from_bytes_with_options(&bytes, options)?
27//!     .validate()?;
28//! ```
29//!
30//! This recomputes all node hashes and checks structural invariants before returning a usable
31//! `MastForest`. Direct deserialization via `MastForest::read_from_bytes` trusts the serialized
32//! hashes and should only be used for data from trusted sources (e.g. compiled locally).
33//!
34//! In practice, the public entry points split into three policies:
35//! - [`MastForest::read_from_bytes`]: trusted full deserialization; rejects hashless payloads and
36//!   trusts serialized non-external digests.
37//! - [`MastForestWireView::new`]: trusted wire-backed cache access; scans only the layout needed
38//!   for random access and rejects hashless payloads.
39//! - [`UntrustedMastForest::read_from_bytes`] and
40//!   [`UntrustedMastForest::read_from_bytes_with_options`]: untrusted paths; parse with bounded
41//!   readers and require [`UntrustedMastForest::validate`] before use.
42
43use alloc::{
44    collections::{BTreeMap, BTreeSet},
45    string::String,
46    sync::Arc,
47    vec::Vec,
48};
49use core::{fmt, ops::Index};
50
51#[cfg(any(test, feature = "arbitrary"))]
52use proptest::prelude::*;
53#[cfg(feature = "serde")]
54use serde::{Deserialize, Serialize};
55
56#[cfg(feature = "serde")]
57use crate::serde::{Deserializable, SliceReader};
58
59mod node;
60#[cfg(any(test, feature = "arbitrary"))]
61pub use node::arbitrary;
62pub(crate) use node::collect_immediate_placements;
63pub use node::{
64    BasicBlockNode, BasicBlockNodeBuilder, CallNode, CallNodeBuilder, DynNode, DynNodeBuilder,
65    ExternalNode, ExternalNodeBuilder, JoinNode, JoinNodeBuilder, LoopNode, LoopNodeBuilder,
66    MastForestContributor, MastNode, MastNodeBuilder, MastNodeContext, MastNodeExt, OP_BATCH_SIZE,
67    OP_GROUP_SIZE, OpBatch, SplitNode, SplitNodeBuilder,
68};
69
70use crate::{
71    Felt, Word,
72    advice::AdviceMap,
73    crypto::hash::Poseidon2,
74    serde::{ByteWriter, DeserializationError, Serializable},
75    utils::{DenseIdMap, Idx, IndexVec, hash_string_to_word},
76};
77
78mod serialization;
79pub use serialization::{
80    AdviceMapView, AdviceValueView, MastForestReadMode, MastForestReadView, MastForestView,
81    MastForestWireView, MastNodeEntry, MastNodeInfo,
82};
83
84mod dense_builder;
85pub use dense_builder::DenseMastForestBuilder;
86
87mod untrusted;
88pub use untrusted::{UntrustedMastForest, UntrustedMastForestReadOptions};
89
90mod merger;
91pub(crate) use merger::MastForestMerger;
92pub use merger::MastForestRootMap;
93
94mod multi_forest_node_iterator;
95pub(crate) use multi_forest_node_iterator::*;
96
97mod node_builder_utils;
98pub use node_builder_utils::build_node_with_remapped_ids;
99
100mod sparse;
101pub use sparse::{MastForestId, SparseMastForest, SparseMastForestBuilder, VisitKind};
102
103#[cfg(test)]
104mod tests;
105
106// MAST FOREST
107// ================================================================================================
108
109/// Represents one or more procedures, represented as a collection of [`MastNode`]s.
110///
111/// A [`MastForest`] does not have an entrypoint, and hence is not executable. A
112/// [`crate::program::Program`] can be built from a [`MastForest`] to specify an entrypoint.
113///
114/// Finalized dense forests keep nodes in final dense order:
115/// - external nodes first, sorted by digest, with no duplicate external digests;
116/// - basic blocks next, in the input order seen by finalization;
117/// - internal nodes last, with every child before its parent and finalization input order as the
118///   tie-breaker.
119///
120/// Serialization expects this in-memory order and validates it before writing.
121///
122/// Normal construction goes through builders. Once finalized, a `MastForest` exposes no append API;
123/// code that changes dense nodes must rebuild and finalize a new forest so ordering, root, and
124/// commitment invariants stay in sync.
125#[derive(Clone, Debug, Default)]
126#[cfg_attr(
127    all(feature = "arbitrary", test),
128    miden_test_serde_macros::serde_test(binary_serde(true))
129)]
130pub struct MastForest {
131    /// All of the nodes local to the trees comprising the MAST forest.
132    nodes: IndexVec<MastNodeId, MastNode>,
133
134    /// Roots of procedures defined within this MAST forest.
135    roots: Vec<MastNodeId>,
136
137    /// Advice map to be loaded into the VM prior to executing procedures from this MAST forest.
138    advice_map: AdviceMap,
139
140    /// Commitments to this MAST forest's roots, external dependencies, and advice map.
141    commitment: MastForestCommitment,
142}
143
144/// Commitment values derived from a MAST forest.
145///
146/// Commitment roles:
147/// - [`MastForest::interface_commitment`] identifies the public procedure roots only.
148/// - [`MastForest::dependency_commitment`] identifies the external dependency digests only.
149/// - [`MastForest::advice_commitment`] identifies the stored advice map only.
150/// - [`MastForest::commitment`] identifies the stored dense forest data: public roots, external
151///   dependencies, and advice. Direct forest-backed static libraries use this value as their source
152///   identity. Package-backed static libraries use the package digest, which is derived from this
153///   forest commitment.
154#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
155struct MastForestCommitment {
156    /// Commitment to the forest's roots, external dependencies, and advice map.
157    commitment: Word,
158
159    /// Commitment to this MAST forest's public procedure roots.
160    interface_commitment: Word,
161
162    /// Commitment to this MAST forest's external dependencies.
163    dependency_commitment: Word,
164
165    /// Commitment to this MAST forest's advice map.
166    advice_commitment: Word,
167}
168
169/// Complete parts needed to construct a finalized [`MastForest`].
170pub(crate) struct MastForestParts {
171    pub nodes: IndexVec<MastNodeId, MastNode>,
172    pub roots: Vec<MastNodeId>,
173    pub advice_map: AdviceMap,
174}
175
176// ------------------------------------------------------------------------------------------------
177/// Constructors
178impl MastForest {
179    /// Creates a new empty [`MastForest`].
180    pub fn new() -> Self {
181        Self {
182            nodes: IndexVec::new(),
183            roots: Vec::new(),
184            advice_map: AdviceMap::default(),
185            commitment: empty_mast_forest_commitment(),
186        }
187    }
188
189    /// Builds a [`MastForest`] from raw parts and validates local structure.
190    ///
191    /// This is hidden because raw dense parts are not a stable public construction API.
192    #[doc(hidden)]
193    pub fn from_raw_parts(
194        nodes: IndexVec<MastNodeId, MastNode>,
195        roots: Vec<MastNodeId>,
196        advice_map: AdviceMap,
197    ) -> Result<Self, MastForestError> {
198        Self::from_parts(MastForestParts { nodes, roots, advice_map })
199    }
200
201    /// Builds a [`MastForest`] from raw parts and returns the node ID remapping applied during
202    /// canonicalization.
203    ///
204    /// This is hidden because raw dense parts and builder-local ID remapping are only used by
205    /// internal builders and tests.
206    #[doc(hidden)]
207    pub fn from_raw_parts_with_id_map(
208        nodes: IndexVec<MastNodeId, MastNode>,
209        roots: Vec<MastNodeId>,
210        advice_map: AdviceMap,
211    ) -> Result<(Self, DenseIdMap<MastNodeId, MastNodeId>), MastForestError> {
212        Self::from_parts_with_id_map(MastForestParts { nodes, roots, advice_map })
213    }
214
215    /// Builds a [`MastForest`] from completed parts.
216    pub(crate) fn from_parts(parts: MastForestParts) -> Result<Self, MastForestError> {
217        Self::from_parts_with_id_map(parts).map(|(forest, _remapping)| forest)
218    }
219
220    pub(crate) fn from_parts_with_id_map(
221        parts: MastForestParts,
222    ) -> Result<(Self, DenseIdMap<MastNodeId, MastNodeId>), MastForestError> {
223        validate_mast_forest_parts_bounds(&parts)?;
224        let (parts, id_remapping) = canonicalize_parts(parts)?;
225
226        let forest = Self {
227            commitment: compute_mast_forest_commitment(
228                &parts.nodes,
229                &parts.roots,
230                &parts.advice_map,
231            ),
232            nodes: parts.nodes,
233            roots: parts.roots,
234            advice_map: parts.advice_map,
235        };
236
237        forest.validate_dense_node_order()?;
238        forest.validate()?;
239        forest.validate_node_hashes()?;
240        Ok((forest, id_remapping))
241    }
242
243    pub(in crate::mast) fn from_trusted_deserialization_parts(
244        parts: MastForestParts,
245    ) -> Result<Self, MastForestError> {
246        validate_mast_forest_parts_bounds(&parts)?;
247        // Trusted dense serialization is expected to have checked this already. Re-check the
248        // cheap ordering invariant here as a precaution before accepting the finalized forest.
249        validate_dense_node_order(&parts.nodes)?;
250        Ok(Self {
251            commitment: compute_mast_forest_commitment(
252                &parts.nodes,
253                &parts.roots,
254                &parts.advice_map,
255            ),
256            nodes: parts.nodes,
257            roots: parts.roots,
258            advice_map: parts.advice_map,
259        })
260    }
261}
262
263// ------------------------------------------------------------------------------------------------
264// Dense forest validation and canonicalization
265
266fn validate_mast_forest_parts_bounds(parts: &MastForestParts) -> Result<(), MastForestError> {
267    if parts.nodes.len() > MastForest::MAX_NODES {
268        return Err(MastForestError::TooManyNodes);
269    }
270
271    let node_count = parts.nodes.len();
272    for &root_id in &parts.roots {
273        if root_id.to_usize() >= node_count {
274            return Err(MastForestError::NodeIdOverflow(root_id, node_count));
275        }
276    }
277
278    Ok(())
279}
280
281/// Canonicalizes dense forest parts and returns the old-to-new node ID remapping.
282///
283/// The final node order is external nodes sorted by digest, then basic blocks in construction
284/// order, then internal nodes with children before parents and construction order as the
285/// tie-breaker.
286fn canonicalize_parts(
287    parts: MastForestParts,
288) -> Result<(MastForestParts, DenseIdMap<MastNodeId, MastNodeId>), MastForestError> {
289    let node_count = parts.nodes.len();
290    let ordered_ids = final_dense_node_order(&parts.nodes)?;
291
292    let mut remapping = DenseIdMap::with_len(node_count);
293    for (new_index, old_id) in ordered_ids.iter().copied().enumerate() {
294        remapping.insert(old_id, MastNodeId::new_unchecked(new_index as u32));
295    }
296
297    if ordered_ids
298        .iter()
299        .enumerate()
300        .all(|(index, &old_id)| old_id == MastNodeId::new_unchecked(index as u32))
301    {
302        debug_assert!(validate_dense_node_order(&parts.nodes).is_ok());
303        return Ok((parts, remapping));
304    }
305
306    let mut nodes = IndexVec::with_capacity(node_count);
307    let empty_forest = MastForest::new();
308    for old_id in ordered_ids {
309        let node = parts.nodes[old_id].clone();
310        let remapped_node =
311            node.to_builder(&empty_forest).remap_children(&remapping).build_linked()?;
312        nodes
313            .push(remapped_node)
314            .expect("canonicalized node count was validated before remapping");
315    }
316
317    let roots = parts
318        .roots
319        .into_iter()
320        .map(|root_id| {
321            remapping
322                .get(root_id)
323                .ok_or(MastForestError::NodeIdOverflow(root_id, node_count))
324        })
325        .collect::<Result<Vec<_>, _>>()?;
326
327    debug_assert!(validate_dense_node_order(&nodes).is_ok());
328
329    Ok((
330        MastForestParts {
331            nodes,
332            roots,
333            advice_map: parts.advice_map,
334        },
335        remapping,
336    ))
337}
338
339fn final_dense_node_order(
340    nodes: &IndexVec<MastNodeId, MastNode>,
341) -> Result<Vec<MastNodeId>, MastForestError> {
342    let node_count = nodes.len();
343    let mut external_ids = Vec::new();
344    let mut basic_block_ids = Vec::new();
345    let mut internal_ids = Vec::new();
346
347    for (index, node) in nodes.iter().enumerate() {
348        let node_id = MastNodeId::new_unchecked(index as u32);
349        match node.order_class() {
350            MastNodeOrderClass::External => external_ids.push(node_id),
351            MastNodeOrderClass::BasicBlock => basic_block_ids.push(node_id),
352            MastNodeOrderClass::Internal => internal_ids.push(node_id),
353        }
354    }
355
356    external_ids.sort_by(|&left_id, &right_id| {
357        nodes[left_id]
358            .digest()
359            .cmp(&nodes[right_id].digest())
360            .then(left_id.0.cmp(&right_id.0))
361    });
362
363    // External nodes are identified only by digest, so duplicate external digests would create two
364    // IDs for the same dependency. Reject them before building the final order.
365    let mut previous_external_digest = None;
366    for &node_id in &external_ids {
367        let digest = nodes[node_id].digest();
368        if let Some(previous_digest) = previous_external_digest
369            && previous_digest >= digest
370        {
371            return Err(MastForestError::InvalidNodeOrder {
372                node_id,
373                reason: "external node digests must be strictly increasing".into(),
374            });
375        }
376        previous_external_digest = Some(digest);
377    }
378
379    let mut ordered_ids = external_ids;
380    let mut ordered = vec![false; node_count];
381    ordered_ids.extend(basic_block_ids);
382    for &node_id in &ordered_ids {
383        ordered[node_id.to_usize()] = true;
384    }
385
386    // Internal nodes are emitted once all children already appear in the final order. The ready set
387    // is keyed by original node ID, so construction order remains the tie-breaker among all
388    // currently-ready internal nodes.
389    let mut unresolved_child_counts = vec![0usize; node_count];
390    let mut parents_by_child = vec![Vec::new(); node_count];
391    for &node_id in &internal_ids {
392        nodes[node_id].for_each_child(|child_id| {
393            if child_id.to_usize() < node_count && !ordered[child_id.to_usize()] {
394                unresolved_child_counts[node_id.to_usize()] += 1;
395                parents_by_child[child_id.to_usize()].push(node_id);
396            }
397        });
398    }
399
400    for &node_id in &internal_ids {
401        let mut invalid_child = None;
402        nodes[node_id].for_each_child(|child_id| {
403            if child_id.to_usize() >= node_count {
404                invalid_child = Some(child_id);
405            }
406        });
407
408        if let Some(child_id) = invalid_child {
409            return Err(MastForestError::NodeIdOverflow(child_id, node_count));
410        }
411    }
412
413    let mut ready_internal_ids = BTreeSet::new();
414    for &node_id in &internal_ids {
415        if unresolved_child_counts[node_id.to_usize()] == 0 {
416            ready_internal_ids.insert(node_id);
417        }
418    }
419
420    let mut ordered_internal_count = 0;
421    while let Some(node_id) = ready_internal_ids.pop_first() {
422        ordered[node_id.to_usize()] = true;
423        ordered_ids.push(node_id);
424        ordered_internal_count += 1;
425
426        for parent_id in core::mem::take(&mut parents_by_child[node_id.to_usize()]) {
427            let count = &mut unresolved_child_counts[parent_id.to_usize()];
428            *count = count.checked_sub(1).expect("ready child must have a pending parent");
429            if *count == 0 {
430                ready_internal_ids.insert(parent_id);
431            }
432        }
433    }
434
435    if ordered_internal_count != internal_ids.len() {
436        let node_id = internal_ids
437            .into_iter()
438            .find(|node_id| !ordered[node_id.to_usize()])
439            .expect("internal cycle must contain a pending node");
440        return Err(MastForestError::InvalidNodeOrder {
441            node_id,
442            reason: "internal nodes must form an acyclic child-before-parent graph".into(),
443        });
444    }
445
446    Ok(ordered_ids)
447}
448
449#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
450pub(in crate::mast) enum MastNodeOrderClass {
451    External,
452    BasicBlock,
453    Internal,
454}
455
456fn validate_dense_node_order(
457    nodes: &IndexVec<MastNodeId, MastNode>,
458) -> Result<(), MastForestError> {
459    let mut previous_class = MastNodeOrderClass::External;
460    let mut previous_external_digest = None;
461
462    for (node_index, node) in nodes.iter().enumerate() {
463        let node_id = MastNodeId::new_unchecked(node_index as u32);
464        let node_class = node.order_class();
465
466        if node_class < previous_class {
467            return Err(MastForestError::InvalidNodeOrder {
468                node_id,
469                reason: format!("node class {node_class:?} appears after {previous_class:?}"),
470            });
471        }
472        previous_class = node_class;
473
474        if let MastNode::External(external_node) = node {
475            let digest = external_node.digest();
476            if let Some(previous_digest) = previous_external_digest
477                && previous_digest >= digest
478            {
479                return Err(MastForestError::InvalidNodeOrder {
480                    node_id,
481                    reason: "external node digests must be strictly increasing".into(),
482                });
483            }
484            previous_external_digest = Some(digest);
485        }
486
487        let mut forward_child = None;
488        node.for_each_child(|child_id| {
489            if child_id.0 >= node_id.0 {
490                forward_child = Some(child_id);
491            }
492        });
493        if let Some(child_id) = forward_child {
494            return Err(MastForestError::ForwardReference(node_id, child_id));
495        }
496    }
497
498    Ok(())
499}
500
501// ------------------------------------------------------------------------------------------------
502/// Equality implementations
503impl PartialEq for MastForest {
504    fn eq(&self, other: &Self) -> bool {
505        self.nodes == other.nodes
506            && self.roots == other.roots
507            && self.advice_map == other.advice_map
508    }
509}
510
511impl Eq for MastForest {}
512
513// ------------------------------------------------------------------------------------------------
514/// State mutators
515impl MastForest {
516    /// The maximum number of nodes that can be stored in a single MAST forest.
517    const MAX_NODES: usize = (1 << 30) - 1;
518
519    /// Marks the given [`MastNodeId`] as being the root of a procedure.
520    ///
521    /// If the specified node is already marked as a root, this will have no effect.
522    ///
523    /// # Panics
524    /// - if `new_root_id`'s internal index is larger than the number of nodes in this forest (i.e.
525    ///   clearly doesn't belong to this MAST forest).
526    #[cfg(any(test, feature = "arbitrary"))]
527    pub fn make_root(&mut self, new_root_id: MastNodeId) {
528        assert!(new_root_id.to_usize() < self.nodes.len());
529
530        if !self.roots.contains(&new_root_id) {
531            self.roots.push(new_root_id);
532            self.commitment = self.compute_mast_forest_commitment();
533        }
534    }
535
536    /// Removes all nodes in the provided set from the MAST forest. The nodes MUST be orphaned (i.e.
537    /// have no parent). Otherwise, this parent's reference is considered "dangling" after the
538    /// removal (i.e. will point to an incorrect node after the removal), and this removal operation
539    /// would result in an invalid [`MastForest`].
540    ///
541    /// It also returns the map from old node IDs to new node IDs. Any [`MastNodeId`] used in
542    /// reference to the old [`MastForest`] should be remapped using this map.
543    #[cfg(test)]
544    pub fn remove_nodes(
545        &mut self,
546        nodes_to_remove: &BTreeSet<MastNodeId>,
547    ) -> BTreeMap<MastNodeId, MastNodeId> {
548        if nodes_to_remove.is_empty() {
549            return BTreeMap::new();
550        }
551
552        self.assert_nodes_to_remove_are_orphaned(nodes_to_remove);
553
554        let old_nodes = core::mem::replace(&mut self.nodes, IndexVec::new());
555        let old_root_ids = core::mem::take(&mut self.roots);
556        let (retained_nodes, id_remappings) = remove_nodes(old_nodes.into_inner(), nodes_to_remove);
557
558        self.remap_and_add_nodes(retained_nodes, &id_remappings);
559        self.remap_and_add_roots(old_root_ids, &id_remappings);
560
561        self.commitment = self.compute_mast_forest_commitment();
562
563        id_remappings
564    }
565
566    /// Merges all `forests` into a new [`MastForest`].
567    ///
568    /// Merging two forests means combining all their constituent parts, i.e. [`MastNode`]s and
569    /// roots. During this process, any duplicate or unreachable nodes are removed. Additionally,
570    /// [`MastNodeId`]s of nodes may change and references to them are remapped to their new
571    /// location.
572    ///
573    /// For example, consider this representation of a forest's nodes with all of these nodes being
574    /// roots:
575    ///
576    /// ```text
577    /// [Block(foo), Block(bar)]
578    /// ```
579    ///
580    /// If we merge another forest into it:
581    ///
582    /// ```text
583    /// [Block(bar), Call(0)]
584    /// ```
585    ///
586    /// then we would expect this forest:
587    ///
588    /// ```text
589    /// [Block(foo), Block(bar), Call(1)]
590    /// ```
591    ///
592    /// - The `Call` to the `bar` block was remapped to its new index (now 1, previously 0).
593    /// - The `Block(bar)` was deduplicated any only exists once in the merged forest.
594    ///
595    /// The function also returns a vector of [`MastForestRootMap`]s, whose length equals the number
596    /// of passed `forests`. The indices in the vector correspond to the ones in `forests`. The map
597    /// of a given forest contains the new locations of its roots in the merged forest. To
598    /// illustrate, the above example would return a vector of two maps:
599    ///
600    /// ```text
601    /// vec![{0 -> 0, 1 -> 1}
602    ///      {0 -> 1, 1 -> 2}]
603    /// ```
604    ///
605    /// - The root locations of the original forest are unchanged.
606    /// - For the second forest, the `bar` block has moved from index 0 to index 1 in the merged
607    ///   forest, and the `Call` has moved from index 1 to 2.
608    ///
609    /// If any forest being merged contains an `External(qux)` node and another forest contains a
610    /// node whose digest is `qux`, then the external node will be replaced with the `qux` node,
611    /// which is effectively deduplication.
612    pub fn merge<'forest>(
613        forests: impl IntoIterator<Item = &'forest MastForest>,
614    ) -> Result<(MastForest, MastForestRootMap), MastForestError> {
615        MastForestMerger::merge(forests)
616    }
617}
618
619// ------------------------------------------------------------------------------------------------
620/// Helpers
621impl MastForest {
622    #[cfg(test)]
623    fn assert_nodes_to_remove_are_orphaned(&self, nodes_to_remove: &BTreeSet<MastNodeId>) {
624        for (node_idx, node) in self.nodes.iter().enumerate() {
625            let node_id = MastNodeId::new_unchecked(node_idx.try_into().expect("too many nodes"));
626            if nodes_to_remove.contains(&node_id) {
627                continue;
628            }
629
630            node.for_each_child(|child_id| {
631                assert!(
632                    !nodes_to_remove.contains(&child_id),
633                    "cannot remove node {child_id:?}; retained node {node_id:?} references it"
634                );
635            });
636        }
637    }
638
639    /// Adds all provided nodes to the internal set of nodes, remapping all [`MastNodeId`]
640    /// references in those nodes.
641    ///
642    /// # Panics
643    /// - Panics if the internal set of nodes is not empty.
644    #[cfg(test)]
645    fn remap_and_add_nodes(
646        &mut self,
647        nodes_to_add: Vec<MastNode>,
648        id_remappings: &BTreeMap<MastNodeId, MastNodeId>,
649    ) {
650        assert!(self.nodes.is_empty());
651        let node_builders =
652            nodes_to_add.into_iter().map(|node| node.to_builder(self)).collect::<Vec<_>>();
653
654        // Add each node to the new MAST forest, making sure to rewrite any outdated internal
655        // `MastNodeId`s
656        for live_node_builder in node_builders {
657            let node = live_node_builder.remap_children(id_remappings).build_linked().unwrap();
658            self.nodes.push(node).unwrap();
659        }
660    }
661
662    /// Remaps and adds all old root ids to the internal set of roots.
663    ///
664    /// # Panics
665    /// - Panics if the internal set of roots is not empty.
666    #[cfg(test)]
667    fn remap_and_add_roots(
668        &mut self,
669        old_root_ids: Vec<MastNodeId>,
670        id_remappings: &BTreeMap<MastNodeId, MastNodeId>,
671    ) {
672        assert!(self.roots.is_empty());
673
674        for old_root_id in old_root_ids {
675            if let Some(new_root_id) = id_remappings.get(&old_root_id).copied() {
676                self.make_root(new_root_id);
677            }
678        }
679    }
680}
681
682/// Returns the set of nodes that are live, as well as the mapping from "old ID" to "new ID" for all
683/// live nodes.
684#[cfg(test)]
685fn remove_nodes(
686    mast_nodes: Vec<MastNode>,
687    nodes_to_remove: &BTreeSet<MastNodeId>,
688) -> (Vec<MastNode>, BTreeMap<MastNodeId, MastNodeId>) {
689    // Note: this allows us to safely use `usize as u32`, guaranteeing that it won't wrap around.
690    assert!(mast_nodes.len() < u32::MAX as usize);
691
692    let mut retained_nodes = Vec::with_capacity(mast_nodes.len());
693    let mut id_remappings = BTreeMap::new();
694
695    for (old_node_index, old_node) in mast_nodes.into_iter().enumerate() {
696        let old_node_id: MastNodeId = MastNodeId(old_node_index as u32);
697
698        if !nodes_to_remove.contains(&old_node_id) {
699            let new_node_id: MastNodeId = MastNodeId(retained_nodes.len() as u32);
700            id_remappings.insert(old_node_id, new_node_id);
701
702            retained_nodes.push(old_node);
703        }
704    }
705
706    (retained_nodes, id_remappings)
707}
708
709fn empty_mast_forest_commitment() -> MastForestCommitment {
710    let interface_commitment = Poseidon2::merge_many(&[]);
711    let dependency_commitment = Poseidon2::merge_many(&[]);
712    let advice_commitment = AdviceMap::default().commitment();
713    MastForestCommitment::new(interface_commitment, dependency_commitment, advice_commitment)
714}
715
716fn compute_nodes_commitment(
717    nodes: &IndexVec<MastNodeId, MastNode>,
718    node_ids: &[MastNodeId],
719) -> Word {
720    let mut digests: Vec<Word> = node_ids.iter().map(|&id| nodes[id].digest()).collect();
721    digests.sort_unstable();
722    Poseidon2::merge_many(&digests)
723}
724
725fn compute_dependency_commitment(nodes: &IndexVec<MastNodeId, MastNode>) -> Word {
726    let mut digests: Vec<Word> = nodes
727        .iter()
728        .filter(|node| node.is_external())
729        .map(MastNodeExt::digest)
730        .collect();
731    digests.sort_unstable();
732    Poseidon2::merge_many(&digests)
733}
734
735impl MastForestCommitment {
736    fn new(
737        interface_commitment: Word,
738        dependency_commitment: Word,
739        advice_commitment: Word,
740    ) -> Self {
741        let commitment = Poseidon2::merge_many(&[
742            interface_commitment,
743            dependency_commitment,
744            advice_commitment,
745        ]);
746        Self {
747            commitment,
748            interface_commitment,
749            dependency_commitment,
750            advice_commitment,
751        }
752    }
753}
754
755fn compute_mast_forest_commitment(
756    nodes: &IndexVec<MastNodeId, MastNode>,
757    roots: &[MastNodeId],
758    advice_map: &AdviceMap,
759) -> MastForestCommitment {
760    let interface_commitment = compute_nodes_commitment(nodes, roots);
761    let dependency_commitment = compute_dependency_commitment(nodes);
762    let advice_commitment = advice_map.commitment();
763    MastForestCommitment::new(interface_commitment, dependency_commitment, advice_commitment)
764}
765
766// ------------------------------------------------------------------------------------------------
767/// Public accessors
768impl MastForest {
769    /// Returns the [`MastNode`] associated with the provided [`MastNodeId`] if valid, or else
770    /// `None`.
771    ///
772    /// This is the fallible version of indexing (e.g. `mast_forest[node_id]`).
773    #[inline(always)]
774    pub fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> {
775        self.nodes.get(node_id)
776    }
777
778    /// Returns the [`MastNodeId`] of the procedure associated with a given digest, if any.
779    #[inline(always)]
780    pub fn find_procedure_root(&self, digest: Word) -> Option<MastNodeId> {
781        self.roots.iter().find(|&&root_id| self[root_id].digest() == digest).copied()
782    }
783
784    /// Returns true if a node with the specified ID is a root of a procedure in this MAST forest.
785    pub fn is_procedure_root(&self, node_id: MastNodeId) -> bool {
786        self.roots.contains(&node_id)
787    }
788
789    /// Returns true if a node with the specified ID is a root of a procedure in this MAST forest,
790    /// and the digest of that procedure is `digest`.
791    ///
792    /// This is primarily intended for use in confirming that procedure exports of a package,
793    /// which declare their MAST node and digest, actually exist in the MAST.
794    pub fn is_procedure_root_with_exact_digest(&self, node_id: MastNodeId, digest: Word) -> bool {
795        self.is_procedure_root(node_id) && self[node_id].digest() == digest
796    }
797
798    /// Returns an iterator over the digests of all procedures in this MAST forest.
799    pub fn procedure_digests(&self) -> impl Iterator<Item = Word> + '_ {
800        self.roots.iter().map(|&root_id| self[root_id].digest())
801    }
802
803    /// Returns an iterator over the digests of local procedures in this MAST forest.
804    ///
805    /// A local procedure is defined as a procedure which is not a single external node.
806    pub fn local_procedure_digests(&self) -> impl Iterator<Item = Word> + '_ {
807        self.roots.iter().filter_map(|&root_id| {
808            let node = &self[root_id];
809            if node.is_external() { None } else { Some(node.digest()) }
810        })
811    }
812
813    /// Returns an iterator over the IDs of the procedures in this MAST forest.
814    pub fn procedure_roots(&self) -> &[MastNodeId] {
815        &self.roots
816    }
817
818    /// Returns the number of procedures in this MAST forest.
819    pub fn num_procedures(&self) -> u32 {
820        self.roots
821            .len()
822            .try_into()
823            .expect("MAST forest contains more than 2^32 procedures.")
824    }
825
826    /// Returns the [Word] representing the content hash of a subset of [`MastNodeId`]s.
827    ///
828    /// # Panics
829    /// This function panics if any `node_ids` is not a node of this forest.
830    pub fn compute_nodes_commitment<'a>(
831        &self,
832        node_ids: impl IntoIterator<Item = &'a MastNodeId>,
833    ) -> Word {
834        let node_ids = node_ids.into_iter().copied().collect::<Vec<_>>();
835        compute_nodes_commitment(&self.nodes, &node_ids)
836    }
837
838    /// Returns the commitment to this MAST forest's public procedure roots.
839    ///
840    /// The commitment is computed as the sequential hash of all procedure root digests after
841    /// sorting them by digest.
842    pub fn interface_commitment(&self) -> Word {
843        self.commitment.interface_commitment
844    }
845
846    /// Returns the commitment to this MAST forest's external dependencies.
847    ///
848    /// The commitment is computed as the sequential hash of all external node digests after
849    /// sorting them by digest.
850    pub fn dependency_commitment(&self) -> Word {
851        self.commitment.dependency_commitment
852    }
853
854    /// Returns the commitment to this MAST forest's advice map.
855    ///
856    /// The commitment is computed over advice entries in key order.
857    pub fn advice_commitment(&self) -> Word {
858        self.commitment.advice_commitment
859    }
860
861    fn compute_mast_forest_commitment(&self) -> MastForestCommitment {
862        compute_mast_forest_commitment(&self.nodes, &self.roots, &self.advice_map)
863    }
864
865    /// Returns the commitment to this MAST forest.
866    ///
867    /// The commitment is computed from the interface, dependency, and advice commitments.
868    pub fn commitment(&self) -> Word {
869        self.commitment.commitment
870    }
871
872    /// Returns the number of nodes in this MAST forest.
873    pub fn num_nodes(&self) -> u32 {
874        self.nodes.len() as u32
875    }
876
877    /// Returns the underlying nodes in this MAST forest.
878    pub fn nodes(&self) -> &[MastNode] {
879        self.nodes.as_slice()
880    }
881
882    pub fn advice_map(&self) -> &AdviceMap {
883        &self.advice_map
884    }
885
886    /// Returns this forest with `advice_map` entries added.
887    pub fn with_advice_map(mut self, advice_map: AdviceMap) -> Self {
888        self.advice_map.extend(advice_map);
889        self.commitment = self.compute_mast_forest_commitment();
890        self
891    }
892
893    // SERIALIZATION
894    // --------------------------------------------------------------------------------------------
895
896    /// Serializes this MastForest with the HASHLESS flag set.
897    ///
898    /// Hashless forest bytes omit rebuildable internal node hashes. External node digests stay on
899    /// the wire because they cannot be rebuilt from local structure. Trusted deserialization
900    /// rejects this flag.
901    ///
902    /// Use this when producing data for untrusted validation.
903    pub fn write_hashless<W: ByteWriter>(&self, target: &mut W) {
904        serialization::write_hashless_into(self, target);
905    }
906}
907
908/// Validation methods
909impl MastForest {
910    pub(in crate::mast) fn validate_dense_node_order(&self) -> Result<(), MastForestError> {
911        validate_dense_node_order(&self.nodes)
912    }
913
914    fn validate_basic_block_invariants(&self) -> Result<(), MastForestError> {
915        for (node_id_idx, node) in self.nodes.iter().enumerate() {
916            let node_id =
917                MastNodeId::new_unchecked(node_id_idx.try_into().expect("too many nodes"));
918            if let MastNode::Block(basic_block) = node {
919                basic_block.validate_batch_invariants().map_err(|error_msg| {
920                    MastForestError::InvalidBatchPadding(node_id, error_msg)
921                })?;
922            }
923        }
924
925        Ok(())
926    }
927
928    /// Validates that all BasicBlockNodes in this forest satisfy the core invariants:
929    /// 1. Power-of-two number of groups in each batch
930    /// 2. No operation group ends with an operation requiring an immediate value
931    /// 3. The last operation group in a batch cannot contain operations requiring immediate values
932    /// 4. OpBatch structural consistency (num_groups <= BATCH_SIZE, group size <= GROUP_SIZE,
933    ///    indptr integrity, bounds checking)
934    ///
935    /// This addresses the gap created by PR 2094, where padding NOOPs are now inserted
936    /// at assembly time rather than dynamically during execution, and adds comprehensive
937    /// structural validation to prevent deserialization-time panics.
938    pub fn validate(&self) -> Result<(), MastForestError> {
939        self.validate_basic_block_invariants()?;
940        Ok(())
941    }
942
943    /// Validates that stored node digests match the hashes implied by local structure.
944    ///
945    /// For `External` nodes the digest is accepted as-is because it is externally provided and
946    /// cannot be reconstructed from local structure alone.
947    fn validate_node_hashes(&self) -> Result<(), MastForestError> {
948        let computed_hashes = self.compute_node_hashes()?;
949        for (node_idx, (node, computed_digest)) in
950            self.nodes.iter().zip(computed_hashes).enumerate()
951        {
952            let expected_digest = node.digest();
953            if expected_digest != computed_digest {
954                return Err(MastForestError::HashMismatch {
955                    node_id: MastNodeId::new_unchecked(node_idx as u32),
956                    expected: expected_digest,
957                    computed: computed_digest,
958                });
959            }
960        }
961
962        Ok(())
963    }
964
965    /// Computes node hashes in topological order.
966    ///
967    /// The returned vector is aligned with node indices, so `digests[node_id as usize]` is the
968    /// digest of that node.
969    ///
970    /// For `External` nodes, the existing digest is returned unchanged.
971    ///
972    /// Returns [`MastForestError::ForwardReference`] if nodes are not in topological order.
973    fn compute_node_hashes(&self) -> Result<Vec<Word>, MastForestError> {
974        use crate::chiplets::hasher;
975
976        /// Checks that child_id references a node that appears before node_id in topological order.
977        fn check_no_forward_ref(
978            node_id: MastNodeId,
979            child_id: MastNodeId,
980        ) -> Result<(), MastForestError> {
981            if child_id.0 >= node_id.0 {
982                return Err(MastForestError::ForwardReference(node_id, child_id));
983            }
984            Ok(())
985        }
986
987        let mut computed_hashes = Vec::with_capacity(self.nodes.len());
988        for (node_idx, node) in self.nodes.iter().enumerate() {
989            let node_id = MastNodeId::new_unchecked(node_idx as u32);
990
991            // Check topological ordering and compute digest.
992            let computed_digest = match node {
993                MastNode::Block(block) => {
994                    let op_groups: Vec<Felt> =
995                        block.op_batches().iter().flat_map(|batch| *batch.groups()).collect();
996                    hasher::hash_elements(&op_groups)
997                },
998                MastNode::Join(join) => {
999                    let left_id = join.first();
1000                    let right_id = join.second();
1001                    check_no_forward_ref(node_id, left_id)?;
1002                    check_no_forward_ref(node_id, right_id)?;
1003
1004                    let left_digest = computed_hashes[left_id.0 as usize];
1005                    let right_digest = computed_hashes[right_id.0 as usize];
1006                    hasher::merge_in_domain(&[left_digest, right_digest], JoinNode::DOMAIN)
1007                },
1008                MastNode::Split(split) => {
1009                    let true_id = split.on_true();
1010                    let false_id = split.on_false();
1011                    check_no_forward_ref(node_id, true_id)?;
1012                    check_no_forward_ref(node_id, false_id)?;
1013
1014                    let true_digest = computed_hashes[true_id.0 as usize];
1015                    let false_digest = computed_hashes[false_id.0 as usize];
1016                    hasher::merge_in_domain(&[true_digest, false_digest], SplitNode::DOMAIN)
1017                },
1018                MastNode::Loop(loop_node) => {
1019                    let body_id = loop_node.body();
1020                    check_no_forward_ref(node_id, body_id)?;
1021
1022                    let body_digest = computed_hashes[body_id.0 as usize];
1023                    hasher::merge_in_domain(&[body_digest, Word::default()], LoopNode::DOMAIN)
1024                },
1025                MastNode::Call(call) => {
1026                    let callee_id = call.callee();
1027                    check_no_forward_ref(node_id, callee_id)?;
1028
1029                    let callee_digest = computed_hashes[callee_id.0 as usize];
1030                    let domain = if call.is_syscall() {
1031                        CallNode::SYSCALL_DOMAIN
1032                    } else {
1033                        CallNode::CALL_DOMAIN
1034                    };
1035                    hasher::merge_in_domain(&[callee_digest, Word::default()], domain)
1036                },
1037                MastNode::Dyn(dyn_node) => {
1038                    if dyn_node.is_dyncall() {
1039                        DynNode::DYNCALL_DEFAULT_DIGEST
1040                    } else {
1041                        DynNode::DYN_DEFAULT_DIGEST
1042                    }
1043                },
1044                MastNode::External(_) => {
1045                    // External nodes have externally-provided digests that cannot be recomputed.
1046                    node.digest()
1047                },
1048            };
1049
1050            computed_hashes.push(computed_digest);
1051        }
1052
1053        Ok(computed_hashes)
1054    }
1055}
1056
1057// MAST FOREST INDEXING
1058// ------------------------------------------------------------------------------------------------
1059
1060impl Index<MastNodeId> for MastForest {
1061    type Output = MastNode;
1062
1063    #[inline(always)]
1064    fn index(&self, node_id: MastNodeId) -> &Self::Output {
1065        &self.nodes[node_id]
1066    }
1067}
1068
1069// EXECUTABLE MAST FOREST
1070// ================================================================================================
1071
1072/// A MAST forest that can be used as the source of nodes during program execution.
1073///
1074/// Implemented by both [`MastForest`] (a dense forest containing all nodes) and
1075/// [`SparseMastForest`] (a sparse subset of a forest containing only the nodes visited during
1076/// some prior execution). The latter preserves the original [`MastNodeId`]s of its source forest,
1077/// which allows it to stand in for the dense forest during re-execution.
1078pub trait ExecutableMastForest {
1079    /// Returns the [`MastNode`] associated with the provided [`MastNodeId`] if present, or else
1080    /// `None`.
1081    fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode>;
1082
1083    /// Returns the digest of the node associated with the provided [`MastNodeId`] if present, or
1084    /// else `None`.
1085    ///
1086    /// For dense forests this is equivalent to `get_node_by_id(id).map(|n| n.digest())`. For
1087    /// [`SparseMastForest`], it additionally consults the digest-only entries — nodes that were
1088    /// referenced (but not entered) during execution and which were therefore stored as digest
1089    /// only. Use this method whenever only the digest of a referenced node is needed (e.g. when
1090    /// populating the hasher state of a parent's trace row).
1091    fn get_digest_by_id(&self, node_id: MastNodeId) -> Option<Word>;
1092
1093    /// Returns the [`MastNodeId`] of the procedure associated with a given digest, if any.
1094    fn find_procedure_root(&self, digest: Word) -> Option<MastNodeId>;
1095
1096    /// Returns the advice map associated with this forest.
1097    fn advice_map(&self) -> &AdviceMap;
1098}
1099
1100impl ExecutableMastForest for MastForest {
1101    #[inline(always)]
1102    fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> {
1103        MastForest::get_node_by_id(self, node_id)
1104    }
1105
1106    #[inline(always)]
1107    fn get_digest_by_id(&self, node_id: MastNodeId) -> Option<Word> {
1108        MastForest::get_node_by_id(self, node_id).map(MastNodeExt::digest)
1109    }
1110
1111    #[inline(always)]
1112    fn find_procedure_root(&self, digest: Word) -> Option<MastNodeId> {
1113        MastForest::find_procedure_root(self, digest)
1114    }
1115
1116    #[inline(always)]
1117    fn advice_map(&self) -> &AdviceMap {
1118        MastForest::advice_map(self)
1119    }
1120}
1121
1122// Blanket impl: an `Arc<T>` is an `ExecutableMastForest` whenever the underlying `T` is, which
1123// allows the executor and tracer plumbing to be generic over a forest type while the live
1124// (`Arc<MastForest>`) and replay (`Arc<SparseMastForest>`) paths each pick a concrete instance.
1125impl<T> Index<MastNodeId> for Arc<T>
1126where
1127    T: Index<MastNodeId, Output = MastNode> + ?Sized,
1128{
1129    type Output = MastNode;
1130
1131    #[inline(always)]
1132    fn index(&self, node_id: MastNodeId) -> &Self::Output {
1133        &(**self)[node_id]
1134    }
1135}
1136
1137impl<T: ExecutableMastForest + ?Sized> ExecutableMastForest for Arc<T> {
1138    #[inline(always)]
1139    fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> {
1140        T::get_node_by_id(self, node_id)
1141    }
1142
1143    #[inline(always)]
1144    fn get_digest_by_id(&self, node_id: MastNodeId) -> Option<Word> {
1145        T::get_digest_by_id(self, node_id)
1146    }
1147
1148    #[inline(always)]
1149    fn find_procedure_root(&self, digest: Word) -> Option<MastNodeId> {
1150        T::find_procedure_root(self, digest)
1151    }
1152
1153    #[inline(always)]
1154    fn advice_map(&self) -> &AdviceMap {
1155        T::advice_map(self)
1156    }
1157}
1158
1159// MAST NODE ID
1160// ================================================================================================
1161
1162/// An opaque handle to a [`MastNode`] in some [`MastForest`]. It is the responsibility of the user
1163/// to use a given [`MastNodeId`] with the corresponding [`MastForest`].
1164///
1165/// Note that the [`MastForest`] does *not* ensure that equal [`MastNode`]s have equal
1166/// [`MastNodeId`] handles. Hence, [`MastNodeId`] equality must not be used to test for equality of
1167/// the underlying [`MastNode`].
1168#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1169#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1170#[cfg_attr(feature = "serde", serde(transparent))]
1171pub struct MastNodeId(u32);
1172
1173/// Operations that mutate a MAST often produce this mapping between old and new NodeIds.
1174pub type Remapping = BTreeMap<MastNodeId, MastNodeId>;
1175
1176impl MastNodeId {
1177    /// Returns a new `MastNodeId` with the provided inner value, or an error if the provided
1178    /// `value` is greater than the number of nodes in the forest.
1179    ///
1180    /// For use in deserialization.
1181    pub fn from_u32_safe(
1182        value: u32,
1183        mast_forest: &MastForest,
1184    ) -> Result<Self, DeserializationError> {
1185        Self::from_u32_with_node_count(value, mast_forest.nodes.len())
1186    }
1187
1188    /// Returns a new [`MastNodeId`] from the given `value` without checking its validity.
1189    pub fn new_unchecked(value: u32) -> Self {
1190        Self(value)
1191    }
1192
1193    /// Returns a new [`MastNodeId`] with the provided `id`, or an error if `id` is greater or equal
1194    /// to `node_count`. The `node_count` is the total number of nodes in the [`MastForest`] for
1195    /// which this ID is being constructed.
1196    ///
1197    /// This function can be used when deserializing an id whose corresponding node is not yet in
1198    /// the forest and [`Self::from_u32_safe`] would fail. For instance, when deserializing the ids
1199    /// referenced by the Join node in this forest:
1200    ///
1201    /// ```text
1202    /// [Join(1, 2), Block(foo), Block(bar)]
1203    /// ```
1204    ///
1205    /// Since it is less safe than [`Self::from_u32_safe`] and usually not needed it is not public.
1206    pub(super) fn from_u32_with_node_count(
1207        id: u32,
1208        node_count: usize,
1209    ) -> Result<Self, DeserializationError> {
1210        if (id as usize) < node_count {
1211            Ok(Self(id))
1212        } else {
1213            Err(DeserializationError::InvalidValue(format!(
1214                "Invalid deserialized MAST node ID '{id}', but {node_count} is the number of nodes in the forest",
1215            )))
1216        }
1217    }
1218
1219    /// Remap the NodeId to its new position using the given [`Remapping`].
1220    pub fn remap(&self, remapping: &Remapping) -> Self {
1221        *remapping.get(self).unwrap_or(self)
1222    }
1223}
1224
1225impl From<u32> for MastNodeId {
1226    fn from(value: u32) -> Self {
1227        MastNodeId::new_unchecked(value)
1228    }
1229}
1230
1231impl Idx for MastNodeId {}
1232
1233impl From<MastNodeId> for u32 {
1234    fn from(value: MastNodeId) -> Self {
1235        value.0
1236    }
1237}
1238
1239impl Serializable for MastNodeId {
1240    fn write_into<W: ByteWriter>(&self, target: &mut W) {
1241        Serializable::write_into(&self.0, target);
1242    }
1243}
1244
1245impl fmt::Display for MastNodeId {
1246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1247        write!(f, "MastNodeId({})", self.0)
1248    }
1249}
1250
1251#[cfg(any(test, feature = "arbitrary"))]
1252impl Arbitrary for MastNodeId {
1253    type Parameters = ();
1254
1255    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1256        use proptest::prelude::*;
1257        any::<u32>().prop_map(MastNodeId).boxed()
1258    }
1259
1260    type Strategy = BoxedStrategy<Self>;
1261}
1262
1263// ITERATOR
1264
1265/// Iterates over all the nodes a root depends on, in pre-order. The iteration can include other
1266/// roots in the same forest.
1267pub struct SubtreeIterator<'a> {
1268    forest: &'a MastForest,
1269    discovered: Vec<MastNodeId>,
1270    unvisited: Vec<MastNodeId>,
1271}
1272impl<'a> SubtreeIterator<'a> {
1273    pub fn new(root: &MastNodeId, forest: &'a MastForest) -> Self {
1274        let discovered = vec![];
1275        let unvisited = vec![*root];
1276        SubtreeIterator { forest, discovered, unvisited }
1277    }
1278}
1279impl Iterator for SubtreeIterator<'_> {
1280    type Item = MastNodeId;
1281    fn next(&mut self) -> Option<MastNodeId> {
1282        while let Some(id) = self.unvisited.pop() {
1283            let node = &self.forest[id];
1284            if !node.has_children() {
1285                return Some(id);
1286            } else {
1287                self.discovered.push(id);
1288                node.append_children_to(&mut self.unvisited);
1289            }
1290        }
1291        self.discovered.pop()
1292    }
1293}
1294
1295/// Derives an error code from an error message by hashing the message and returning the 0th element
1296/// of the resulting [`Word`].
1297pub fn error_code_from_msg(msg: impl AsRef<str>) -> Felt {
1298    // hash the message and return 0th felt of the resulting Word
1299    hash_string_to_word(msg.as_ref())[0]
1300}
1301
1302// MAST FOREST ERROR
1303// ================================================================================================
1304
1305/// Represents the types of errors that can occur when dealing with MAST forest.
1306#[derive(Debug, thiserror::Error, PartialEq, Eq)]
1307pub enum MastForestError {
1308    #[error("MAST forest node count exceeds the maximum of {} nodes", MastForest::MAX_NODES)]
1309    TooManyNodes,
1310    #[error("node id {0} is greater than or equal to forest length {1}")]
1311    NodeIdOverflow(MastNodeId, usize),
1312    #[error("basic block cannot be created from an empty list of operations")]
1313    EmptyBasicBlock,
1314    #[error("advice map key {0} already exists when merging forests")]
1315    AdviceMapKeyCollisionOnMerge(Word),
1316    #[error("digest is required for deserialization")]
1317    DigestRequiredForDeserialization,
1318    #[error("invalid batch in basic block node {0:?}: {1}")]
1319    InvalidBatchPadding(MastNodeId, String),
1320    #[error("invalid node order at {node_id:?}: {reason}")]
1321    InvalidNodeOrder { node_id: MastNodeId, reason: String },
1322    #[error(
1323        "node {0:?} references child {1:?} which comes after it in the forest (forward reference)"
1324    )]
1325    ForwardReference(MastNodeId, MastNodeId),
1326    #[error("hash mismatch for node {node_id:?}: expected {expected:?}, computed {computed:?}")]
1327    HashMismatch {
1328        node_id: MastNodeId,
1329        expected: Word,
1330        computed: Word,
1331    },
1332    #[error("deserialization failed: {0}")]
1333    Deserialization(DeserializationError),
1334}
1335
1336// Custom serde implementation for MastForest delegates to the binary serialization format.
1337#[cfg(feature = "serde")]
1338impl Serialize for MastForest {
1339    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1340    where
1341        S: serde::Serializer,
1342    {
1343        let bytes = Serializable::to_bytes(self);
1344        serializer.serialize_bytes(&bytes)
1345    }
1346}
1347
1348#[cfg(feature = "serde")]
1349impl<'de> Deserialize<'de> for MastForest {
1350    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1351    where
1352        D: serde::Deserializer<'de>,
1353    {
1354        // Deserialize bytes, then use miden-crypto Deserializable
1355        let bytes = Vec::<u8>::deserialize(deserializer)?;
1356        let mut slice_reader = SliceReader::new(&bytes);
1357        Deserializable::read_from(&mut slice_reader).map_err(serde::de::Error::custom)
1358    }
1359}