Skip to main content

miden_mast_package/package/
serialization.rs

1//! The serialization format of `Package` is as follows:
2//!
3//! #### Header
4//! - `MAGIC_PACKAGE`, a 4-byte tag, followed by a NUL-byte, i.e. `b"\0"`
5//! - `VERSION`, a 3-byte semantic version number, 1 byte for each component, i.e. MAJ.MIN.PATCH
6//!
7//! #### Metadata
8//! - `name` (`String`)
9//! - `version` ([`miden_assembly_syntax::Version`] serialized as a `String`)
10//! - `description` (optional, `String`)
11//! - `kind` (`u8`, see [`crate::TargetType`])
12//!
13//! #### Code
14//! - `mast` (see [`miden_assembly_syntax::Library`])
15//!
16//! #### Manifest
17//! - `manifest` (see [`crate::PackageManifest`])
18//!
19//! #### Custom Sections
20//! - `sections` (a vector of zero or more [`crate::Section`])
21//!
22//! #### Reader trust policy
23//!
24//! Package deserialization has two independently important trust decisions:
25//!
26//! - whether the embedded [`MastForest`] must be recomputed and validated;
27//! - whether package-owned debug sections may be exposed to callers.
28//!
29//! [`Package::read_from`] and [`Package::read_from_bytes`] are the normal untrusted readers. They
30//! validate the embedded MAST forest and discard package-owned debug sections before returning the
31//! package. Use them for bytes received across a trust boundary.
32//!
33//! [`Package::read_from_trusted`] and [`Package::read_from_bytes_trusted`] are for local
34//! files/cache entries controlled by the same trusted build or execution system. They validate the
35//! embedded MAST forest, but preserve package-owned debug sections so [`Package::debug_info`] can
36//! decode them.
37//!
38//! [`Package::read_from_unchecked`] and [`Package::read_from_bytes_unchecked`] are also trusted
39//! same-domain readers, but skip MAST validation. Use them only for bytes that were already
40//! validated before being persisted by the same trusted system.
41//!
42//! Embedded kernel package bytes are stored in the opaque `kernel` custom section. Untrusted
43//! package reads may carry those bytes, but decoding the embedded kernel through the package API
44//! uses the untrusted reader and therefore strips any nested package-owned debug sections.
45
46use alloc::{
47    format,
48    string::{String, ToString},
49    sync::Arc,
50    vec::Vec,
51};
52
53use miden_assembly_syntax::ast::{self, AttributeSet, PathBuf};
54use miden_core::{
55    Word,
56    mast::{MastForest, MastNodeExt, MastNodeId, UntrustedMastForest},
57    serde::{
58        BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
59        SliceReader,
60    },
61};
62
63use super::{
64    ConstantExport, PackageId, PackageModule, PackageSubmodule, ProcedureExport, TargetType,
65    TypeExport,
66};
67use crate::{
68    Dependency, ManifestValidationError, Package, PackageExport, PackageManifest, Section,
69    debug_info::DebugSourceNodeId,
70};
71
72// CONSTANTS
73// ================================================================================================
74
75/// Magic string for detecting that a file is serialized [`Package`]
76const MAGIC_PACKAGE: &[u8; 5] = b"MASP\0";
77
78/// The format version.
79///
80/// If future modifications are made to this format, the version should be incremented by 1.
81const VERSION: [u8; 3] = [6, 0, 0];
82
83/// Byte-read budget multiplier for package deserialization from a byte slice.
84///
85/// The budget is intentionally finite to reject malicious length prefixes, but larger than the
86/// source length because collection deserialization uses conservative per-element size estimates.
87const PACKAGE_BYTE_READ_BUDGET_MULTIPLIER: usize = 64;
88
89// PACKAGE SERIALIZATION/DESERIALIZATION
90// ================================================================================================
91
92impl Package {
93    #[doc(hidden)]
94    pub fn write_header_into<W: ByteWriter>(&self, target: &mut W) {
95        // Write magic & version
96        target.write_bytes(MAGIC_PACKAGE);
97        target.write_bytes(&VERSION);
98
99        // Write package name
100        self.name.write_into(target);
101
102        // Write package version
103        self.version.to_string().write_into(target);
104
105        // Write package description
106        self.description.write_into(target);
107
108        // Write package kind
109        target.write_u8(self.kind.into());
110    }
111
112    #[doc(hidden)]
113    pub fn write_trailer_into<W: ByteWriter>(&self, target: &mut W) {
114        // Write manifest
115        self.manifest.write_into(target);
116
117        // Write custom sections
118        target.write_usize(self.sections.len());
119        for section in self.sections.iter() {
120            section.write_into(target);
121        }
122    }
123
124    /// Reads a trusted package from `source` without validating the embedded MAST forest.
125    ///
126    /// # Trust boundary
127    ///
128    /// This skips embedded MAST validation and trusts serialized node digests. Use it only for
129    /// bytes that were already validated before being persisted by the same trusted system.
130    ///
131    /// Do not use this for user-controlled packages, network input, registry artifacts, or any
132    /// other package that crosses a trust boundary. Use [`Package::read_from`] for those
133    /// inputs.
134    pub fn read_from_unchecked<R: ByteReader>(
135        source: &mut R,
136    ) -> Result<Self, DeserializationError> {
137        let header = Self::read_header_from(source)?;
138        let mast_forest = Self::read_mast_forest(source, false)?;
139        Self::read_from_with_header_and_mast(source, header, mast_forest, true)
140    }
141
142    /// Reads trusted package bytes without validating the embedded MAST forest.
143    ///
144    /// # Trust boundary
145    ///
146    /// This skips embedded MAST validation and trusts serialized node digests. Use it only for
147    /// bytes that were already validated before being persisted by the same trusted system.
148    ///
149    /// Do not use this for user-controlled packages, network input, registry artifacts, or any
150    /// other package that crosses a trust boundary. Use [`Package::read_from_bytes`] for those
151    /// inputs.
152    pub fn read_from_bytes_unchecked(bytes: &[u8]) -> Result<Self, DeserializationError> {
153        let mut source = SliceReader::new(bytes);
154        Self::read_from_unchecked(&mut source)
155    }
156
157    /// Reads a trusted local package while validating the embedded MAST forest.
158    ///
159    /// This keeps the same structural validation as [`Package::read_from`], but allows
160    /// package-owned debug sections to be decoded as trusted metadata. Use this only for local
161    /// files or cache artifacts controlled by this process or build system. Do not use this for
162    /// inbound artifacts from an untrusted channel; use [`Package::read_from`] instead so debug
163    /// sections are discarded before the package is exposed to callers.
164    pub fn read_from_trusted<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
165        let header = Self::read_header_from(source)?;
166        let mast_forest = Self::read_mast_forest(source, true)?;
167        Self::read_from_with_header_and_mast(source, header, mast_forest, true)
168    }
169
170    /// Reads trusted local package bytes while validating the embedded MAST forest.
171    ///
172    /// See [`Package::read_from_trusted`].
173    pub fn read_from_bytes_trusted(bytes: &[u8]) -> Result<Self, DeserializationError> {
174        let budget = bytes.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
175        let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
176        Self::read_from_trusted(&mut reader)
177    }
178
179    fn read_mast_forest<R: ByteReader>(
180        source: &mut R,
181        validate_mast_forest: bool,
182    ) -> Result<Arc<MastForest>, DeserializationError> {
183        if validate_mast_forest {
184            UntrustedMastForest::read_from(source)?.validate().map_err(|err| {
185                DeserializationError::InvalidValue(format!(
186                    "library contains an invalid untrusted MAST forest: {err}"
187                ))
188            })
189        } else {
190            MastForest::read_from(source)
191        }
192        .map(Arc::new)
193    }
194}
195
196impl Serializable for Package {
197    fn write_into<W: ByteWriter>(&self, target: &mut W) {
198        self.write_header_into(target);
199
200        // Write MAST artifact
201        self.mast.write_into(target);
202
203        self.write_trailer_into(target);
204    }
205}
206
207struct PackageHeader {
208    name: PackageId,
209    version: crate::Version,
210    description: Option<String>,
211    kind: TargetType,
212}
213
214impl Package {
215    fn read_header_from<R: ByteReader>(
216        source: &mut R,
217    ) -> Result<PackageHeader, DeserializationError> {
218        // Read and validate magic & version
219        let magic: [u8; 5] = source.read_array()?;
220        if magic != *MAGIC_PACKAGE {
221            return Err(DeserializationError::InvalidValue(format!(
222                "invalid magic bytes. Expected '{MAGIC_PACKAGE:?}', got '{magic:?}'"
223            )));
224        }
225
226        let version: [u8; 3] = source.read_array()?;
227        if version != VERSION {
228            return Err(DeserializationError::InvalidValue(format!(
229                "unsupported version. Got '{version:?}', but only '{VERSION:?}' is supported"
230            )));
231        }
232
233        // Read package name
234        let name = PackageId::read_from(source)?;
235
236        // Read package version
237        let version = String::read_from(source)?
238            .parse::<crate::Version>()
239            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
240
241        // Read package description
242        let description = Option::<String>::read_from(source)?;
243
244        // Read package kind
245        let kind_tag = source.read_u8()?;
246        let kind = TargetType::try_from(kind_tag)
247            .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
248
249        Ok(PackageHeader { name, version, description, kind })
250    }
251
252    fn read_from_with_header_and_mast<R: ByteReader>(
253        source: &mut R,
254        header: PackageHeader,
255        mast: Arc<MastForest>,
256        debug_sections_trusted: bool,
257    ) -> Result<Self, DeserializationError> {
258        let PackageHeader { name, version, description, kind } = header;
259
260        // Read manifest
261        let manifest = PackageManifest::read_from_safe(source, &mast)?;
262
263        // Read custom sections
264        let mut sections = Vec::<Section>::read_from(source)?;
265        if !debug_sections_trusted && sections.iter().any(|section| section.id.is_debug()) {
266            log::warn!(
267                "Package read ignored debug sections from an untrusted artifact; use Package::read_from_trusted for local cache/debug reads"
268            );
269            sections.retain(|section| !section.id.is_debug());
270        }
271
272        let mut package = Self {
273            name,
274            version,
275            digest: Default::default(),
276            description,
277            kind,
278            mast,
279            manifest,
280            sections,
281            debug_sections_trusted,
282        };
283
284        package
285            .compute_interface_digest()
286            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
287        package.recompute_mast_commitment();
288
289        Ok(package)
290    }
291}
292
293impl Deserializable for Package {
294    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
295        let header = Self::read_header_from(source)?;
296
297        // Read MAST artifact
298        let mast = Self::read_mast_forest(source, true)?;
299
300        Self::read_from_with_header_and_mast(source, header, mast, false)
301    }
302
303    fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
304        let budget = bytes.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
305        let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
306        Self::read_from(&mut reader)
307    }
308}
309
310// PACKAGE MANIFEST SERIALIZATION/DESERIALIZATION
311// ================================================================================================
312
313#[cfg(feature = "serde")]
314impl serde::Serialize for PackageManifest {
315    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
316    where
317        S: serde::Serializer,
318    {
319        use alloc::collections::BTreeMap;
320
321        use miden_assembly_syntax::Path;
322        use serde::ser::SerializeStruct;
323
324        struct PackageExports<'a>(&'a BTreeMap<Arc<Path>, PackageExport>);
325
326        impl serde::Serialize for PackageExports<'_> {
327            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
328            where
329                S: serde::Serializer,
330            {
331                use serde::ser::SerializeSeq;
332
333                let mut serializer = serializer.serialize_seq(Some(self.0.len()))?;
334                for value in self.0.values() {
335                    serializer.serialize_element(value)?;
336                }
337                serializer.end()
338            }
339        }
340
341        struct PackageModules<'a>(&'a BTreeMap<Arc<Path>, PackageModule>);
342
343        impl serde::Serialize for PackageModules<'_> {
344            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
345            where
346                S: serde::Serializer,
347            {
348                use serde::ser::SerializeSeq;
349
350                let mut serializer = serializer.serialize_seq(Some(self.0.len()))?;
351                for value in self.0.values() {
352                    serializer.serialize_element(value)?;
353                }
354                serializer.end()
355            }
356        }
357
358        let mut serializer = serializer.serialize_struct("PackageManifest", 4)?;
359        serializer.serialize_field("exports", &PackageExports(&self.exports))?;
360        serializer.serialize_field("modules", &PackageModules(&self.modules))?;
361        serializer.serialize_field("dependencies", &self.dependencies)?;
362        serializer.serialize_field("entrypoint", &self.entrypoint)?;
363        serializer.end()
364    }
365}
366
367#[cfg(feature = "serde")]
368impl<'de> serde::Deserialize<'de> for PackageManifest {
369    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
370    where
371        D: serde::Deserializer<'de>,
372    {
373        #[derive(serde::Deserialize)]
374        #[serde(field_identifier, rename_all = "lowercase")]
375        enum Field {
376            Exports,
377            Modules,
378            Dependencies,
379            Entrypoint,
380        }
381
382        struct PackageManifestVisitor;
383
384        impl<'de> serde::de::Visitor<'de> for PackageManifestVisitor {
385            type Value = PackageManifest;
386
387            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
388                formatter.write_str("struct PackageManifest")
389            }
390
391            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
392            where
393                A: serde::de::SeqAccess<'de>,
394            {
395                let exports = seq
396                    .next_element::<Vec<PackageExport>>()?
397                    .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
398                let modules = seq
399                    .next_element::<Vec<PackageModule>>()?
400                    .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
401                let dependencies = seq
402                    .next_element::<Vec<Dependency>>()?
403                    .ok_or_else(|| serde::de::Error::invalid_length(2, &self))?;
404                let entrypoint = seq
405                    .next_element::<Option<PathBuf>>()
406                    .map(|p| p.map(|p| p.map(Arc::<ast::Path>::from)))?;
407                PackageManifest::new(exports)
408                    .and_then(|manifest| manifest.with_modules(modules))
409                    .and_then(|manifest| manifest.with_dependencies(dependencies))
410                    .and_then(|manifest| {
411                        if let Some(Some(entrypoint)) = entrypoint {
412                            manifest.with_entrypoint(entrypoint)
413                        } else {
414                            Ok(manifest)
415                        }
416                    })
417                    .map_err(serde::de::Error::custom)
418            }
419
420            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
421            where
422                A: serde::de::MapAccess<'de>,
423            {
424                let mut exports = None;
425                let mut modules = None;
426                let mut dependencies = None;
427                let mut entrypoint = None;
428                while let Some(key) = map.next_key()? {
429                    match key {
430                        Field::Exports => {
431                            if exports.is_some() {
432                                return Err(serde::de::Error::duplicate_field("exports"));
433                            }
434                            exports = Some(map.next_value::<Vec<PackageExport>>()?);
435                        },
436                        Field::Modules => {
437                            if modules.is_some() {
438                                return Err(serde::de::Error::duplicate_field("modules"));
439                            }
440                            modules = Some(map.next_value::<Vec<PackageModule>>()?);
441                        },
442                        Field::Dependencies => {
443                            if dependencies.is_some() {
444                                return Err(serde::de::Error::duplicate_field("dependencies"));
445                            }
446                            dependencies = Some(map.next_value::<Vec<Dependency>>()?);
447                        },
448                        Field::Entrypoint => {
449                            if entrypoint.is_some() {
450                                return Err(serde::de::Error::duplicate_field("entrypoint"));
451                            }
452                            entrypoint = Some(
453                                map.next_value::<Option<PathBuf>>()
454                                    .map(|p| p.map(Arc::<ast::Path>::from))?,
455                            );
456                        },
457                    }
458                }
459                let exports = exports.ok_or_else(|| serde::de::Error::missing_field("exports"))?;
460                let modules = modules.ok_or_else(|| serde::de::Error::missing_field("modules"))?;
461                let dependencies =
462                    dependencies.ok_or_else(|| serde::de::Error::missing_field("dependencies"))?;
463                PackageManifest::new(exports)
464                    .and_then(|manifest| manifest.with_modules(modules))
465                    .and_then(|manifest| manifest.with_dependencies(dependencies))
466                    .and_then(|manifest| {
467                        if let Some(Some(entrypoint)) = entrypoint {
468                            manifest.with_entrypoint(entrypoint)
469                        } else {
470                            Ok(manifest)
471                        }
472                    })
473                    .map_err(serde::de::Error::custom)
474            }
475        }
476
477        deserializer.deserialize_struct(
478            "PackageManifest",
479            &["exports", "modules", "dependencies", "entrypoint"],
480            PackageManifestVisitor,
481        )
482    }
483}
484
485impl Serializable for PackageManifest {
486    fn write_into<W: ByteWriter>(&self, target: &mut W) {
487        // Write exports
488        target.write_usize(self.num_exports());
489        for export in self.exports() {
490            export.write_into(target);
491        }
492
493        // Write module surfaces
494        target.write_usize(self.num_modules());
495        for module in self.modules() {
496            module.write_into(target);
497        }
498
499        // Write dependencies
500        target.write_usize(self.num_dependencies());
501        for dep in self.dependencies() {
502            dep.write_into(target);
503        }
504
505        // Write entrypoint
506        if let Some(entrypoint) = self.entrypoint.as_ref() {
507            target.write_bool(true);
508            entrypoint.write_into(target);
509        } else {
510            target.write_bool(false);
511        }
512    }
513}
514
515impl PackageManifest {
516    pub fn read_from_safe<R: ByteReader>(
517        source: &mut R,
518        mast: &MastForest,
519    ) -> Result<Self, DeserializationError> {
520        // Read exports
521        let exports_len = source.read_usize()?;
522        let max_exports = source.max_alloc(PackageExport::min_serialized_size());
523        if exports_len > max_exports {
524            return Err(DeserializationError::InvalidValue(format!(
525                "requested {exports_len} elements but reader can provide at most {max_exports}"
526            )));
527        }
528        let mut exports = Vec::with_capacity(exports_len);
529        for _ in 0..exports_len {
530            exports.push(PackageExport::read_from_safe(source, mast)?);
531        }
532
533        // Read module surfaces
534        let modules_len = source.read_usize()?;
535        let max_modules = source.max_alloc(PackageModule::min_serialized_size());
536        if modules_len > max_modules {
537            return Err(DeserializationError::InvalidValue(format!(
538                "requested {modules_len} elements but reader can provide at most {max_modules}"
539            )));
540        }
541        let modules = source.read_many_iter(modules_len)?.collect::<Result<Vec<_>, _>>()?;
542
543        // Read dependencies
544        let dependencies = Vec::<Dependency>::read_from(source)?;
545
546        // Read entrypoint
547        let entrypoint = if source.read_bool()? {
548            Some(PathBuf::read_from(source).map(Arc::<ast::Path>::from)?)
549        } else {
550            None
551        };
552
553        PackageManifest::new(exports)
554            .and_then(|manifest| manifest.with_modules(modules))
555            .and_then(|manifest| manifest.with_dependencies(dependencies))
556            .and_then(|manifest| {
557                if let Some(entrypoint) = entrypoint {
558                    manifest.with_entrypoint(entrypoint)
559                } else {
560                    Ok(manifest)
561                }
562            })
563            .map_err(|error| DeserializationError::InvalidValue(error.to_string()))
564    }
565}
566
567impl Deserializable for PackageManifest {
568    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
569        // Read exports
570        let exports_len = source.read_usize()?;
571        let exports = source.read_many_iter(exports_len)?.collect::<Result<Vec<_>, _>>()?;
572
573        // Read module surfaces
574        let modules_len = source.read_usize()?;
575        let modules = source.read_many_iter(modules_len)?.collect::<Result<Vec<_>, _>>()?;
576
577        // Read dependencies
578        let dependencies = Vec::<Dependency>::read_from(source)?;
579
580        // Read entrypoint
581        let entrypoint = if source.read_bool()? {
582            Some(PathBuf::read_from(source).map(Arc::<ast::Path>::from)?)
583        } else {
584            None
585        };
586
587        PackageManifest::new(exports)
588            .and_then(|manifest| manifest.with_modules(modules))
589            .and_then(|manifest| manifest.with_dependencies(dependencies))
590            .and_then(|manifest| {
591                if let Some(entrypoint) = entrypoint {
592                    manifest.with_entrypoint(entrypoint)
593                } else {
594                    Ok(manifest)
595                }
596            })
597            .map_err(|error| DeserializationError::InvalidValue(error.to_string()))
598    }
599}
600
601// PACKAGE MODULE SURFACE SERIALIZATION/DESERIALIZATION
602// ================================================================================================
603
604impl Serializable for PackageModule {
605    fn write_into<W: ByteWriter>(&self, target: &mut W) {
606        self.path.write_into(target);
607        target.write_usize(self.submodules.len());
608        for submodule in self.submodules.iter() {
609            submodule.write_into(target);
610        }
611    }
612}
613
614impl Deserializable for PackageModule {
615    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
616        let path = PathBuf::read_from(source)?.into_boxed_path().into();
617        let submodules = Vec::<PackageSubmodule>::read_from(source)?;
618        Ok(Self { path, submodules })
619    }
620}
621
622impl Serializable for PackageSubmodule {
623    fn write_into<W: ByteWriter>(&self, target: &mut W) {
624        self.name.write_into(target);
625    }
626}
627
628impl Deserializable for PackageSubmodule {
629    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
630        let name = ast::Ident::read_from(source)?;
631        Ok(Self { name })
632    }
633}
634
635// PACKAGE EXPORT SERIALIZATION/DESERIALIZATION
636// ================================================================================================
637
638impl Serializable for PackageExport {
639    fn write_into<W: ByteWriter>(&self, target: &mut W) {
640        target.write_u8(self.tag());
641        match self {
642            Self::Procedure(export) => export.write_into(target),
643            Self::Constant(export) => export.write_into(target),
644            Self::Type(export) => export.write_into(target),
645        }
646    }
647}
648
649impl PackageExport {
650    pub fn read_from_safe<R: ByteReader>(
651        source: &mut R,
652        mast: &MastForest,
653    ) -> Result<Self, DeserializationError> {
654        match source.read_u8()? {
655            1 => ProcedureExport::read_from_safe(source, mast).map(Self::Procedure),
656            2 => ConstantExport::read_from(source).map(Self::Constant),
657            3 => TypeExport::read_from(source).map(Self::Type),
658            invalid => Err(DeserializationError::InvalidValue(format!(
659                "unexpected PackageExport tag: '{invalid}'"
660            ))),
661        }
662    }
663}
664
665impl Deserializable for PackageExport {
666    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
667        match source.read_u8()? {
668            1 => ProcedureExport::read_from(source).map(Self::Procedure),
669            2 => ConstantExport::read_from(source).map(Self::Constant),
670            3 => TypeExport::read_from(source).map(Self::Type),
671            invalid => Err(DeserializationError::InvalidValue(format!(
672                "unexpected PackageExport tag: '{invalid}'"
673            ))),
674        }
675    }
676}
677
678impl Serializable for ProcedureExport {
679    fn write_into<W: ByteWriter>(&self, target: &mut W) {
680        self.path.write_into(target);
681        if let Some(node_id) = self.node {
682            target.write_bool(true);
683            target.write_u32(node_id.into());
684        } else {
685            target.write_bool(false);
686        }
687        if let Some(source_node) = self.source_node {
688            target.write_bool(true);
689            source_node.write_into(target);
690        } else {
691            target.write_bool(false);
692        }
693        self.digest.write_into(target);
694        match self.signature.as_ref() {
695            Some(sig) => {
696                target.write_bool(true);
697                sig.write_into(target);
698            },
699            None => {
700                target.write_bool(false);
701            },
702        }
703        self.attributes.write_into(target);
704    }
705}
706
707impl ProcedureExport {
708    pub fn read_from_safe<R: ByteReader>(
709        source: &mut R,
710        mast: &MastForest,
711    ) -> Result<Self, DeserializationError> {
712        use miden_assembly_syntax::ast::types::FunctionType;
713        let path = PathBuf::read_from(source)?.into_boxed_path().into();
714        let node = if source.read_bool()? {
715            let node_id = MastNodeId::from_u32_safe(source.read_u32()?, mast)?;
716            if !mast.is_procedure_root(node_id) {
717                return Err(DeserializationError::InvalidValue(
718                    ManifestValidationError::InvalidProcedureExport { path }.to_string(),
719                ));
720            }
721            Some(node_id)
722        } else {
723            None
724        };
725        let source_node = if source.read_bool()? {
726            Some(DebugSourceNodeId::read_from(source)?)
727        } else {
728            None
729        };
730        let digest = Word::read_from(source)?;
731        // Ensure that the digest associated with `node` matches the provided digest
732        if let Some(node) = node
733            && digest != mast[node].digest()
734        {
735            return Err(DeserializationError::InvalidValue(
736                ManifestValidationError::InvalidProcedureExport { path }.to_string(),
737            ));
738        }
739        let signature = if source.read_bool()? {
740            Some(FunctionType::read_from(source)?)
741        } else {
742            None
743        };
744        let attributes = AttributeSet::read_from(source)?;
745        Ok(Self {
746            path,
747            node,
748            source_node,
749            digest,
750            signature,
751            attributes,
752        })
753    }
754}
755
756impl Deserializable for ProcedureExport {
757    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
758        use miden_assembly_syntax::ast::types::FunctionType;
759        let path = PathBuf::read_from(source)?.into_boxed_path().into();
760        let node = if source.read_bool()? {
761            Some(MastNodeId::new_unchecked(source.read_u32()?))
762        } else {
763            None
764        };
765        let source_node = if source.read_bool()? {
766            Some(DebugSourceNodeId::read_from(source)?)
767        } else {
768            None
769        };
770        let digest = Word::read_from(source)?;
771        let signature = if source.read_bool()? {
772            Some(FunctionType::read_from(source)?)
773        } else {
774            None
775        };
776        let attributes = AttributeSet::read_from(source)?;
777        Ok(Self {
778            path,
779            node,
780            source_node,
781            digest,
782            signature,
783            attributes,
784        })
785    }
786}
787
788impl Serializable for ConstantExport {
789    fn write_into<W: ByteWriter>(&self, target: &mut W) {
790        self.path.write_into(target);
791        self.value.write_into(target);
792    }
793}
794
795impl Deserializable for ConstantExport {
796    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
797        let path = PathBuf::read_from(source)?.into_boxed_path().into();
798        let value = ast::ConstantValue::read_from(source)?;
799        Ok(Self { path, value })
800    }
801}
802
803impl Serializable for TypeExport {
804    fn write_into<W: ByteWriter>(&self, target: &mut W) {
805        self.path.write_into(target);
806        self.ty.write_into(target);
807    }
808}
809
810impl Deserializable for TypeExport {
811    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
812        use miden_assembly_syntax::ast::types::Type;
813        let path = PathBuf::read_from(source)?.into_boxed_path().into();
814        let ty = Type::read_from(source)?;
815        Ok(Self { path, ty })
816    }
817}
818
819#[cfg(test)]
820mod tests {
821    #[cfg(feature = "std")]
822    use alloc::format;
823    use alloc::{
824        string::{String, ToString},
825        sync::Arc,
826        vec,
827        vec::Vec,
828    };
829    use core::assert_matches;
830    use std::collections::BTreeMap;
831    #[cfg(feature = "std")]
832    use std::fs;
833
834    use miden_assembly_syntax::ast::{Ident, Path as AstPath, PathBuf, ProcedureName};
835    use miden_core::{
836        Felt, Word,
837        advice::AdviceMap,
838        mast::{
839            BasicBlockNodeBuilder, DenseMastForestBuilder, MastForest, MastNode, MastNodeExt,
840            MastNodeId,
841        },
842        operations::Operation,
843        serde::{
844            BudgetedReader, ByteWriter, Deserializable, DeserializationError, Serializable,
845            SliceReader,
846        },
847        utils::IndexVec,
848    };
849
850    use super::{
851        MAGIC_PACKAGE, PACKAGE_BYTE_READ_BUDGET_MULTIPLIER, Package, PackageManifest, Section,
852        VERSION,
853    };
854    use crate::{
855        Dependency, ManifestValidationError, PackageExport, PackageId, PackageModule,
856        PackageSubmodule, ProcedureExport, SectionId, TargetType,
857        debug_info::{
858            DebugSourceAsmOp, DebugSourceGraphSection, DebugSourceMapSection, DebugSourceNode,
859            DebugSourceNodeId,
860        },
861    };
862
863    fn build_single_node_forest(
864        operations: Vec<Operation>,
865        make_root: bool,
866    ) -> (MastForest, MastNodeId) {
867        let mut builder = DenseMastForestBuilder::new();
868        let node_id = builder
869            .push_node(BasicBlockNodeBuilder::new(operations))
870            .expect("failed to build basic block");
871        if make_root {
872            builder.mark_root(node_id);
873        }
874        let (forest, remapping) = builder.finish_with_id_map().expect("forest should be valid");
875        let node_id = remapping.get(node_id).expect("node should be retained");
876        (forest, node_id)
877    }
878
879    fn build_forest() -> (MastForest, MastNodeId) {
880        build_single_node_forest(vec![Operation::Add], true)
881    }
882
883    fn absolute_path(name: &str) -> Arc<AstPath> {
884        let path = PathBuf::new(name).expect("invalid path");
885        let path = path.as_path().to_absolute().unwrap().into_owned();
886        Arc::from(path.into_boxed_path())
887    }
888
889    fn build_package_exports() -> (Arc<MastForest>, Vec<PackageExport>) {
890        let (forest, node_id) = build_forest();
891        let path = absolute_path("test::proc");
892        let export =
893            ProcedureExport::new(Arc::clone(&path), Some(node_id), forest[node_id].digest(), None);
894
895        (Arc::new(forest), vec![PackageExport::Procedure(export)])
896    }
897
898    fn build_package() -> Package {
899        let (mast, exports) = build_package_exports();
900
901        Package::create(
902            PackageId::from("test_pkg"),
903            crate::Version::new(0, 0, 0),
904            TargetType::Library,
905            mast,
906            exports,
907            None,
908        )
909        .expect("test package should be valid")
910    }
911
912    fn build_package_with_debug_info() -> Package {
913        let mut nodes = IndexVec::<MastNodeId, MastNode>::new();
914        let node = BasicBlockNodeBuilder::new(vec![Operation::Add])
915            .build()
916            .expect("failed to build basic block");
917        let digest = node.digest();
918        let node_id = nodes.push(node.into()).expect("failed to add basic block");
919        let source_node = DebugSourceNodeId::from(0);
920
921        let mast = Arc::new(
922            MastForest::from_raw_parts(nodes, vec![node_id], AdviceMap::default())
923                .expect("forest should be valid"),
924        );
925        let path = absolute_path("test::proc");
926        let exports = vec![PackageExport::Procedure(
927            ProcedureExport::new(path, Some(node_id), digest, None)
928                .with_source_node(Some(source_node)),
929        )];
930        let mut package = Package::create(
931            PackageId::from("test_pkg"),
932            crate::Version::new(0, 0, 0),
933            TargetType::Library,
934            mast,
935            exports,
936            None,
937        )
938        .expect("test package should be valid");
939        let source_graph = DebugSourceGraphSection::from_parts(
940            vec![DebugSourceNode::new(node_id, Vec::new(), 0, 1)],
941            vec![source_node],
942        );
943        let source_map = DebugSourceMapSection::from_parts(
944            vec![DebugSourceAsmOp::new(source_node, 0, None, "trusted".into(), "add".into(), 1)],
945            Vec::new(),
946        );
947        package
948            .sections
949            .push(Section::new(SectionId::DEBUG_SOURCE_GRAPH, source_graph.to_bytes()));
950        package
951            .sections
952            .push(Section::new(SectionId::DEBUG_SOURCE_MAP, source_map.to_bytes()));
953        package
954    }
955
956    fn build_dependency() -> Dependency {
957        Dependency {
958            name: PackageId::from("dep"),
959            kind: TargetType::Library,
960            version: crate::Version::new(1, 0, 0),
961            digest: Default::default(),
962        }
963    }
964
965    fn package_bytes_with_sections_count(count: usize) -> Vec<u8> {
966        let package = build_package();
967        let mut bytes = Vec::new();
968
969        bytes.write_bytes(MAGIC_PACKAGE);
970        bytes.write_bytes(&VERSION);
971        package.name.write_into(&mut bytes);
972        package.version.to_string().write_into(&mut bytes);
973        package.description.write_into(&mut bytes);
974        bytes.write_u8(package.kind.into());
975        package.mast.write_into(&mut bytes);
976        package.manifest.write_into(&mut bytes);
977        bytes.write_usize(count);
978
979        bytes
980    }
981
982    #[test]
983    fn package_serialization_roundtrip() {
984        use proptest::{
985            prelude::*,
986            test_runner::{Config, TestRunner},
987        };
988
989        // since the test is quite expensive, 128 cases should be enough to cover all edge cases
990        // (default is 256)
991        let cases = 128;
992        TestRunner::new(Config::with_cases(cases))
993            .run(&any::<Package>(), move |package| {
994                let bytes = package.to_bytes();
995                let deserialized = Package::read_from_bytes(&bytes).unwrap();
996                let mut expected = package;
997                expected.sections.retain(|section| !section.id.is_debug());
998                prop_assert_eq!(expected.to_bytes(), deserialized.to_bytes());
999                Ok(())
1000            })
1001            .unwrap_or_else(|err| {
1002                panic!("{err}");
1003            });
1004    }
1005
1006    #[test]
1007    fn executable_package_entrypoint_roundtrips() {
1008        let (forest, node_id) = build_forest();
1009        let entrypoint =
1010            Arc::from(AstPath::exec_path().join(ProcedureName::MAIN_PROC_NAME).into_boxed_path());
1011        let export = ProcedureExport::new(
1012            Arc::clone(&entrypoint),
1013            Some(node_id),
1014            forest[node_id].digest(),
1015            None,
1016        );
1017        let package = Package::create(
1018            PackageId::from("test_pkg"),
1019            crate::Version::new(0, 0, 0),
1020            TargetType::Executable,
1021            Arc::new(forest),
1022            [PackageExport::Procedure(export)],
1023            None,
1024        )
1025        .expect("executable package should be valid");
1026
1027        let deserialized = Package::read_from_bytes(&package.to_bytes())
1028            .expect("executable package should deserialize without duplicate entrypoint errors");
1029
1030        assert_eq!(deserialized.manifest.entrypoint(), Some(entrypoint));
1031    }
1032
1033    #[test]
1034    fn package_checked_deserialization_discards_untrusted_debug_sections() {
1035        let package = build_package_with_debug_info();
1036        let bytes = package.to_bytes();
1037
1038        let deserialized = Package::read_from_bytes(&bytes).unwrap();
1039
1040        assert!(
1041            !deserialized.sections.iter().any(|section| section.id.is_debug()),
1042            "untrusted package reads should discard debug sections"
1043        );
1044        assert!(deserialized.debug_info().unwrap().is_none());
1045        let debug_source_map_id = SectionId::DEBUG_SOURCE_MAP.as_str().as_bytes();
1046        assert!(
1047            !deserialized
1048                .to_bytes()
1049                .windows(debug_source_map_id.len())
1050                .any(|window| window == debug_source_map_id),
1051            "discarded debug sections should not be reserialized"
1052        );
1053    }
1054
1055    #[test]
1056    fn package_trusted_deserialization_preserves_trusted_debug_sections() {
1057        let package = build_package_with_debug_info();
1058        let bytes = package.to_bytes();
1059
1060        let deserialized = Package::read_from_bytes_trusted(&bytes).unwrap();
1061
1062        assert!(
1063            deserialized
1064                .sections
1065                .iter()
1066                .any(|section| section.id == SectionId::DEBUG_SOURCE_MAP)
1067        );
1068        assert!(deserialized.debug_info().unwrap().is_some());
1069    }
1070
1071    #[test]
1072    fn package_unchecked_deserialization_preserves_trusted_debug_sections() {
1073        let package = build_package_with_debug_info();
1074        let bytes = package.to_bytes();
1075
1076        let deserialized = Package::read_from_bytes_unchecked(&bytes).unwrap();
1077
1078        assert!(
1079            deserialized
1080                .sections
1081                .iter()
1082                .any(|section| section.id == SectionId::DEBUG_SOURCE_MAP)
1083        );
1084        assert!(deserialized.debug_info().unwrap().is_some());
1085    }
1086
1087    #[cfg(feature = "std")]
1088    #[test]
1089    fn package_deserialize_from_file_discards_untrusted_debug_sections() {
1090        let package = build_package_with_debug_info();
1091        let path = std::env::temp_dir().join(format!(
1092            "miden-package-deserialize-{}-{}.masp",
1093            std::process::id(),
1094            "debug-sections"
1095        ));
1096        package.write_to_file(&path).unwrap();
1097
1098        let deserialized = Package::deserialize_from_file(&path).unwrap();
1099        fs::remove_file(&path).unwrap();
1100
1101        assert!(
1102            !deserialized.sections.iter().any(|section| section.id.is_debug()),
1103            "untrusted package file reads should discard debug sections"
1104        );
1105        assert!(deserialized.debug_info().unwrap().is_none());
1106    }
1107
1108    #[cfg(feature = "std")]
1109    #[test]
1110    fn package_deserialize_from_file_trusted_preserves_trusted_debug_sections() {
1111        let package = build_package_with_debug_info();
1112        let path = std::env::temp_dir().join(format!(
1113            "miden-package-deserialize-{}-{}.masp",
1114            std::process::id(),
1115            "trusted-debug-sections"
1116        ));
1117        package.write_to_file(&path).unwrap();
1118
1119        let deserialized = Package::deserialize_from_file_trusted(&path).unwrap();
1120        fs::remove_file(&path).unwrap();
1121
1122        assert!(
1123            deserialized
1124                .sections
1125                .iter()
1126                .any(|section| section.id == SectionId::DEBUG_SOURCE_MAP)
1127        );
1128        assert!(deserialized.debug_info().unwrap().is_some());
1129    }
1130
1131    #[test]
1132    fn package_content_digest_changes_when_identity_fields_change() {
1133        let package = build_package();
1134        let digest = package.content_digest();
1135
1136        let renamed = Package {
1137            name: PackageId::from("renamed_pkg"),
1138            ..package.clone()
1139        };
1140        assert_ne!(digest, renamed.content_digest());
1141
1142        let versioned = Package {
1143            version: crate::Version::new(1, 2, 3),
1144            ..package.clone()
1145        };
1146        assert_ne!(digest, versioned.content_digest());
1147
1148        let executable = Package { kind: TargetType::Executable, ..package };
1149        assert_ne!(digest, executable.content_digest());
1150    }
1151
1152    #[test]
1153    fn package_content_digest_changes_when_manifest_changes() {
1154        let package = build_package();
1155        let digest = package.content_digest();
1156
1157        let mut with_dependency = package;
1158        with_dependency
1159            .manifest
1160            .add_dependency(Dependency {
1161                name: PackageId::from("dep_pkg"),
1162                kind: TargetType::Library,
1163                version: crate::Version::new(1, 0, 0),
1164                digest: Word::from([1_u32, 2, 3, 4]),
1165            })
1166            .expect("test dependency should be unique");
1167        assert_ne!(digest, with_dependency.content_digest());
1168    }
1169
1170    #[test]
1171    fn package_content_digest_changes_when_account_component_metadata_changes() {
1172        let package = build_package();
1173        let digest = package.content_digest();
1174
1175        let with_metadata = Package {
1176            sections: vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![1, 2, 3, 4])],
1177            ..package.clone()
1178        };
1179        assert_ne!(digest, with_metadata.content_digest());
1180
1181        let with_different_metadata = Package {
1182            sections: vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![4, 3, 2, 1])],
1183            ..package
1184        };
1185        assert_ne!(with_metadata.content_digest(), with_different_metadata.content_digest());
1186    }
1187
1188    #[test]
1189    fn package_content_digest_ignores_description_and_opaque_custom_sections_for_now() {
1190        let package = build_package();
1191        let digest = package.content_digest();
1192
1193        let described = Package {
1194            description: Some(String::from("human-facing package description")),
1195            ..package.clone()
1196        };
1197        assert_eq!(digest, described.content_digest());
1198
1199        let with_section = Package {
1200            sections: vec![Section::new(
1201                SectionId::custom("opaque").expect("valid custom section id"),
1202                vec![1, 2, 3, 4],
1203            )],
1204            ..package
1205        };
1206        assert_eq!(digest, with_section.content_digest());
1207    }
1208
1209    #[test]
1210    fn package_manifest_rejects_over_budget_dependencies() {
1211        let mut bytes = Vec::new();
1212        bytes.write_usize(0);
1213        bytes.write_usize(0);
1214        bytes.write_usize(2);
1215
1216        let mut reader = BudgetedReader::new(SliceReader::new(&bytes), 2);
1217        let err = PackageManifest::read_from(&mut reader).unwrap_err();
1218        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1219    }
1220
1221    #[test]
1222    fn package_rejects_over_budget_sections() {
1223        let bytes = package_bytes_with_sections_count(2);
1224        let mut reader = BudgetedReader::new(SliceReader::new(&bytes), bytes.len());
1225        let err = Package::read_from(&mut reader).unwrap_err();
1226        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1227    }
1228
1229    #[test]
1230    fn package_read_from_bytes_rejects_fuzzed_oom_payload() {
1231        // This fuzz payload encodes counts large enough to cause excessive allocation or read work.
1232        // If this starts succeeding, package byte-slice deserialization is no longer budgeted.
1233        let payload = [
1234            0x4d, 0x41, 0x53, 0x50, 0x00, 0x04, 0x00, 0x00, 0x11, 0x74, 0x65, 0x73, 0x74, 0x5f,
1235            0x70, 0x6b, 0x67, 0x0b, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x00, 0x00, 0x4d, 0x41, 0x53,
1236            0x54, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x17, 0x03, 0x22,
1237            0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1238            0x00, 0x00, 0x30, 0x2f, 0x08, 0x0a, 0x21, 0xa9, 0xb6, 0xf6, 0x1a, 0x52, 0x30, 0xc5,
1239            0x64, 0xc7, 0xdb, 0x4d, 0x83, 0x0b, 0x32, 0x58, 0x89, 0x88, 0xb2, 0x78, 0x69, 0xbb,
1240            0x23, 0xa6, 0x18, 0x9c, 0xc9, 0x35, 0x2d, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1241            0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1242            0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x00, 0x0c, 0x00, 0x3a, 0x3a, 0x74, 0x65, 0x73,
1243            0x74, 0x3a, 0x3a, 0x70, 0x72, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
1244            0x0f, 0x03, 0x0f, 0x01, 0x00, 0x00, 0x17, 0x03, 0x22, 0x01, 0x00, 0x00, 0x00, 0x01,
1245            0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0xc9, 0x35, 0x2d, 0x01, 0x00, 0x03, 0x0f, 0x03,
1246            0x0f, 0x01, 0x01, 0x01,
1247        ];
1248
1249        let result = Package::read_from_bytes(&payload);
1250        assert!(result.is_err());
1251
1252        // Wrapped fuzz inputs must use the generic budgeted entry point; otherwise the outer
1253        // collection length can drive unbounded work before the inner package fails.
1254        let mut vec_payload = vec![0];
1255        vec_payload.extend_from_slice(&1000u64.to_le_bytes());
1256        let budget = vec_payload.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
1257        let result = Vec::<Package>::read_from_bytes_with_budget(&vec_payload, budget);
1258        assert!(result.is_err());
1259
1260        let mut option_payload = vec![1];
1261        option_payload.extend_from_slice(&payload);
1262        let budget = option_payload.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
1263        let result = Option::<Package>::read_from_bytes_with_budget(&option_payload, budget);
1264        assert!(result.is_err());
1265    }
1266
1267    /// Verifies that deserializing a library rejects procedure exports whose `MastNodeId` is not a
1268    /// procedure root in the underlying MAST forest (issue #2831).
1269    #[test]
1270    fn package_rejects_non_root_export() {
1271        let (forest, node_id) = build_single_node_forest(vec![Operation::Add], false);
1272        let digest = forest[node_id].digest();
1273
1274        let path = absolute_path("test::proc");
1275        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1276            Arc::clone(&path),
1277            Some(node_id),
1278            digest,
1279            None,
1280        ))];
1281
1282        let package = Package {
1283            name: PackageId::from("test_pkg"),
1284            version: crate::Version::new(0, 0, 0),
1285            digest,
1286            description: None,
1287            kind: TargetType::Library,
1288            mast: Arc::new(forest),
1289            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1290            sections: Default::default(),
1291            debug_sections_trusted: true,
1292        };
1293
1294        // Manually serialize the tampered package: forest + one export referencing a non-root node.
1295        let mut tampered_bytes = Vec::new();
1296        package.write_into(&mut tampered_bytes);
1297
1298        // Deserializing should fail because the export references a non-root node.
1299        let result = Package::read_from_bytes(&tampered_bytes);
1300        assert!(
1301            result.is_err(),
1302            "deserialization should reject exports referencing non-root nodes"
1303        );
1304        let err_msg = result.unwrap_err().to_string();
1305        assert!(
1306            err_msg.contains("node id and digest do not correspond to a procedure root"),
1307            "error should mention missing procedure root, got: {err_msg}"
1308        );
1309    }
1310
1311    #[test]
1312    fn package_manifest_new_rejects_duplicate_export_paths() {
1313        let path = absolute_path("test::proc");
1314        let exports = vec![
1315            PackageExport::Procedure(ProcedureExport::new(
1316                path.clone(),
1317                None,
1318                Word::default(),
1319                None,
1320            )),
1321            PackageExport::Procedure(ProcedureExport::new(
1322                path.clone(),
1323                None,
1324                Word::default(),
1325                None,
1326            )),
1327        ];
1328
1329        let err = PackageManifest::new(exports)
1330            .expect_err("duplicate export paths should be rejected by constructors");
1331        assert_matches!(err, ManifestValidationError::DuplicateExport(err_path) if err_path == path);
1332    }
1333
1334    #[test]
1335    fn package_manifest_roundtrips_module_surfaces() {
1336        let export = PackageExport::Procedure(ProcedureExport::new(
1337            absolute_path("test::api::foo"),
1338            None,
1339            Word::default(),
1340            None,
1341        ));
1342        let module = PackageModule::new(
1343            absolute_path("test"),
1344            [PackageSubmodule::new(Ident::new("api").unwrap())],
1345        );
1346        let child = PackageModule::new(absolute_path("test::api"), []);
1347
1348        let manifest = PackageManifest::new([export])
1349            .and_then(|manifest| manifest.with_modules([module, child]))
1350            .expect("manifest should be valid");
1351        let bytes = manifest.to_bytes();
1352        let decoded = PackageManifest::read_from_bytes(&bytes).expect("manifest should roundtrip");
1353
1354        let root = decoded
1355            .get_module(absolute_path("test").as_ref())
1356            .expect("root module surface should be present");
1357        assert_eq!(root.submodules().len(), 1);
1358        assert_eq!(root.submodules()[0].name.as_str(), "api");
1359        assert!(decoded.get_module(absolute_path("test::api").as_ref()).is_some());
1360    }
1361
1362    #[test]
1363    fn package_manifest_add_dependency_rejects_duplicate_dependencies() {
1364        let mut manifest = PackageManifest {
1365            exports: Default::default(),
1366            modules: Default::default(),
1367            dependencies: Default::default(),
1368            entrypoint: None,
1369        };
1370        let dependency = build_dependency();
1371
1372        manifest
1373            .add_dependency(dependency.clone())
1374            .expect("first dependency should be accepted");
1375        let err = manifest
1376            .add_dependency(dependency)
1377            .expect_err("duplicate dependencies should be rejected by helpers");
1378        assert_matches!(err, ManifestValidationError::DuplicateDependency(pkgid) if pkgid == "dep");
1379    }
1380
1381    #[test]
1382    fn package_manifest_rejects_duplicate_export_paths() {
1383        let path = absolute_path("test::proc");
1384        let export =
1385            PackageExport::Procedure(ProcedureExport::new(path, None, Word::default(), None));
1386
1387        let mut bytes = Vec::new();
1388        bytes.write_usize(2);
1389        export.write_into(&mut bytes);
1390        export.write_into(&mut bytes);
1391        bytes.write_usize(0);
1392        bytes.write_usize(0);
1393        bytes.write_bool(false);
1394
1395        let mut reader = SliceReader::new(&bytes);
1396        let err = PackageManifest::read_from(&mut reader)
1397            .expect_err("duplicate export paths should be rejected during deserialization");
1398        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1399    }
1400
1401    #[test]
1402    fn package_manifest_rejects_duplicate_dependencies() {
1403        let dependency = build_dependency();
1404
1405        let mut bytes = Vec::new();
1406        bytes.write_usize(0);
1407        bytes.write_usize(0);
1408        bytes.write_usize(2);
1409        dependency.write_into(&mut bytes);
1410        dependency.write_into(&mut bytes);
1411        bytes.write_bool(false);
1412
1413        let mut reader = SliceReader::new(&bytes);
1414        let err = PackageManifest::read_from(&mut reader)
1415            .expect_err("duplicate dependencies should be rejected during deserialization");
1416        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1417    }
1418
1419    #[test]
1420    fn package_manifest_deserialization_rejects_malformed_quoted_procedure_leaf() {
1421        let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1422        let exports = BTreeMap::from_iter([(
1423            bad.clone(),
1424            PackageExport::Procedure(ProcedureExport::new(bad, None, Default::default(), None)),
1425        )]);
1426
1427        let manifest = PackageManifest {
1428            exports,
1429            modules: Default::default(),
1430            dependencies: Default::default(),
1431            entrypoint: None,
1432        };
1433
1434        let bytes = manifest.to_bytes();
1435
1436        let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1437            "expected malformed procedure export leaf name rejection during deserialization",
1438        );
1439        let message = alloc::format!("{err}");
1440        assert_matches!(
1441            message,
1442            msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1443        );
1444    }
1445
1446    #[test]
1447    fn package_manifest_deserialization_rejects_malformed_quoted_constant_leaf() {
1448        let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1449        let exports = BTreeMap::from_iter([(
1450            bad.clone(),
1451            PackageExport::Constant(crate::ConstantExport {
1452                path: bad,
1453                value: miden_assembly_syntax::ast::ConstantValue::Int(
1454                    miden_debug_types::Span::unknown(1u32.into()),
1455                ),
1456            }),
1457        )]);
1458
1459        let manifest = PackageManifest {
1460            exports,
1461            modules: Default::default(),
1462            dependencies: Default::default(),
1463            entrypoint: None,
1464        };
1465
1466        let bytes = manifest.to_bytes();
1467
1468        let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1469            "expected malformed constant export leaf name rejection during deserialization",
1470        );
1471        let message = alloc::format!("{err}");
1472        assert_matches!(
1473            message,
1474            msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1475        );
1476    }
1477
1478    #[test]
1479    fn package_manifest_deserialization_rejects_malformed_quoted_type_leaf() {
1480        let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1481        let exports = BTreeMap::from_iter([(
1482            bad.clone(),
1483            PackageExport::Type(crate::TypeExport {
1484                path: bad,
1485                ty: miden_assembly_syntax::ast::types::Type::Felt,
1486            }),
1487        )]);
1488
1489        let manifest = PackageManifest {
1490            exports,
1491            modules: Default::default(),
1492            dependencies: Default::default(),
1493            entrypoint: None,
1494        };
1495
1496        let bytes = manifest.to_bytes();
1497
1498        let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1499            "expected malformed type export leaf name rejection during deserialization",
1500        );
1501        let message = alloc::format!("{err}");
1502        assert_matches!(
1503            message,
1504            msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1505        );
1506    }
1507
1508    #[test]
1509    fn regression_package_deserialisation_rejects_spoofed_mast_node_digests() {
1510        // Build mast for:
1511        //
1512        // pub proc p
1513        //     push.1
1514        // end
1515        let (forest, node_id) =
1516            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1517        let digest = forest[node_id].digest();
1518
1519        let path = absolute_path("lib::p");
1520        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1521            Arc::clone(&path),
1522            Some(node_id),
1523            digest,
1524            None,
1525        ))];
1526
1527        let package = Package {
1528            name: PackageId::from("lib"),
1529            version: crate::Version::new(0, 0, 0),
1530            digest,
1531            description: None,
1532            kind: TargetType::Library,
1533            mast: Arc::new(forest),
1534            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1535            sections: Default::default(),
1536            debug_sections_trusted: true,
1537        };
1538
1539        let (bytes, _) =
1540            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-library-digest");
1541        let err = Package::read_from_bytes(&bytes)
1542            .expect_err("expected package deserialization to reject inconsistent node digests");
1543        assert!(
1544            err.to_string().contains("invalid untrusted MAST forest"),
1545            "expected untrusted-MAST validation failure, got: {err}"
1546        );
1547        assert!(
1548            err.to_string().contains("hash mismatch for node"),
1549            "expected digest mismatch failure, got: {err}"
1550        );
1551    }
1552
1553    #[test]
1554    fn unchecked_package_deserialisation_rejects_spoofed_mast_node_digests() {
1555        // Build mast for:
1556        //
1557        // pub proc p
1558        //     push.1
1559        // end
1560        let (forest, node_id) =
1561            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1562        let digest = forest[node_id].digest();
1563
1564        let path = absolute_path("lib::p");
1565        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1566            Arc::clone(&path),
1567            Some(node_id),
1568            digest,
1569            None,
1570        ))];
1571
1572        let package = Package {
1573            name: PackageId::from("lib"),
1574            version: crate::Version::new(0, 0, 0),
1575            digest,
1576            description: None,
1577            kind: TargetType::Library,
1578            mast: Arc::new(forest),
1579            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1580            sections: Default::default(),
1581            debug_sections_trusted: true,
1582        };
1583
1584        let (bytes, _spoofed_digest) =
1585            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-library-digest");
1586        let err = Package::read_from_bytes_unchecked(&bytes)
1587            .expect_err("expected package deserialization to reject inconsistent node digests");
1588        assert!(
1589            err.to_string()
1590                .contains("declared node id and digest do not correspond to a procedure root"),
1591            "expected package manifest validation failure, got: {err}"
1592        );
1593    }
1594
1595    #[test]
1596    fn regression_kernel_package_deserialisation_rejects_spoofed_mast_node_digests() {
1597        // Build mast for:
1598        //
1599        // pub proc k1
1600        //     push.1
1601        // end
1602        let (forest, node_id) =
1603            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1604        let digest = forest[node_id].digest();
1605
1606        let path = absolute_path("$kernel::k1");
1607        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1608            Arc::clone(&path),
1609            Some(node_id),
1610            digest,
1611            None,
1612        ))];
1613
1614        let package = Package {
1615            name: PackageId::from("kernel"),
1616            version: crate::Version::new(0, 0, 0),
1617            digest,
1618            description: None,
1619            kind: TargetType::Kernel,
1620            mast: Arc::new(forest),
1621            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1622            sections: Default::default(),
1623            debug_sections_trusted: true,
1624        };
1625
1626        let (bytes, _) =
1627            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1628        let err = Package::read_from_bytes(&bytes).expect_err(
1629            "expected kernel package deserialization to reject inconsistent node digests",
1630        );
1631        assert!(
1632            err.to_string().contains("invalid untrusted MAST forest"),
1633            "expected untrusted-MAST validation failure, got: {err}"
1634        );
1635        assert!(
1636            err.to_string().contains("hash mismatch for node"),
1637            "expected digest mismatch failure, got: {err}"
1638        );
1639    }
1640
1641    #[cfg(feature = "std")]
1642    #[test]
1643    fn package_deserialize_from_file_rejects_spoofed_kernel_mast_node_digests() {
1644        // Build mast for:
1645        //
1646        // pub proc k1
1647        //     push.1
1648        // end
1649        let (forest, node_id) =
1650            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1651        let digest = forest[node_id].digest();
1652
1653        let path = absolute_path("$kernel::k1");
1654        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1655            Arc::clone(&path),
1656            Some(node_id),
1657            digest,
1658            None,
1659        ))];
1660
1661        let package = Package {
1662            name: PackageId::from("kernel"),
1663            version: crate::Version::new(0, 0, 0),
1664            digest,
1665            description: None,
1666            kind: TargetType::Kernel,
1667            mast: Arc::new(forest),
1668            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1669            sections: Default::default(),
1670            debug_sections_trusted: true,
1671        };
1672
1673        let (bytes, _) =
1674            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1675        let file_path = std::env::temp_dir().join(format!(
1676            "miden-package-deserialize-{}-{}.masp",
1677            std::process::id(),
1678            "spoofed-kernel-digest"
1679        ));
1680        fs::write(&file_path, bytes).expect("failed to write tampered package file");
1681
1682        let err = Package::deserialize_from_file(&file_path)
1683            .expect_err("expected file deserialization to reject inconsistent node digests");
1684        fs::remove_file(&file_path).unwrap();
1685
1686        assert!(
1687            err.to_string().contains("invalid untrusted MAST forest"),
1688            "expected untrusted-MAST validation failure, got: {err}"
1689        );
1690        assert!(
1691            err.to_string().contains("hash mismatch for node"),
1692            "expected digest mismatch failure, got: {err}"
1693        );
1694    }
1695
1696    #[test]
1697    fn unchecked_kernel_package_deserialisation_accepts_spoofed_mast_node_digests() {
1698        // Build mast for:
1699        //
1700        // pub proc k1
1701        //     push.1
1702        // end
1703        let (forest, node_id) =
1704            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1705        let digest = forest[node_id].digest();
1706
1707        let path = absolute_path("$kernel::k1");
1708        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1709            Arc::clone(&path),
1710            Some(node_id),
1711            digest,
1712            None,
1713        ))];
1714
1715        let package = Package {
1716            name: PackageId::from("kernel"),
1717            version: crate::Version::new(0, 0, 0),
1718            digest,
1719            description: None,
1720            kind: TargetType::Kernel,
1721            mast: Arc::new(forest),
1722            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1723            sections: Default::default(),
1724            debug_sections_trusted: true,
1725        };
1726
1727        let (bytes, _spoofed_digest) =
1728            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1729        let err = Package::read_from_bytes_unchecked(&bytes).expect_err(
1730            "expected unchecked kernel deserialization to reject inconsistent node digests",
1731        );
1732        assert!(
1733            err.to_string()
1734                .contains("declared node id and digest do not correspond to a procedure root"),
1735            "expected package manifest validation failure, got: {err}"
1736        );
1737    }
1738
1739    fn read_usize_vint64(bytes: &[u8], offset: &mut usize) -> usize {
1740        // This test patches raw bytes in place, so it needs byte offsets that
1741        // ByteReader::read_usize does not expose.
1742        let first_byte = bytes.get(*offset).copied().expect("out-of-bounds vint64 peek");
1743        let length = first_byte.trailing_zeros() as usize + 1;
1744
1745        if length == 9 {
1746            *offset += 1;
1747            let end = (*offset).checked_add(8).expect("offset overflow while reading vint64");
1748            let chunk: [u8; 8] = bytes[*offset..end].try_into().expect("out-of-bounds vint64");
1749            *offset = end;
1750            let value = u64::from_le_bytes(chunk);
1751            usize::try_from(value).expect("encoded usize does not fit host usize")
1752        } else {
1753            let end = (*offset).checked_add(length).expect("offset overflow while reading vint64");
1754            let mut encoded = [0u8; 8];
1755            encoded[..length].copy_from_slice(&bytes[*offset..end]);
1756            *offset = end;
1757            let value = u64::from_le_bytes(encoded) >> length;
1758            usize::try_from(value).expect("encoded usize does not fit host usize")
1759        }
1760    }
1761
1762    fn locate_first_node_hash(bytes: &[u8]) -> (usize, usize) {
1763        // Header: magic[4] + flags[1] + version[3]
1764        let mut offset = 0usize;
1765        offset += 4;
1766        offset += 1;
1767        offset += 3;
1768
1769        let internal_node_count = read_usize_vint64(bytes, &mut offset);
1770        let external_node_count = read_usize_vint64(bytes, &mut offset);
1771        let node_count = internal_node_count
1772            .checked_add(external_node_count)
1773            .expect("node count overflow");
1774
1775        // Roots: len (usize) + elements (u32 LE)
1776        let roots_len = read_usize_vint64(bytes, &mut offset);
1777        offset += roots_len * 4;
1778
1779        // Basic block data: len (usize) + bytes
1780        let bb_len = read_usize_vint64(bytes, &mut offset);
1781        offset += bb_len;
1782
1783        offset += node_count * 8;
1784        offset += external_node_count * 32;
1785
1786        (offset, internal_node_count)
1787    }
1788
1789    fn build_package_bytes_with_spoofed_first_node_digest(
1790        lib: &Package,
1791        spoof_seed: &str,
1792    ) -> (Vec<u8>, Word) {
1793        use miden_core::serde::Serializable;
1794
1795        // Serialize the MastForest normally so the byte layout is stable.
1796        let forest = lib.mast_forest().as_ref();
1797        let original_digest = forest[MastNodeId::new_unchecked(0)].digest();
1798        let mut output_bytes = Vec::new();
1799        lib.write_header_into(&mut output_bytes);
1800        let forest_offset = output_bytes.len();
1801        forest.write_into(&mut output_bytes);
1802
1803        let (node_hashes_start, node_count) =
1804            locate_first_node_hash(&output_bytes[forest_offset..]);
1805        assert!(node_count > 0, "expected at least one node info entry");
1806
1807        // Patch node 0 digest in-place.
1808        let spoofed_digest = miden_core::utils::hash_string_to_word(spoof_seed);
1809        assert_ne!(spoofed_digest, original_digest, "spoofed digest must differ");
1810
1811        let mut spoofed_digest_bytes = Vec::new();
1812        spoofed_digest.write_into(&mut spoofed_digest_bytes);
1813        assert_eq!(spoofed_digest_bytes.len(), 32, "Word must serialize to 32 bytes");
1814
1815        let node0_digest_offset = forest_offset + node_hashes_start;
1816        output_bytes[node0_digest_offset..node0_digest_offset + 32]
1817            .copy_from_slice(&spoofed_digest_bytes);
1818
1819        lib.write_trailer_into(&mut output_bytes);
1820
1821        (output_bytes, spoofed_digest)
1822    }
1823}