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//! For maximum protection against denial-of-service attacks from malicious input, use
16//! [`UntrustedMastForest::read_from_bytes_with_budget`] which limits memory consumption:
17//!
18//! ```ignore
19//! use miden_core::mast::UntrustedMastForest;
20//!
21//! // Budget limits pre-allocation sizes and total bytes consumed
22//! let forest = UntrustedMastForest::read_from_bytes_with_budget(&bytes, bytes.len())?
23//!     .validate()?;
24//! ```
25//!
26//! This recomputes all node hashes and checks structural invariants before returning a usable
27//! `MastForest`. Direct deserialization via `MastForest::read_from_bytes` trusts the serialized
28//! hashes and should only be used for data from trusted sources (e.g. compiled locally).
29
30use alloc::{
31    collections::{BTreeMap, BTreeSet},
32    string::String,
33    sync::Arc,
34    vec::Vec,
35};
36use core::{
37    fmt,
38    ops::{Index, IndexMut},
39};
40
41use miden_utils_sync::OnceLockCompat;
42#[cfg(feature = "serde")]
43use serde::{Deserialize, Serialize};
44
45mod node;
46#[cfg(any(test, feature = "arbitrary"))]
47pub use node::arbitrary;
48pub(crate) use node::collect_immediate_placements;
49pub use node::{
50    BasicBlockNode, BasicBlockNodeBuilder, CallNode, CallNodeBuilder, DecoratedOpLink,
51    DecoratorOpLinkIterator, DecoratorStore, DynNode, DynNodeBuilder, ExternalNode,
52    ExternalNodeBuilder, JoinNode, JoinNodeBuilder, LoopNode, LoopNodeBuilder,
53    MastForestContributor, MastNode, MastNodeBuilder, MastNodeExt, OP_BATCH_SIZE, OP_GROUP_SIZE,
54    OpBatch, OperationOrDecorator, SplitNode, SplitNodeBuilder,
55};
56
57use crate::{
58    Felt, LexicographicWord, Word,
59    advice::AdviceMap,
60    operations::{AssemblyOp, DebugVarInfo, Decorator},
61    serde::{
62        BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
63        SliceReader,
64    },
65    utils::{Idx, IndexVec, hash_string_to_word},
66};
67
68mod debuginfo;
69pub use debuginfo::{
70    AsmOpIndexError, DebugInfo, DebugVarId, DecoratedLinks, DecoratedLinksIter,
71    DecoratorIndexError, NodeToDecoratorIds, OpToAsmOpId, OpToDebugVarIds, OpToDecoratorIds,
72};
73
74mod serialization;
75
76mod merger;
77pub(crate) use merger::MastForestMerger;
78pub use merger::MastForestRootMap;
79
80mod multi_forest_node_iterator;
81pub(crate) use multi_forest_node_iterator::*;
82
83mod node_fingerprint;
84pub use node_fingerprint::{DecoratorFingerprint, MastNodeFingerprint};
85
86mod node_builder_utils;
87pub use node_builder_utils::build_node_with_remapped_ids;
88
89#[cfg(test)]
90mod tests;
91
92// MAST FOREST
93// ================================================================================================
94
95/// Represents one or more procedures, represented as a collection of [`MastNode`]s.
96///
97/// A [`MastForest`] does not have an entrypoint, and hence is not executable. A
98/// [`crate::program::Program`] can be built from a [`MastForest`] to specify an entrypoint.
99#[derive(Clone, Debug, Default)]
100pub struct MastForest {
101    /// All of the nodes local to the trees comprising the MAST forest.
102    nodes: IndexVec<MastNodeId, MastNode>,
103
104    /// Roots of procedures defined within this MAST forest.
105    roots: Vec<MastNodeId>,
106
107    /// Advice map to be loaded into the VM prior to executing procedures from this MAST forest.
108    advice_map: AdviceMap,
109
110    /// Debug information including decorators and error codes.
111    /// Always present (as per issue #1821), but can be empty for stripped builds.
112    debug_info: DebugInfo,
113
114    /// Cached commitment to this MAST forest (commitment to all roots).
115    /// This is computed lazily on first access and invalidated on any mutation.
116    commitment_cache: OnceLockCompat<Word>,
117}
118
119// ------------------------------------------------------------------------------------------------
120/// Constructors
121impl MastForest {
122    /// Creates a new empty [`MastForest`].
123    pub fn new() -> Self {
124        Self {
125            nodes: IndexVec::new(),
126            roots: Vec::new(),
127            advice_map: AdviceMap::default(),
128            debug_info: DebugInfo::new(),
129            commitment_cache: OnceLockCompat::new(),
130        }
131    }
132}
133
134// ------------------------------------------------------------------------------------------------
135/// Equality implementations
136impl PartialEq for MastForest {
137    fn eq(&self, other: &Self) -> bool {
138        // Compare all fields except commitment_cache, which is derived data
139        self.nodes == other.nodes
140            && self.roots == other.roots
141            && self.advice_map == other.advice_map
142            && self.debug_info == other.debug_info
143    }
144}
145
146impl Eq for MastForest {}
147
148// ------------------------------------------------------------------------------------------------
149/// State mutators
150impl MastForest {
151    /// The maximum number of nodes that can be stored in a single MAST forest.
152    const MAX_NODES: usize = (1 << 30) - 1;
153
154    /// Marks the given [`MastNodeId`] as being the root of a procedure.
155    ///
156    /// If the specified node is already marked as a root, this will have no effect.
157    ///
158    /// # Panics
159    /// - if `new_root_id`'s internal index is larger than the number of nodes in this forest (i.e.
160    ///   clearly doesn't belong to this MAST forest).
161    pub fn make_root(&mut self, new_root_id: MastNodeId) {
162        assert!(new_root_id.to_usize() < self.nodes.len());
163
164        if !self.roots.contains(&new_root_id) {
165            self.roots.push(new_root_id);
166            // Invalidate the cached commitment since we modified the roots
167            self.commitment_cache.take();
168        }
169    }
170
171    /// Removes all nodes in the provided set from the MAST forest. The nodes MUST be orphaned (i.e.
172    /// have no parent). Otherwise, this parent's reference is considered "dangling" after the
173    /// removal (i.e. will point to an incorrect node after the removal), and this removal operation
174    /// would result in an invalid [`MastForest`].
175    ///
176    /// It also returns the map from old node IDs to new node IDs. Any [`MastNodeId`] used in
177    /// reference to the old [`MastForest`] should be remapped using this map.
178    pub fn remove_nodes(
179        &mut self,
180        nodes_to_remove: &BTreeSet<MastNodeId>,
181    ) -> BTreeMap<MastNodeId, MastNodeId> {
182        if nodes_to_remove.is_empty() {
183            return BTreeMap::new();
184        }
185
186        let old_nodes = core::mem::replace(&mut self.nodes, IndexVec::new());
187        let old_root_ids = core::mem::take(&mut self.roots);
188        let (retained_nodes, id_remappings) = remove_nodes(old_nodes.into_inner(), nodes_to_remove);
189
190        self.remap_and_add_nodes(retained_nodes, &id_remappings);
191        self.remap_and_add_roots(old_root_ids, &id_remappings);
192
193        // Remap the asm_op_storage and debug_var_storage to use the new node IDs
194        self.debug_info.remap_asm_op_storage(&id_remappings);
195        self.debug_info.remap_debug_var_storage(&id_remappings);
196
197        // Invalidate the cached commitment since we modified the forest structure
198        self.commitment_cache.take();
199
200        id_remappings
201    }
202
203    /// Clears all [`DebugInfo`] from this forest: decorators, error codes, and procedure names.
204    ///
205    /// ```
206    /// # use miden_core::mast::MastForest;
207    /// let mut forest = MastForest::new();
208    /// forest.clear_debug_info();
209    /// assert!(forest.decorators().is_empty());
210    /// ```
211    pub fn clear_debug_info(&mut self) {
212        self.debug_info = DebugInfo::empty_for_nodes(self.nodes.len());
213    }
214
215    /// Compacts the forest by merging duplicate nodes.
216    ///
217    /// This operation performs node deduplication by merging the forest with itself.
218    /// The method assumes that debug info has already been cleared if that is desired.
219    /// This method consumes the forest and returns a new compacted forest.
220    ///
221    /// The process works by:
222    /// 1. Merging the forest with itself to deduplicate identical nodes
223    /// 2. Updating internal node references and remappings
224    /// 3. Returning the compacted forest and root map
225    ///
226    /// # Examples
227    ///
228    /// ```rust
229    /// use miden_core::mast::MastForest;
230    ///
231    /// let mut forest = MastForest::new();
232    /// // Add nodes to the forest
233    ///
234    /// // First clear debug info if needed
235    /// forest.clear_debug_info();
236    ///
237    /// // Then compact the forest (consumes the original)
238    /// let (compacted_forest, root_map) = forest.compact();
239    ///
240    /// // compacted_forest is now compacted with duplicate nodes merged
241    /// ```
242    pub fn compact(self) -> (MastForest, MastForestRootMap) {
243        // Merge with itself to deduplicate nodes
244        // Note: This cannot fail for a self-merge under normal conditions.
245        // The only possible failures (TooManyNodes, TooManyDecorators) would require the
246        // original forest to be at capacity limits, at which point compaction wouldn't help.
247        MastForest::merge([&self])
248            .expect("Failed to compact MastForest: this should never happen during self-merge")
249    }
250
251    /// Merges all `forests` into a new [`MastForest`].
252    ///
253    /// Merging two forests means combining all their constituent parts, i.e. [`MastNode`]s,
254    /// [`Decorator`]s and roots. During this process, any duplicate or
255    /// unreachable nodes are removed. Additionally, [`MastNodeId`]s of nodes as well as
256    /// [`DecoratorId`]s of decorators may change and references to them are remapped to their new
257    /// location.
258    ///
259    /// For example, consider this representation of a forest's nodes with all of these nodes being
260    /// roots:
261    ///
262    /// ```text
263    /// [Block(foo), Block(bar)]
264    /// ```
265    ///
266    /// If we merge another forest into it:
267    ///
268    /// ```text
269    /// [Block(bar), Call(0)]
270    /// ```
271    ///
272    /// then we would expect this forest:
273    ///
274    /// ```text
275    /// [Block(foo), Block(bar), Call(1)]
276    /// ```
277    ///
278    /// - The `Call` to the `bar` block was remapped to its new index (now 1, previously 0).
279    /// - The `Block(bar)` was deduplicated any only exists once in the merged forest.
280    ///
281    /// The function also returns a vector of [`MastForestRootMap`]s, whose length equals the number
282    /// of passed `forests`. The indices in the vector correspond to the ones in `forests`. The map
283    /// of a given forest contains the new locations of its roots in the merged forest. To
284    /// illustrate, the above example would return a vector of two maps:
285    ///
286    /// ```text
287    /// vec![{0 -> 0, 1 -> 1}
288    ///      {0 -> 1, 1 -> 2}]
289    /// ```
290    ///
291    /// - The root locations of the original forest are unchanged.
292    /// - For the second forest, the `bar` block has moved from index 0 to index 1 in the merged
293    ///   forest, and the `Call` has moved from index 1 to 2.
294    ///
295    /// If any forest being merged contains an `External(qux)` node and another forest contains a
296    /// node whose digest is `qux`, then the external node will be replaced with the `qux` node,
297    /// which is effectively deduplication. Decorators are ignored when it comes to merging
298    /// External nodes. This means that an External node with decorators may be replaced by a node
299    /// without decorators or vice versa.
300    pub fn merge<'forest>(
301        forests: impl IntoIterator<Item = &'forest MastForest>,
302    ) -> Result<(MastForest, MastForestRootMap), MastForestError> {
303        MastForestMerger::merge(forests)
304    }
305}
306
307// ------------------------------------------------------------------------------------------------
308/// Helpers
309impl MastForest {
310    /// Adds all provided nodes to the internal set of nodes, remapping all [`MastNodeId`]
311    /// references in those nodes.
312    ///
313    /// # Panics
314    /// - Panics if the internal set of nodes is not empty.
315    fn remap_and_add_nodes(
316        &mut self,
317        nodes_to_add: Vec<MastNode>,
318        id_remappings: &BTreeMap<MastNodeId, MastNodeId>,
319    ) {
320        assert!(self.nodes.is_empty());
321        // extract decorator information from the nodes by converting them into builders
322        let node_builders =
323            nodes_to_add.into_iter().map(|node| node.to_builder(self)).collect::<Vec<_>>();
324
325        // Clear decorator storage after extracting builders (builders contain decorator data)
326        self.debug_info.clear_mappings();
327
328        // Add each node to the new MAST forest, making sure to rewrite any outdated internal
329        // `MastNodeId`s
330        for live_node_builder in node_builders {
331            live_node_builder.remap_children(id_remappings).add_to_forest(self).unwrap();
332        }
333    }
334
335    /// Remaps and adds all old root ids to the internal set of roots.
336    ///
337    /// # Panics
338    /// - Panics if the internal set of roots is not empty.
339    fn remap_and_add_roots(
340        &mut self,
341        old_root_ids: Vec<MastNodeId>,
342        id_remappings: &BTreeMap<MastNodeId, MastNodeId>,
343    ) {
344        assert!(self.roots.is_empty());
345
346        for old_root_id in old_root_ids {
347            let new_root_id = id_remappings.get(&old_root_id).copied().unwrap_or(old_root_id);
348            self.make_root(new_root_id);
349        }
350    }
351}
352
353/// Returns the set of nodes that are live, as well as the mapping from "old ID" to "new ID" for all
354/// live nodes.
355fn remove_nodes(
356    mast_nodes: Vec<MastNode>,
357    nodes_to_remove: &BTreeSet<MastNodeId>,
358) -> (Vec<MastNode>, BTreeMap<MastNodeId, MastNodeId>) {
359    // Note: this allows us to safely use `usize as u32`, guaranteeing that it won't wrap around.
360    assert!(mast_nodes.len() < u32::MAX as usize);
361
362    let mut retained_nodes = Vec::with_capacity(mast_nodes.len());
363    let mut id_remappings = BTreeMap::new();
364
365    for (old_node_index, old_node) in mast_nodes.into_iter().enumerate() {
366        let old_node_id: MastNodeId = MastNodeId(old_node_index as u32);
367
368        if !nodes_to_remove.contains(&old_node_id) {
369            let new_node_id: MastNodeId = MastNodeId(retained_nodes.len() as u32);
370            id_remappings.insert(old_node_id, new_node_id);
371
372            retained_nodes.push(old_node);
373        }
374    }
375
376    (retained_nodes, id_remappings)
377}
378
379// ------------------------------------------------------------------------------------------------
380/// Public accessors
381impl MastForest {
382    /// Returns the [`MastNode`] associated with the provided [`MastNodeId`] if valid, or else
383    /// `None`.
384    ///
385    /// This is the fallible version of indexing (e.g. `mast_forest[node_id]`).
386    #[inline(always)]
387    pub fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> {
388        self.nodes.get(node_id)
389    }
390
391    /// Returns the [`MastNodeId`] of the procedure associated with a given digest, if any.
392    #[inline(always)]
393    pub fn find_procedure_root(&self, digest: Word) -> Option<MastNodeId> {
394        self.roots.iter().find(|&&root_id| self[root_id].digest() == digest).copied()
395    }
396
397    /// Returns true if a node with the specified ID is a root of a procedure in this MAST forest.
398    pub fn is_procedure_root(&self, node_id: MastNodeId) -> bool {
399        self.roots.contains(&node_id)
400    }
401
402    /// Returns an iterator over the digests of all procedures in this MAST forest.
403    pub fn procedure_digests(&self) -> impl Iterator<Item = Word> + '_ {
404        self.roots.iter().map(|&root_id| self[root_id].digest())
405    }
406
407    /// Returns an iterator over the digests of local procedures in this MAST forest.
408    ///
409    /// A local procedure is defined as a procedure which is not a single external node.
410    pub fn local_procedure_digests(&self) -> impl Iterator<Item = Word> + '_ {
411        self.roots.iter().filter_map(|&root_id| {
412            let node = &self[root_id];
413            if node.is_external() { None } else { Some(node.digest()) }
414        })
415    }
416
417    /// Returns an iterator over the IDs of the procedures in this MAST forest.
418    pub fn procedure_roots(&self) -> &[MastNodeId] {
419        &self.roots
420    }
421
422    /// Returns the number of procedures in this MAST forest.
423    pub fn num_procedures(&self) -> u32 {
424        self.roots
425            .len()
426            .try_into()
427            .expect("MAST forest contains more than 2^32 procedures.")
428    }
429
430    /// Returns the [Word] representing the content hash of a subset of [`MastNodeId`]s.
431    ///
432    /// # Panics
433    /// This function panics if any `node_ids` is not a node of this forest.
434    pub fn compute_nodes_commitment<'a>(
435        &self,
436        node_ids: impl IntoIterator<Item = &'a MastNodeId>,
437    ) -> Word {
438        let mut digests: Vec<Word> = node_ids.into_iter().map(|&id| self[id].digest()).collect();
439        digests.sort_unstable_by_key(|word| LexicographicWord::from(*word));
440        miden_crypto::hash::poseidon2::Poseidon2::merge_many(&digests)
441    }
442
443    /// Returns the commitment to this MAST forest.
444    ///
445    /// The commitment is computed as the sequential hash of all procedure roots in the forest.
446    /// This value is cached after the first computation and reused for subsequent calls,
447    /// unless the forest is mutated (in which case the cache is invalidated).
448    ///
449    /// The commitment uniquely identifies the forest's structure, as each root's digest
450    /// transitively includes all of its descendants. Therefore, a commitment to all roots
451    /// is a commitment to the entire forest.
452    pub fn commitment(&self) -> Word {
453        *self.commitment_cache.get_or_init(|| self.compute_nodes_commitment(&self.roots))
454    }
455
456    /// Returns the number of nodes in this MAST forest.
457    pub fn num_nodes(&self) -> u32 {
458        self.nodes.len() as u32
459    }
460
461    /// Returns the underlying nodes in this MAST forest.
462    pub fn nodes(&self) -> &[MastNode] {
463        self.nodes.as_slice()
464    }
465
466    pub fn advice_map(&self) -> &AdviceMap {
467        &self.advice_map
468    }
469
470    pub fn advice_map_mut(&mut self) -> &mut AdviceMap {
471        &mut self.advice_map
472    }
473
474    // SERIALIZATION
475    // --------------------------------------------------------------------------------------------
476
477    /// Serializes this MastForest without debug information.
478    ///
479    /// This produces a smaller output by omitting decorators, error codes, and procedure names.
480    /// The resulting bytes can be deserialized with the standard [`Deserializable`] impl,
481    /// which auto-detects the format and creates an empty [`DebugInfo`].
482    ///
483    /// Use this for production builds where debug info is not needed.
484    ///
485    /// # Example
486    ///
487    /// ```
488    /// use miden_core::{mast::MastForest, serde::Serializable};
489    ///
490    /// let forest = MastForest::new();
491    ///
492    /// // Full serialization (with debug info)
493    /// let full_bytes = forest.to_bytes();
494    ///
495    /// // Stripped serialization (without debug info)
496    /// let mut stripped_bytes = Vec::new();
497    /// forest.write_stripped(&mut stripped_bytes);
498    ///
499    /// // Both can be deserialized the same way
500    /// // let restored = MastForest::read_from_bytes(&stripped_bytes).unwrap();
501    /// ```
502    pub fn write_stripped<W: ByteWriter>(&self, target: &mut W) {
503        use serialization::StrippedMastForest;
504        StrippedMastForest(self).write_into(target);
505    }
506}
507
508// ------------------------------------------------------------------------------------------------
509/// Decorator methods
510impl MastForest {
511    /// Returns a list of all decorators contained in this [MastForest].
512    pub fn decorators(&self) -> &[Decorator] {
513        self.debug_info.decorators()
514    }
515
516    /// Returns the [`Decorator`] associated with the provided [`DecoratorId`] if valid, or else
517    /// `None`.
518    ///
519    /// This is the fallible version of indexing (e.g. `mast_forest[decorator_id]`).
520    #[inline]
521    pub fn decorator_by_id(&self, decorator_id: DecoratorId) -> Option<&Decorator> {
522        self.debug_info.decorator(decorator_id)
523    }
524
525    /// Returns decorator indices for a specific operation within a node.
526    ///
527    /// This is the primary accessor for reading decorators from the centralized storage.
528    /// Returns a slice of decorator IDs for the given operation.
529    #[inline]
530    pub(crate) fn decorator_indices_for_op(
531        &self,
532        node_id: MastNodeId,
533        local_op_idx: usize,
534    ) -> &[DecoratorId] {
535        self.debug_info.decorators_for_operation(node_id, local_op_idx)
536    }
537
538    /// Returns an iterator over decorator references for a specific operation within a node.
539    ///
540    /// This is the preferred method for accessing decorators, as it provides direct
541    /// references to the decorator objects.
542    #[inline]
543    pub fn decorators_for_op<'a>(
544        &'a self,
545        node_id: MastNodeId,
546        local_op_idx: usize,
547    ) -> impl Iterator<Item = &'a Decorator> + 'a {
548        self.decorator_indices_for_op(node_id, local_op_idx)
549            .iter()
550            .map(move |&decorator_id| &self[decorator_id])
551    }
552
553    /// Returns the decorators to be executed before this node is executed.
554    #[inline]
555    pub fn before_enter_decorators(&self, node_id: MastNodeId) -> &[DecoratorId] {
556        self.debug_info.before_enter_decorators(node_id)
557    }
558
559    /// Returns the decorators to be executed after this node is executed.
560    #[inline]
561    pub fn after_exit_decorators(&self, node_id: MastNodeId) -> &[DecoratorId] {
562        self.debug_info.after_exit_decorators(node_id)
563    }
564
565    /// Returns decorator links for a node, including operation indices.
566    ///
567    /// This provides a flattened view of all decorators for a node with their operation indices.
568    #[inline]
569    pub(crate) fn decorator_links_for_node<'a>(
570        &'a self,
571        node_id: MastNodeId,
572    ) -> Result<DecoratedLinks<'a>, DecoratorIndexError> {
573        self.debug_info.decorator_links_for_node(node_id)
574    }
575
576    /// Adds a decorator to the forest, and returns the associated [`DecoratorId`].
577    pub fn add_decorator(&mut self, decorator: Decorator) -> Result<DecoratorId, MastForestError> {
578        self.debug_info.add_decorator(decorator)
579    }
580
581    /// Adds a debug variable to the forest, and returns the associated [`DebugVarId`].
582    pub fn add_debug_var(
583        &mut self,
584        debug_var: DebugVarInfo,
585    ) -> Result<DebugVarId, MastForestError> {
586        self.debug_info.add_debug_var(debug_var)
587    }
588
589    /// Returns debug variable IDs for a specific operation within a node.
590    pub fn debug_vars_for_operation(
591        &self,
592        node_id: MastNodeId,
593        local_op_idx: usize,
594    ) -> &[DebugVarId] {
595        self.debug_info.debug_vars_for_operation(node_id, local_op_idx)
596    }
597
598    /// Returns the debug variable with the given ID, if it exists.
599    pub fn debug_var(&self, debug_var_id: DebugVarId) -> Option<&DebugVarInfo> {
600        self.debug_info.debug_var(debug_var_id)
601    }
602
603    /// Adds decorator IDs for a node to the storage.
604    ///
605    /// Used when building nodes for efficient decorator access during execution.
606    ///
607    /// # Note
608    /// This method does not validate decorator IDs immediately. Validation occurs during
609    /// operations that need to access the actual decorator data (e.g., merging, serialization).
610    #[inline]
611    pub(crate) fn register_node_decorators(
612        &mut self,
613        node_id: MastNodeId,
614        before_enter: &[DecoratorId],
615        after_exit: &[DecoratorId],
616    ) {
617        self.debug_info.register_node_decorators(node_id, before_enter, after_exit);
618    }
619
620    /// Returns the [`AssemblyOp`] associated with a node.
621    ///
622    /// For basic block nodes with a `target_op_idx`, returns the AssemblyOp for that operation.
623    /// For other nodes or when no `target_op_idx` is provided, returns the first AssemblyOp.
624    pub fn get_assembly_op(
625        &self,
626        node_id: MastNodeId,
627        target_op_idx: Option<usize>,
628    ) -> Option<&AssemblyOp> {
629        match target_op_idx {
630            Some(op_idx) => self.debug_info.asm_op_for_operation(node_id, op_idx),
631            None => self.debug_info.first_asm_op_for_node(node_id),
632        }
633    }
634}
635
636// ------------------------------------------------------------------------------------------------
637/// Validation methods
638impl MastForest {
639    /// Validates that all BasicBlockNodes in this forest satisfy the core invariants:
640    /// 1. Power-of-two number of groups in each batch
641    /// 2. No operation group ends with an operation requiring an immediate value
642    /// 3. The last operation group in a batch cannot contain operations requiring immediate values
643    /// 4. OpBatch structural consistency (num_groups <= BATCH_SIZE, group size <= GROUP_SIZE,
644    ///    indptr integrity, bounds checking)
645    ///
646    /// This addresses the gap created by PR 2094, where padding NOOPs are now inserted
647    /// at assembly time rather than dynamically during execution, and adds comprehensive
648    /// structural validation to prevent deserialization-time panics.
649    pub fn validate(&self) -> Result<(), MastForestError> {
650        // Validate basic block batch invariants
651        for (node_id_idx, node) in self.nodes.iter().enumerate() {
652            let node_id =
653                MastNodeId::new_unchecked(node_id_idx.try_into().expect("too many nodes"));
654            if let MastNode::Block(basic_block) = node {
655                basic_block.validate_batch_invariants().map_err(|error_msg| {
656                    MastForestError::InvalidBatchPadding(node_id, error_msg)
657                })?;
658            }
659        }
660
661        // Validate that all procedure name digests correspond to procedure roots in the forest
662        for (digest, _) in self.debug_info.procedure_names() {
663            if self.find_procedure_root(digest).is_none() {
664                return Err(MastForestError::InvalidProcedureNameDigest(digest));
665            }
666        }
667
668        Ok(())
669    }
670
671    /// Validates topological ordering of nodes and recomputes all node hashes.
672    ///
673    /// This method iterates through all nodes in index order, verifying:
674    /// 1. All child references point to nodes with smaller indices (topological order)
675    /// 2. Each node's recomputed digest matches its stored digest
676    ///
677    /// # Errors
678    ///
679    /// Returns `MastForestError::ForwardReference` if any node references a child that
680    /// appears later in the forest.
681    ///
682    /// Returns `MastForestError::HashMismatch` if any node's recomputed digest doesn't
683    /// match its stored digest.
684    fn validate_node_hashes(&self) -> Result<(), MastForestError> {
685        use crate::chiplets::hasher;
686
687        /// Checks that child_id references a node that appears before node_id in topological order.
688        fn check_no_forward_ref(
689            node_id: MastNodeId,
690            child_id: MastNodeId,
691        ) -> Result<(), MastForestError> {
692            if child_id.0 >= node_id.0 {
693                return Err(MastForestError::ForwardReference(node_id, child_id));
694            }
695            Ok(())
696        }
697
698        for (node_idx, node) in self.nodes.iter().enumerate() {
699            let node_id = MastNodeId::new_unchecked(node_idx as u32);
700
701            // Check topological ordering and compute expected digest
702            let computed_digest = match node {
703                MastNode::Block(block) => {
704                    let op_groups: Vec<Felt> =
705                        block.op_batches().iter().flat_map(|batch| *batch.groups()).collect();
706                    hasher::hash_elements(&op_groups)
707                },
708                MastNode::Join(join) => {
709                    let left_id = join.first();
710                    let right_id = join.second();
711                    check_no_forward_ref(node_id, left_id)?;
712                    check_no_forward_ref(node_id, right_id)?;
713
714                    let left_digest = self.nodes[left_id].digest();
715                    let right_digest = self.nodes[right_id].digest();
716                    hasher::merge_in_domain(&[left_digest, right_digest], JoinNode::DOMAIN)
717                },
718                MastNode::Split(split) => {
719                    let true_id = split.on_true();
720                    let false_id = split.on_false();
721                    check_no_forward_ref(node_id, true_id)?;
722                    check_no_forward_ref(node_id, false_id)?;
723
724                    let true_digest = self.nodes[true_id].digest();
725                    let false_digest = self.nodes[false_id].digest();
726                    hasher::merge_in_domain(&[true_digest, false_digest], SplitNode::DOMAIN)
727                },
728                MastNode::Loop(loop_node) => {
729                    let body_id = loop_node.body();
730                    check_no_forward_ref(node_id, body_id)?;
731
732                    let body_digest = self.nodes[body_id].digest();
733                    hasher::merge_in_domain(&[body_digest, Word::default()], LoopNode::DOMAIN)
734                },
735                MastNode::Call(call) => {
736                    let callee_id = call.callee();
737                    check_no_forward_ref(node_id, callee_id)?;
738
739                    let callee_digest = self.nodes[callee_id].digest();
740                    let domain = if call.is_syscall() {
741                        CallNode::SYSCALL_DOMAIN
742                    } else {
743                        CallNode::CALL_DOMAIN
744                    };
745                    hasher::merge_in_domain(&[callee_digest, Word::default()], domain)
746                },
747                MastNode::Dyn(dyn_node) => {
748                    if dyn_node.is_dyncall() {
749                        DynNode::DYNCALL_DEFAULT_DIGEST
750                    } else {
751                        DynNode::DYN_DEFAULT_DIGEST
752                    }
753                },
754                MastNode::External(_) => {
755                    // External nodes have externally-provided digests that cannot be recomputed
756                    continue;
757                },
758            };
759
760            let stored_digest = node.digest();
761            if computed_digest != stored_digest {
762                return Err(MastForestError::HashMismatch {
763                    node_id,
764                    expected: stored_digest,
765                    computed: computed_digest,
766                });
767            }
768        }
769
770        Ok(())
771    }
772}
773
774// ------------------------------------------------------------------------------------------------
775/// Error message methods
776impl MastForest {
777    /// Given an error code as a Felt, resolves it to its corresponding error message.
778    pub fn resolve_error_message(&self, code: Felt) -> Option<Arc<str>> {
779        let key = code.as_canonical_u64();
780        self.debug_info.error_message(key)
781    }
782
783    /// Registers an error message in the MAST Forest and returns the corresponding error code as a
784    /// Felt.
785    pub fn register_error(&mut self, msg: Arc<str>) -> Felt {
786        let code: Felt = error_code_from_msg(&msg);
787        // we use u64 as keys for the map
788        self.debug_info.insert_error_code(code.as_canonical_u64(), msg);
789        code
790    }
791}
792
793// ------------------------------------------------------------------------------------------------
794/// Procedure name methods
795impl MastForest {
796    /// Returns the procedure name for the given MAST root digest, if present.
797    pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
798        self.debug_info.procedure_name(digest)
799    }
800
801    /// Returns an iterator over all (digest, name) pairs of procedure names.
802    pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
803        self.debug_info.procedure_names()
804    }
805
806    /// Inserts a procedure name for the given MAST root digest.
807    pub fn insert_procedure_name(&mut self, digest: Word, name: Arc<str>) {
808        assert!(
809            self.find_procedure_root(digest).is_some(),
810            "attempted to insert procedure name for digest that is not a procedure root"
811        );
812        self.debug_info.insert_procedure_name(digest, name);
813    }
814
815    /// Returns a reference to the debug info for this forest.
816    pub fn debug_info(&self) -> &DebugInfo {
817        &self.debug_info
818    }
819
820    /// Returns a mutable reference to the debug info.
821    ///
822    /// This is intended for use by the assembler to register AssemblyOps and other debug
823    /// information during compilation.
824    pub fn debug_info_mut(&mut self) -> &mut DebugInfo {
825        &mut self.debug_info
826    }
827}
828
829// TEST HELPERS
830// ================================================================================================
831
832#[cfg(test)]
833impl MastForest {
834    /// Returns all decorators for a given node as a vector of (position, DecoratorId) tuples.
835    ///
836    /// This helper method combines before_enter, operation-indexed, and after_exit decorators
837    /// into a single collection, which is useful for testing decorator positions and ordering.
838    ///
839    /// **Performance Warning**: This method performs multiple allocations through collect() calls
840    /// and should not be relied upon for performance-critical code. It is intended for testing
841    /// only.
842    pub fn all_decorators(&self, node_id: MastNodeId) -> Vec<(usize, DecoratorId)> {
843        let node = &self[node_id];
844
845        // For non-basic blocks, just get before_enter and after_exit decorators at position 0
846        if !node.is_basic_block() {
847            let before_enter_decorators: Vec<_> = self
848                .before_enter_decorators(node_id)
849                .iter()
850                .map(|&deco_id| (0, deco_id))
851                .collect();
852
853            let after_exit_decorators: Vec<_> = self
854                .after_exit_decorators(node_id)
855                .iter()
856                .map(|&deco_id| (1, deco_id))
857                .collect();
858
859            return [before_enter_decorators, after_exit_decorators].concat();
860        }
861
862        // For basic blocks, we need to handle operation-indexed decorators with proper positioning
863        let block = node.unwrap_basic_block();
864
865        // Before-enter decorators are at position 0
866        let before_enter_decorators: Vec<_> = self
867            .before_enter_decorators(node_id)
868            .iter()
869            .map(|&deco_id| (0, deco_id))
870            .collect();
871
872        // Operation-indexed decorators with their actual positions
873        let op_indexed_decorators: Vec<_> =
874            self.decorator_links_for_node(node_id).unwrap().into_iter().collect();
875
876        // After-exit decorators are positioned after all operations
877        let after_exit_decorators: Vec<_> = self
878            .after_exit_decorators(node_id)
879            .iter()
880            .map(|&deco_id| (block.num_operations() as usize, deco_id))
881            .collect();
882
883        [before_enter_decorators, op_indexed_decorators, after_exit_decorators].concat()
884    }
885}
886
887// MAST FOREST INDEXING
888// ------------------------------------------------------------------------------------------------
889
890impl Index<MastNodeId> for MastForest {
891    type Output = MastNode;
892
893    #[inline(always)]
894    fn index(&self, node_id: MastNodeId) -> &Self::Output {
895        &self.nodes[node_id]
896    }
897}
898
899impl IndexMut<MastNodeId> for MastForest {
900    #[inline(always)]
901    fn index_mut(&mut self, node_id: MastNodeId) -> &mut Self::Output {
902        &mut self.nodes[node_id]
903    }
904}
905
906impl Index<DecoratorId> for MastForest {
907    type Output = Decorator;
908
909    #[inline(always)]
910    fn index(&self, decorator_id: DecoratorId) -> &Self::Output {
911        self.debug_info.decorator(decorator_id).expect("DecoratorId out of bounds")
912    }
913}
914
915impl IndexMut<DecoratorId> for MastForest {
916    #[inline(always)]
917    fn index_mut(&mut self, decorator_id: DecoratorId) -> &mut Self::Output {
918        self.debug_info.decorator_mut(decorator_id).expect("DecoratorId out of bounds")
919    }
920}
921
922// MAST NODE ID
923// ================================================================================================
924
925/// An opaque handle to a [`MastNode`] in some [`MastForest`]. It is the responsibility of the user
926/// to use a given [`MastNodeId`] with the corresponding [`MastForest`].
927///
928/// Note that the [`MastForest`] does *not* ensure that equal [`MastNode`]s have equal
929/// [`MastNodeId`] handles. Hence, [`MastNodeId`] equality must not be used to test for equality of
930/// the underlying [`MastNode`].
931#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
932#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
933#[cfg_attr(feature = "serde", serde(transparent))]
934#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
935pub struct MastNodeId(u32);
936
937/// Operations that mutate a MAST often produce this mapping between old and new NodeIds.
938pub type Remapping = BTreeMap<MastNodeId, MastNodeId>;
939
940impl MastNodeId {
941    /// Returns a new `MastNodeId` with the provided inner value, or an error if the provided
942    /// `value` is greater than the number of nodes in the forest.
943    ///
944    /// For use in deserialization.
945    pub fn from_u32_safe(
946        value: u32,
947        mast_forest: &MastForest,
948    ) -> Result<Self, DeserializationError> {
949        Self::from_u32_with_node_count(value, mast_forest.nodes.len())
950    }
951
952    /// Returns a new [`MastNodeId`] with the provided `node_id`, or an error if `node_id` is
953    /// greater than the number of nodes in the [`MastForest`] for which this ID is being
954    /// constructed.
955    pub fn from_usize_safe(
956        node_id: usize,
957        mast_forest: &MastForest,
958    ) -> Result<Self, DeserializationError> {
959        let node_id: u32 = node_id.try_into().map_err(|_| {
960            DeserializationError::InvalidValue(format!(
961                "node id '{node_id}' does not fit into a u32"
962            ))
963        })?;
964        MastNodeId::from_u32_safe(node_id, mast_forest)
965    }
966
967    /// Returns a new [`MastNodeId`] from the given `value` without checking its validity.
968    pub fn new_unchecked(value: u32) -> Self {
969        Self(value)
970    }
971
972    /// Returns a new [`MastNodeId`] with the provided `id`, or an error if `id` is greater or equal
973    /// to `node_count`. The `node_count` is the total number of nodes in the [`MastForest`] for
974    /// which this ID is being constructed.
975    ///
976    /// This function can be used when deserializing an id whose corresponding node is not yet in
977    /// the forest and [`Self::from_u32_safe`] would fail. For instance, when deserializing the ids
978    /// referenced by the Join node in this forest:
979    ///
980    /// ```text
981    /// [Join(1, 2), Block(foo), Block(bar)]
982    /// ```
983    ///
984    /// Since it is less safe than [`Self::from_u32_safe`] and usually not needed it is not public.
985    pub(super) fn from_u32_with_node_count(
986        id: u32,
987        node_count: usize,
988    ) -> Result<Self, DeserializationError> {
989        if (id as usize) < node_count {
990            Ok(Self(id))
991        } else {
992            Err(DeserializationError::InvalidValue(format!(
993                "Invalid deserialized MAST node ID '{id}', but {node_count} is the number of nodes in the forest",
994            )))
995        }
996    }
997
998    /// Remap the NodeId to its new position using the given [`Remapping`].
999    pub fn remap(&self, remapping: &Remapping) -> Self {
1000        *remapping.get(self).unwrap_or(self)
1001    }
1002}
1003
1004impl From<u32> for MastNodeId {
1005    fn from(value: u32) -> Self {
1006        MastNodeId::new_unchecked(value)
1007    }
1008}
1009
1010impl Idx for MastNodeId {}
1011
1012impl From<MastNodeId> for u32 {
1013    fn from(value: MastNodeId) -> Self {
1014        value.0
1015    }
1016}
1017
1018impl fmt::Display for MastNodeId {
1019    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1020        write!(f, "MastNodeId({})", self.0)
1021    }
1022}
1023
1024#[cfg(any(test, feature = "arbitrary"))]
1025impl proptest::prelude::Arbitrary for MastNodeId {
1026    type Parameters = ();
1027
1028    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1029        use proptest::prelude::*;
1030        any::<u32>().prop_map(MastNodeId).boxed()
1031    }
1032
1033    type Strategy = proptest::prelude::BoxedStrategy<Self>;
1034}
1035
1036// ITERATOR
1037
1038/// Iterates over all the nodes a root depends on, in pre-order. The iteration can include other
1039/// roots in the same forest.
1040pub struct SubtreeIterator<'a> {
1041    forest: &'a MastForest,
1042    discovered: Vec<MastNodeId>,
1043    unvisited: Vec<MastNodeId>,
1044}
1045impl<'a> SubtreeIterator<'a> {
1046    pub fn new(root: &MastNodeId, forest: &'a MastForest) -> Self {
1047        let discovered = vec![];
1048        let unvisited = vec![*root];
1049        SubtreeIterator { forest, discovered, unvisited }
1050    }
1051}
1052impl Iterator for SubtreeIterator<'_> {
1053    type Item = MastNodeId;
1054    fn next(&mut self) -> Option<MastNodeId> {
1055        while let Some(id) = self.unvisited.pop() {
1056            let node = &self.forest[id];
1057            if !node.has_children() {
1058                return Some(id);
1059            } else {
1060                self.discovered.push(id);
1061                node.append_children_to(&mut self.unvisited);
1062            }
1063        }
1064        self.discovered.pop()
1065    }
1066}
1067
1068// DECORATOR ID
1069// ================================================================================================
1070
1071/// An opaque handle to a [`Decorator`] in some [`MastForest`]. It is the responsibility of the user
1072/// to use a given [`DecoratorId`] with the corresponding [`MastForest`].
1073#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1074#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1075#[cfg_attr(feature = "serde", serde(transparent))]
1076pub struct DecoratorId(u32);
1077
1078impl DecoratorId {
1079    /// Returns a new `DecoratorId` with the provided inner value, or an error if the provided
1080    /// `value` is greater than the number of nodes in the forest.
1081    ///
1082    /// For use in deserialization.
1083    pub fn from_u32_safe(
1084        value: u32,
1085        mast_forest: &MastForest,
1086    ) -> Result<Self, DeserializationError> {
1087        Self::from_u32_bounded(value, mast_forest.debug_info.num_decorators())
1088    }
1089
1090    /// Returns a new `DecoratorId` with the provided inner value, or an error if the provided
1091    /// `value` is greater than or equal to `bound`.
1092    ///
1093    /// For use in deserialization when the bound is known without needing the full MastForest.
1094    pub fn from_u32_bounded(value: u32, bound: usize) -> Result<Self, DeserializationError> {
1095        if (value as usize) < bound {
1096            Ok(Self(value))
1097        } else {
1098            Err(DeserializationError::InvalidValue(format!(
1099                "Invalid deserialized MAST decorator id '{}', but allows only {} decorators",
1100                value, bound,
1101            )))
1102        }
1103    }
1104
1105    /// Creates a new [`DecoratorId`] without checking its validity.
1106    pub(crate) fn new_unchecked(value: u32) -> Self {
1107        Self(value)
1108    }
1109}
1110
1111impl From<u32> for DecoratorId {
1112    fn from(value: u32) -> Self {
1113        DecoratorId::new_unchecked(value)
1114    }
1115}
1116
1117impl Idx for DecoratorId {}
1118
1119impl From<DecoratorId> for u32 {
1120    fn from(value: DecoratorId) -> Self {
1121        value.0
1122    }
1123}
1124
1125impl fmt::Display for DecoratorId {
1126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1127        write!(f, "DecoratorId({})", self.0)
1128    }
1129}
1130
1131impl Serializable for DecoratorId {
1132    fn write_into<W: ByteWriter>(&self, target: &mut W) {
1133        self.0.write_into(target)
1134    }
1135}
1136
1137impl Deserializable for DecoratorId {
1138    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1139        let value = u32::read_from(source)?;
1140        Ok(Self(value))
1141    }
1142}
1143
1144// ASM OP ID
1145// ================================================================================================
1146
1147/// Unique identifier for an [`AssemblyOp`] within a [`MastForest`].
1148///
1149/// Unlike decorators (which are executed at runtime), AssemblyOps are metadata
1150/// used only for error context and debugging tools.
1151#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1152#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1153#[cfg_attr(feature = "serde", serde(transparent))]
1154pub struct AsmOpId(u32);
1155
1156impl AsmOpId {
1157    /// Creates a new [`AsmOpId`] with the provided inner value.
1158    pub const fn new(value: u32) -> Self {
1159        Self(value)
1160    }
1161}
1162
1163impl From<u32> for AsmOpId {
1164    fn from(value: u32) -> Self {
1165        AsmOpId::new(value)
1166    }
1167}
1168
1169impl Idx for AsmOpId {}
1170
1171impl From<AsmOpId> for u32 {
1172    fn from(id: AsmOpId) -> Self {
1173        id.0
1174    }
1175}
1176
1177impl fmt::Display for AsmOpId {
1178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1179        write!(f, "AsmOpId({})", self.0)
1180    }
1181}
1182
1183impl Serializable for AsmOpId {
1184    fn write_into<W: ByteWriter>(&self, target: &mut W) {
1185        self.0.write_into(target)
1186    }
1187}
1188
1189impl Deserializable for AsmOpId {
1190    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1191        let value = u32::read_from(source)?;
1192        Ok(Self(value))
1193    }
1194}
1195
1196/// Derives an error code from an error message by hashing the message and returning the 0th element
1197/// of the resulting [`Word`].
1198pub fn error_code_from_msg(msg: impl AsRef<str>) -> Felt {
1199    // hash the message and return 0th felt of the resulting Word
1200    hash_string_to_word(msg.as_ref())[0]
1201}
1202
1203// MAST FOREST ERROR
1204// ================================================================================================
1205
1206/// Represents the types of errors that can occur when dealing with MAST forest.
1207#[derive(Debug, thiserror::Error, PartialEq, Eq)]
1208pub enum MastForestError {
1209    #[error("MAST forest decorator count exceeds the maximum of {} decorators", u32::MAX)]
1210    TooManyDecorators,
1211    #[error("MAST forest node count exceeds the maximum of {} nodes", MastForest::MAX_NODES)]
1212    TooManyNodes,
1213    #[error("node id {0} is greater than or equal to forest length {1}")]
1214    NodeIdOverflow(MastNodeId, usize),
1215    #[error("decorator id {0} is greater than or equal to decorator count {1}")]
1216    DecoratorIdOverflow(DecoratorId, usize),
1217    #[error("basic block cannot be created from an empty list of operations")]
1218    EmptyBasicBlock,
1219    #[error(
1220        "decorator root of child with node id {0} is missing but is required for fingerprint computation"
1221    )]
1222    ChildFingerprintMissing(MastNodeId),
1223    #[error("advice map key {0} already exists when merging forests")]
1224    AdviceMapKeyCollisionOnMerge(Word),
1225    #[error("decorator storage error: {0}")]
1226    DecoratorError(DecoratorIndexError),
1227    #[error("digest is required for deserialization")]
1228    DigestRequiredForDeserialization,
1229    #[error("invalid batch in basic block node {0:?}: {1}")]
1230    InvalidBatchPadding(MastNodeId, String),
1231    #[error("procedure name references digest that is not a procedure root: {0:?}")]
1232    InvalidProcedureNameDigest(Word),
1233    #[error(
1234        "node {0:?} references child {1:?} which comes after it in the forest (forward reference)"
1235    )]
1236    ForwardReference(MastNodeId, MastNodeId),
1237    #[error("hash mismatch for node {node_id:?}: expected {expected:?}, computed {computed:?}")]
1238    HashMismatch {
1239        node_id: MastNodeId,
1240        expected: Word,
1241        computed: Word,
1242    },
1243}
1244
1245// Custom serde implementations for MastForest that handle linked decorators properly
1246// by delegating to the existing miden-crypto serialization which already handles
1247// the conversion between linked and owned decorator formats.
1248#[cfg(feature = "serde")]
1249impl serde::Serialize for MastForest {
1250    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1251    where
1252        S: serde::Serializer,
1253    {
1254        // Use the existing miden-crypto serialization which already handles linked decorators
1255        let bytes = Serializable::to_bytes(self);
1256        serializer.serialize_bytes(&bytes)
1257    }
1258}
1259
1260#[cfg(feature = "serde")]
1261impl<'de> serde::Deserialize<'de> for MastForest {
1262    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1263    where
1264        D: serde::Deserializer<'de>,
1265    {
1266        // Deserialize bytes, then use miden-crypto Deserializable
1267        let bytes = Vec::<u8>::deserialize(deserializer)?;
1268        let mut slice_reader = SliceReader::new(&bytes);
1269        Deserializable::read_from(&mut slice_reader).map_err(serde::de::Error::custom)
1270    }
1271}
1272
1273// UNTRUSTED MAST FOREST
1274// ================================================================================================
1275
1276/// A [`MastForest`] deserialized from untrusted input that has not yet been validated.
1277///
1278/// This type wraps a `MastForest` that was deserialized from bytes but has not had its
1279/// node hashes verified. Before using the forest, callers must call [`validate()`](Self::validate)
1280/// to verify structural integrity and recompute all node hashes.
1281///
1282/// # Usage
1283///
1284/// ```ignore
1285/// // Deserialize from untrusted bytes
1286/// let untrusted = UntrustedMastForest::read_from_bytes(&bytes)?;
1287///
1288/// // Validate structure and hashes
1289/// let forest = untrusted.validate()?;
1290///
1291/// // Now safe to use
1292/// let root = forest.procedure_roots()[0];
1293/// ```
1294///
1295/// # Security
1296///
1297/// This type exists to provide type-level safety for untrusted deserialization. The validation
1298/// performed by [`validate()`](Self::validate) includes:
1299///
1300/// 1. **Structural validation**: Checks that basic block batch invariants are satisfied and
1301///    procedure names reference valid roots.
1302/// 2. **Topological ordering**: Verifies that all node references point to nodes that appear
1303///    earlier in the forest (no forward references).
1304/// 3. **Hash recomputation**: Recomputes the digest for every node and verifies it matches the
1305///    stored digest.
1306#[derive(Debug, Clone)]
1307pub struct UntrustedMastForest(MastForest);
1308
1309impl UntrustedMastForest {
1310    /// Validates the forest by checking structural invariants and recomputing all node hashes.
1311    ///
1312    /// This method performs a complete validation of the deserialized forest:
1313    ///
1314    /// 1. Validates structural invariants (batch padding, procedure names)
1315    /// 2. Validates topological ordering (no forward references)
1316    /// 3. Recomputes all node hashes and compares against stored digests
1317    ///
1318    /// # Returns
1319    ///
1320    /// - `Ok(MastForest)` if validation succeeds
1321    /// - `Err(MastForestError)` with details about the first validation failure
1322    ///
1323    /// # Errors
1324    ///
1325    /// Returns an error if:
1326    /// - Any basic block has invalid batch structure ([`MastForestError::InvalidBatchPadding`])
1327    /// - Any procedure name references a non-root digest
1328    ///   ([`MastForestError::InvalidProcedureNameDigest`])
1329    /// - Any node references a child that appears later in the forest
1330    ///   ([`MastForestError::ForwardReference`])
1331    /// - Any node's recomputed hash doesn't match its stored digest
1332    ///   ([`MastForestError::HashMismatch`])
1333    pub fn validate(self) -> Result<MastForest, MastForestError> {
1334        let forest = self.0;
1335
1336        // Step 1: Validate structural invariants (existing validate() checks)
1337        forest.validate()?;
1338
1339        // Step 2: Validate topological ordering and recompute hashes
1340        forest.validate_node_hashes()?;
1341
1342        Ok(forest)
1343    }
1344
1345    /// Deserializes an [`UntrustedMastForest`] from bytes.
1346    ///
1347    /// This method uses a [`BudgetedReader`] with a budget equal to the input size to protect
1348    /// against denial-of-service attacks from malicious input.
1349    ///
1350    /// For stricter limits, use
1351    /// [`read_from_bytes_with_budget`](Self::read_from_bytes_with_budget) with a custom budget.
1352    ///
1353    /// # Example
1354    ///
1355    /// ```ignore
1356    /// // Read from untrusted source
1357    /// let untrusted = UntrustedMastForest::read_from_bytes(&bytes)?;
1358    ///
1359    /// // Validate before use
1360    /// let forest = untrusted.validate()?;
1361    /// ```
1362    pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
1363        Self::read_from_bytes_with_budget(bytes, bytes.len())
1364    }
1365
1366    /// Deserializes an [`UntrustedMastForest`] from bytes with a byte budget.
1367    ///
1368    /// This method uses a [`BudgetedReader`] to limit memory consumption during deserialization,
1369    /// protecting against denial-of-service attacks from malicious input that claims to contain
1370    /// an excessive number of elements.
1371    ///
1372    /// # Arguments
1373    ///
1374    /// * `bytes` - The serialized forest bytes
1375    /// * `budget` - Maximum bytes to consume during deserialization. Set this to `bytes.len()` for
1376    ///   typical use cases, or lower to enforce stricter limits.
1377    ///
1378    /// # Example
1379    ///
1380    /// ```ignore
1381    /// // Read from untrusted source with budget equal to input size
1382    /// let untrusted = UntrustedMastForest::read_from_bytes_with_budget(&bytes, bytes.len())?;
1383    ///
1384    /// // Validate before use
1385    /// let forest = untrusted.validate()?;
1386    /// ```
1387    ///
1388    /// # Security
1389    ///
1390    /// The budget limits:
1391    /// - Pre-allocation sizes when deserializing collections (via `max_alloc`)
1392    /// - Total bytes consumed during deserialization
1393    ///
1394    /// This prevents attacks where malicious input claims an unrealistic number of elements
1395    /// (e.g., `len = 2^60`), causing excessive memory allocation before any data is read.
1396    pub fn read_from_bytes_with_budget(
1397        bytes: &[u8],
1398        budget: usize,
1399    ) -> Result<Self, DeserializationError> {
1400        let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
1401        Self::read_from(&mut reader)
1402    }
1403}