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