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, DebugSourceNode, DebugSourceNodeId, PackageDebugInfoBuilder,
859        },
860    };
861
862    fn build_single_node_forest(
863        operations: Vec<Operation>,
864        make_root: bool,
865    ) -> (MastForest, MastNodeId) {
866        let mut builder = DenseMastForestBuilder::new();
867        let node_id = builder
868            .push_node(BasicBlockNodeBuilder::new(operations))
869            .expect("failed to build basic block");
870        if make_root {
871            builder.mark_root(node_id);
872        }
873        let (forest, remapping) = builder.finish_with_id_map().expect("forest should be valid");
874        let node_id = remapping.get(node_id).expect("node should be retained");
875        (forest, node_id)
876    }
877
878    fn build_forest() -> (MastForest, MastNodeId) {
879        build_single_node_forest(vec![Operation::Add], true)
880    }
881
882    fn absolute_path(name: &str) -> Arc<AstPath> {
883        let path = PathBuf::new(name).expect("invalid path");
884        let path = path.as_path().to_absolute().unwrap().into_owned();
885        Arc::from(path.into_boxed_path())
886    }
887
888    fn build_package_exports() -> (Arc<MastForest>, Vec<PackageExport>) {
889        let (forest, node_id) = build_forest();
890        let path = absolute_path("test::proc");
891        let export =
892            ProcedureExport::new(Arc::clone(&path), Some(node_id), forest[node_id].digest(), None);
893
894        (Arc::new(forest), vec![PackageExport::Procedure(export)])
895    }
896
897    fn build_package() -> Package {
898        let (mast, exports) = build_package_exports();
899
900        Package::create(
901            PackageId::from("test_pkg"),
902            crate::Version::new(0, 0, 0),
903            TargetType::Library,
904            mast,
905            exports,
906            None,
907        )
908        .expect("test package should be valid")
909    }
910
911    fn build_package_with_debug_info() -> Package {
912        let mut nodes = IndexVec::<MastNodeId, MastNode>::new();
913        let node = BasicBlockNodeBuilder::new(vec![Operation::Add])
914            .build()
915            .expect("failed to build basic block");
916        let digest = node.digest();
917        let node_id = nodes.push(node.into()).expect("failed to add basic block");
918        let source_node = DebugSourceNodeId::from(0);
919
920        let mast = Arc::new(
921            MastForest::from_raw_parts(nodes, vec![node_id], AdviceMap::default())
922                .expect("forest should be valid"),
923        );
924        let path = absolute_path("test::proc");
925        let exports = vec![PackageExport::Procedure(
926            ProcedureExport::new(path, Some(node_id), digest, None)
927                .with_source_node(Some(source_node)),
928        )];
929        let mut package = Package::create(
930            PackageId::from("test_pkg"),
931            crate::Version::new(0, 0, 0),
932            TargetType::Library,
933            mast,
934            exports,
935            None,
936        )
937        .expect("test package should be valid");
938        let mut debug_info = PackageDebugInfoBuilder::default();
939        let context_name_idx = debug_info.add_string("trusted");
940        let op_name_idx = debug_info.add_string("add");
941        let added_source_node = debug_info
942            .add_node(DebugSourceNode {
943                exec_node: node_id,
944                children: Vec::new(),
945                op_start: 0,
946                op_end: 1,
947                asm_ops: vec![DebugSourceAsmOp::new(0, None, context_name_idx, op_name_idx, 1)],
948                debug_vars: Vec::new(),
949                inline_calls: Vec::new(),
950            })
951            .unwrap();
952        assert_eq!(added_source_node, source_node);
953        debug_info.add_root(source_node);
954        package
955            .sections
956            .push(Section::new(SectionId::DEBUG_INFO, debug_info.build().to_bytes()));
957        package
958    }
959
960    fn build_dependency() -> Dependency {
961        Dependency {
962            name: PackageId::from("dep"),
963            kind: TargetType::Library,
964            version: crate::Version::new(1, 0, 0),
965            digest: Default::default(),
966        }
967    }
968
969    fn package_bytes_with_sections_count(count: usize) -> Vec<u8> {
970        let package = build_package();
971        let mut bytes = Vec::new();
972
973        bytes.write_bytes(MAGIC_PACKAGE);
974        bytes.write_bytes(&VERSION);
975        package.name.write_into(&mut bytes);
976        package.version.to_string().write_into(&mut bytes);
977        package.description.write_into(&mut bytes);
978        bytes.write_u8(package.kind.into());
979        package.mast.write_into(&mut bytes);
980        package.manifest.write_into(&mut bytes);
981        bytes.write_usize(count);
982
983        bytes
984    }
985
986    #[test]
987    fn package_serialization_roundtrip() {
988        use proptest::{
989            prelude::*,
990            test_runner::{Config, TestRunner},
991        };
992
993        // since the test is quite expensive, 128 cases should be enough to cover all edge cases
994        // (default is 256)
995        let cases = 128;
996        TestRunner::new(Config::with_cases(cases))
997            .run(&any::<Package>(), move |package| {
998                let bytes = package.to_bytes();
999                let deserialized = Package::read_from_bytes(&bytes).unwrap();
1000                let mut expected = package;
1001                expected.sections.retain(|section| !section.id.is_debug());
1002                prop_assert_eq!(expected.to_bytes(), deserialized.to_bytes());
1003                Ok(())
1004            })
1005            .unwrap_or_else(|err| {
1006                panic!("{err}");
1007            });
1008    }
1009
1010    #[test]
1011    fn executable_package_entrypoint_roundtrips() {
1012        let (forest, node_id) = build_forest();
1013        let entrypoint =
1014            Arc::from(AstPath::exec_path().join(ProcedureName::MAIN_PROC_NAME).into_boxed_path());
1015        let export = ProcedureExport::new(
1016            Arc::clone(&entrypoint),
1017            Some(node_id),
1018            forest[node_id].digest(),
1019            None,
1020        );
1021        let package = Package::create(
1022            PackageId::from("test_pkg"),
1023            crate::Version::new(0, 0, 0),
1024            TargetType::Executable,
1025            Arc::new(forest),
1026            [PackageExport::Procedure(export)],
1027            None,
1028        )
1029        .expect("executable package should be valid");
1030
1031        let deserialized = Package::read_from_bytes(&package.to_bytes())
1032            .expect("executable package should deserialize without duplicate entrypoint errors");
1033
1034        assert_eq!(deserialized.manifest.entrypoint(), Some(entrypoint));
1035    }
1036
1037    #[test]
1038    fn package_checked_deserialization_discards_untrusted_debug_sections() {
1039        let package = build_package_with_debug_info();
1040        let bytes = package.to_bytes();
1041
1042        let deserialized = Package::read_from_bytes(&bytes).unwrap();
1043
1044        assert!(
1045            !deserialized.sections.iter().any(|section| section.id.is_debug()),
1046            "untrusted package reads should discard debug sections"
1047        );
1048        assert!(deserialized.debug_info().unwrap().is_none());
1049        let debug_info_id = SectionId::DEBUG_INFO.as_str().as_bytes();
1050        assert!(
1051            !deserialized
1052                .to_bytes()
1053                .windows(debug_info_id.len())
1054                .any(|window| window == debug_info_id),
1055            "discarded debug sections should not be reserialized"
1056        );
1057    }
1058
1059    #[test]
1060    fn package_trusted_deserialization_preserves_trusted_debug_sections() {
1061        let package = build_package_with_debug_info();
1062        let bytes = package.to_bytes();
1063
1064        let deserialized = Package::read_from_bytes_trusted(&bytes).unwrap();
1065
1066        assert!(deserialized.sections.iter().any(|section| section.id == SectionId::DEBUG_INFO));
1067        assert!(deserialized.debug_info().unwrap().is_some());
1068    }
1069
1070    #[test]
1071    fn package_unchecked_deserialization_preserves_trusted_debug_sections() {
1072        let package = build_package_with_debug_info();
1073        let bytes = package.to_bytes();
1074
1075        let deserialized = Package::read_from_bytes_unchecked(&bytes).unwrap();
1076
1077        assert!(deserialized.sections.iter().any(|section| section.id == SectionId::DEBUG_INFO));
1078        assert!(deserialized.debug_info().unwrap().is_some());
1079    }
1080
1081    #[cfg(feature = "std")]
1082    #[test]
1083    fn package_deserialize_from_file_discards_untrusted_debug_sections() {
1084        let package = build_package_with_debug_info();
1085        let path = std::env::temp_dir().join(format!(
1086            "miden-package-deserialize-{}-{}.masp",
1087            std::process::id(),
1088            "debug-sections"
1089        ));
1090        package.write_to_file(&path).unwrap();
1091
1092        let deserialized = Package::deserialize_from_file(&path).unwrap();
1093        fs::remove_file(&path).unwrap();
1094
1095        assert!(
1096            !deserialized.sections.iter().any(|section| section.id.is_debug()),
1097            "untrusted package file reads should discard debug sections"
1098        );
1099        assert!(deserialized.debug_info().unwrap().is_none());
1100    }
1101
1102    #[cfg(feature = "std")]
1103    #[test]
1104    fn package_deserialize_from_file_trusted_preserves_trusted_debug_sections() {
1105        let package = build_package_with_debug_info();
1106        let path = std::env::temp_dir().join(format!(
1107            "miden-package-deserialize-{}-{}.masp",
1108            std::process::id(),
1109            "trusted-debug-sections"
1110        ));
1111        package.write_to_file(&path).unwrap();
1112
1113        let deserialized = Package::deserialize_from_file_trusted(&path).unwrap();
1114        fs::remove_file(&path).unwrap();
1115
1116        assert!(deserialized.sections.iter().any(|section| section.id == SectionId::DEBUG_INFO));
1117        assert!(deserialized.debug_info().unwrap().is_some());
1118    }
1119
1120    #[test]
1121    fn package_content_digest_changes_when_identity_fields_change() {
1122        let package = build_package();
1123        let digest = package.content_digest();
1124
1125        let renamed = Package {
1126            name: PackageId::from("renamed_pkg"),
1127            ..package.clone()
1128        };
1129        assert_ne!(digest, renamed.content_digest());
1130
1131        let versioned = Package {
1132            version: crate::Version::new(1, 2, 3),
1133            ..package.clone()
1134        };
1135        assert_ne!(digest, versioned.content_digest());
1136
1137        let executable = Package { kind: TargetType::Executable, ..package };
1138        assert_ne!(digest, executable.content_digest());
1139    }
1140
1141    #[test]
1142    fn package_content_digest_changes_when_manifest_changes() {
1143        let package = build_package();
1144        let digest = package.content_digest();
1145
1146        let mut with_dependency = package;
1147        with_dependency
1148            .manifest
1149            .add_dependency(Dependency {
1150                name: PackageId::from("dep_pkg"),
1151                kind: TargetType::Library,
1152                version: crate::Version::new(1, 0, 0),
1153                digest: Word::from([1_u32, 2, 3, 4]),
1154            })
1155            .expect("test dependency should be unique");
1156        assert_ne!(digest, with_dependency.content_digest());
1157    }
1158
1159    #[test]
1160    fn package_content_digest_changes_when_account_component_metadata_changes() {
1161        let package = build_package();
1162        let digest = package.content_digest();
1163
1164        let with_metadata = Package {
1165            sections: vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![1, 2, 3, 4])],
1166            ..package.clone()
1167        };
1168        assert_ne!(digest, with_metadata.content_digest());
1169
1170        let with_different_metadata = Package {
1171            sections: vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![4, 3, 2, 1])],
1172            ..package
1173        };
1174        assert_ne!(with_metadata.content_digest(), with_different_metadata.content_digest());
1175    }
1176
1177    #[test]
1178    fn package_content_digest_ignores_description_and_opaque_custom_sections_for_now() {
1179        let package = build_package();
1180        let digest = package.content_digest();
1181
1182        let described = Package {
1183            description: Some(String::from("human-facing package description")),
1184            ..package.clone()
1185        };
1186        assert_eq!(digest, described.content_digest());
1187
1188        let with_section = Package {
1189            sections: vec![Section::new(
1190                SectionId::custom("opaque").expect("valid custom section id"),
1191                vec![1, 2, 3, 4],
1192            )],
1193            ..package
1194        };
1195        assert_eq!(digest, with_section.content_digest());
1196    }
1197
1198    #[test]
1199    fn package_manifest_rejects_over_budget_dependencies() {
1200        let mut bytes = Vec::new();
1201        bytes.write_usize(0);
1202        bytes.write_usize(0);
1203        bytes.write_usize(2);
1204
1205        let mut reader = BudgetedReader::new(SliceReader::new(&bytes), 2);
1206        let err = PackageManifest::read_from(&mut reader).unwrap_err();
1207        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1208    }
1209
1210    #[test]
1211    fn package_rejects_over_budget_sections() {
1212        let bytes = package_bytes_with_sections_count(2);
1213        let mut reader = BudgetedReader::new(SliceReader::new(&bytes), bytes.len());
1214        let err = Package::read_from(&mut reader).unwrap_err();
1215        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1216    }
1217
1218    #[test]
1219    fn package_read_from_bytes_rejects_fuzzed_oom_payload() {
1220        // This fuzz payload encodes counts large enough to cause excessive allocation or read work.
1221        // If this starts succeeding, package byte-slice deserialization is no longer budgeted.
1222        let payload = [
1223            0x4d, 0x41, 0x53, 0x50, 0x00, 0x04, 0x00, 0x00, 0x11, 0x74, 0x65, 0x73, 0x74, 0x5f,
1224            0x70, 0x6b, 0x67, 0x0b, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x00, 0x00, 0x4d, 0x41, 0x53,
1225            0x54, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x17, 0x03, 0x22,
1226            0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1227            0x00, 0x00, 0x30, 0x2f, 0x08, 0x0a, 0x21, 0xa9, 0xb6, 0xf6, 0x1a, 0x52, 0x30, 0xc5,
1228            0x64, 0xc7, 0xdb, 0x4d, 0x83, 0x0b, 0x32, 0x58, 0x89, 0x88, 0xb2, 0x78, 0x69, 0xbb,
1229            0x23, 0xa6, 0x18, 0x9c, 0xc9, 0x35, 0x2d, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1230            0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1231            0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x00, 0x0c, 0x00, 0x3a, 0x3a, 0x74, 0x65, 0x73,
1232            0x74, 0x3a, 0x3a, 0x70, 0x72, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
1233            0x0f, 0x03, 0x0f, 0x01, 0x00, 0x00, 0x17, 0x03, 0x22, 0x01, 0x00, 0x00, 0x00, 0x01,
1234            0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0xc9, 0x35, 0x2d, 0x01, 0x00, 0x03, 0x0f, 0x03,
1235            0x0f, 0x01, 0x01, 0x01,
1236        ];
1237
1238        let result = Package::read_from_bytes(&payload);
1239        assert!(result.is_err());
1240
1241        // Wrapped fuzz inputs must use the generic budgeted entry point; otherwise the outer
1242        // collection length can drive unbounded work before the inner package fails.
1243        let mut vec_payload = vec![0];
1244        vec_payload.extend_from_slice(&1000u64.to_le_bytes());
1245        let budget = vec_payload.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
1246        let result = Vec::<Package>::read_from_bytes_with_budget(&vec_payload, budget);
1247        assert!(result.is_err());
1248
1249        let mut option_payload = vec![1];
1250        option_payload.extend_from_slice(&payload);
1251        let budget = option_payload.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
1252        let result = Option::<Package>::read_from_bytes_with_budget(&option_payload, budget);
1253        assert!(result.is_err());
1254    }
1255
1256    /// Verifies that deserializing a library rejects procedure exports whose `MastNodeId` is not a
1257    /// procedure root in the underlying MAST forest (issue #2831).
1258    #[test]
1259    fn package_rejects_non_root_export() {
1260        let (forest, node_id) = build_single_node_forest(vec![Operation::Add], false);
1261        let digest = forest[node_id].digest();
1262
1263        let path = absolute_path("test::proc");
1264        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1265            Arc::clone(&path),
1266            Some(node_id),
1267            digest,
1268            None,
1269        ))];
1270
1271        let package = Package {
1272            name: PackageId::from("test_pkg"),
1273            version: crate::Version::new(0, 0, 0),
1274            digest,
1275            description: None,
1276            kind: TargetType::Library,
1277            mast: Arc::new(forest),
1278            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1279            sections: Default::default(),
1280            debug_sections_trusted: true,
1281        };
1282
1283        // Manually serialize the tampered package: forest + one export referencing a non-root node.
1284        let mut tampered_bytes = Vec::new();
1285        package.write_into(&mut tampered_bytes);
1286
1287        // Deserializing should fail because the export references a non-root node.
1288        let result = Package::read_from_bytes(&tampered_bytes);
1289        assert!(
1290            result.is_err(),
1291            "deserialization should reject exports referencing non-root nodes"
1292        );
1293        let err_msg = result.unwrap_err().to_string();
1294        assert!(
1295            err_msg.contains("node id and digest do not correspond to a procedure root"),
1296            "error should mention missing procedure root, got: {err_msg}"
1297        );
1298    }
1299
1300    #[test]
1301    fn package_manifest_new_rejects_duplicate_export_paths() {
1302        let path = absolute_path("test::proc");
1303        let exports = vec![
1304            PackageExport::Procedure(ProcedureExport::new(
1305                path.clone(),
1306                None,
1307                Word::default(),
1308                None,
1309            )),
1310            PackageExport::Procedure(ProcedureExport::new(
1311                path.clone(),
1312                None,
1313                Word::default(),
1314                None,
1315            )),
1316        ];
1317
1318        let err = PackageManifest::new(exports)
1319            .expect_err("duplicate export paths should be rejected by constructors");
1320        assert_matches!(err, ManifestValidationError::DuplicateExport(err_path) if err_path == path);
1321    }
1322
1323    #[test]
1324    fn package_manifest_roundtrips_module_surfaces() {
1325        let export = PackageExport::Procedure(ProcedureExport::new(
1326            absolute_path("test::api::foo"),
1327            None,
1328            Word::default(),
1329            None,
1330        ));
1331        let module = PackageModule::new(
1332            absolute_path("test"),
1333            [PackageSubmodule::new(Ident::new("api").unwrap())],
1334        );
1335        let child = PackageModule::new(absolute_path("test::api"), []);
1336
1337        let manifest = PackageManifest::new([export])
1338            .and_then(|manifest| manifest.with_modules([module, child]))
1339            .expect("manifest should be valid");
1340        let bytes = manifest.to_bytes();
1341        let decoded = PackageManifest::read_from_bytes(&bytes).expect("manifest should roundtrip");
1342
1343        let root = decoded
1344            .get_module(absolute_path("test").as_ref())
1345            .expect("root module surface should be present");
1346        assert_eq!(root.submodules().len(), 1);
1347        assert_eq!(root.submodules()[0].name.as_str(), "api");
1348        assert!(decoded.get_module(absolute_path("test::api").as_ref()).is_some());
1349    }
1350
1351    #[test]
1352    fn package_manifest_add_dependency_rejects_duplicate_dependencies() {
1353        let mut manifest = PackageManifest {
1354            exports: Default::default(),
1355            modules: Default::default(),
1356            dependencies: Default::default(),
1357            entrypoint: None,
1358        };
1359        let dependency = build_dependency();
1360
1361        manifest
1362            .add_dependency(dependency.clone())
1363            .expect("first dependency should be accepted");
1364        let err = manifest
1365            .add_dependency(dependency)
1366            .expect_err("duplicate dependencies should be rejected by helpers");
1367        assert_matches!(err, ManifestValidationError::DuplicateDependency(pkgid) if pkgid == "dep");
1368    }
1369
1370    #[test]
1371    fn package_manifest_rejects_duplicate_export_paths() {
1372        let path = absolute_path("test::proc");
1373        let export =
1374            PackageExport::Procedure(ProcedureExport::new(path, None, Word::default(), None));
1375
1376        let mut bytes = Vec::new();
1377        bytes.write_usize(2);
1378        export.write_into(&mut bytes);
1379        export.write_into(&mut bytes);
1380        bytes.write_usize(0);
1381        bytes.write_usize(0);
1382        bytes.write_bool(false);
1383
1384        let mut reader = SliceReader::new(&bytes);
1385        let err = PackageManifest::read_from(&mut reader)
1386            .expect_err("duplicate export paths should be rejected during deserialization");
1387        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1388    }
1389
1390    #[test]
1391    fn package_manifest_rejects_duplicate_dependencies() {
1392        let dependency = build_dependency();
1393
1394        let mut bytes = Vec::new();
1395        bytes.write_usize(0);
1396        bytes.write_usize(0);
1397        bytes.write_usize(2);
1398        dependency.write_into(&mut bytes);
1399        dependency.write_into(&mut bytes);
1400        bytes.write_bool(false);
1401
1402        let mut reader = SliceReader::new(&bytes);
1403        let err = PackageManifest::read_from(&mut reader)
1404            .expect_err("duplicate dependencies should be rejected during deserialization");
1405        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1406    }
1407
1408    #[test]
1409    fn package_manifest_deserialization_rejects_malformed_quoted_procedure_leaf() {
1410        let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1411        let exports = BTreeMap::from_iter([(
1412            bad.clone(),
1413            PackageExport::Procedure(ProcedureExport::new(bad, None, Default::default(), None)),
1414        )]);
1415
1416        let manifest = PackageManifest {
1417            exports,
1418            modules: Default::default(),
1419            dependencies: Default::default(),
1420            entrypoint: None,
1421        };
1422
1423        let bytes = manifest.to_bytes();
1424
1425        let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1426            "expected malformed procedure export leaf name rejection during deserialization",
1427        );
1428        let message = alloc::format!("{err}");
1429        assert_matches!(
1430            message,
1431            msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1432        );
1433    }
1434
1435    #[test]
1436    fn package_manifest_deserialization_rejects_malformed_quoted_constant_leaf() {
1437        let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1438        let exports = BTreeMap::from_iter([(
1439            bad.clone(),
1440            PackageExport::Constant(crate::ConstantExport {
1441                path: bad,
1442                value: miden_assembly_syntax::ast::ConstantValue::Int(
1443                    miden_debug_types::Span::unknown(1u32.into()),
1444                ),
1445            }),
1446        )]);
1447
1448        let manifest = PackageManifest {
1449            exports,
1450            modules: Default::default(),
1451            dependencies: Default::default(),
1452            entrypoint: None,
1453        };
1454
1455        let bytes = manifest.to_bytes();
1456
1457        let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1458            "expected malformed constant export leaf name rejection during deserialization",
1459        );
1460        let message = alloc::format!("{err}");
1461        assert_matches!(
1462            message,
1463            msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1464        );
1465    }
1466
1467    #[test]
1468    fn package_manifest_deserialization_rejects_malformed_quoted_type_leaf() {
1469        let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1470        let exports = BTreeMap::from_iter([(
1471            bad.clone(),
1472            PackageExport::Type(crate::TypeExport {
1473                path: bad,
1474                ty: miden_assembly_syntax::ast::types::Type::Felt,
1475            }),
1476        )]);
1477
1478        let manifest = PackageManifest {
1479            exports,
1480            modules: Default::default(),
1481            dependencies: Default::default(),
1482            entrypoint: None,
1483        };
1484
1485        let bytes = manifest.to_bytes();
1486
1487        let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1488            "expected malformed type export leaf name rejection during deserialization",
1489        );
1490        let message = alloc::format!("{err}");
1491        assert_matches!(
1492            message,
1493            msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1494        );
1495    }
1496
1497    #[test]
1498    fn regression_package_deserialisation_rejects_spoofed_mast_node_digests() {
1499        // Build mast for:
1500        //
1501        // pub proc p
1502        //     push.1
1503        // end
1504        let (forest, node_id) =
1505            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1506        let digest = forest[node_id].digest();
1507
1508        let path = absolute_path("lib::p");
1509        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1510            Arc::clone(&path),
1511            Some(node_id),
1512            digest,
1513            None,
1514        ))];
1515
1516        let package = Package {
1517            name: PackageId::from("lib"),
1518            version: crate::Version::new(0, 0, 0),
1519            digest,
1520            description: None,
1521            kind: TargetType::Library,
1522            mast: Arc::new(forest),
1523            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1524            sections: Default::default(),
1525            debug_sections_trusted: true,
1526        };
1527
1528        let (bytes, _) =
1529            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-library-digest");
1530        let err = Package::read_from_bytes(&bytes)
1531            .expect_err("expected package deserialization to reject inconsistent node digests");
1532        assert!(
1533            err.to_string().contains("invalid untrusted MAST forest"),
1534            "expected untrusted-MAST validation failure, got: {err}"
1535        );
1536        assert!(
1537            err.to_string().contains("hash mismatch for node"),
1538            "expected digest mismatch failure, got: {err}"
1539        );
1540    }
1541
1542    #[test]
1543    fn unchecked_package_deserialisation_rejects_spoofed_mast_node_digests() {
1544        // Build mast for:
1545        //
1546        // pub proc p
1547        //     push.1
1548        // end
1549        let (forest, node_id) =
1550            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1551        let digest = forest[node_id].digest();
1552
1553        let path = absolute_path("lib::p");
1554        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1555            Arc::clone(&path),
1556            Some(node_id),
1557            digest,
1558            None,
1559        ))];
1560
1561        let package = Package {
1562            name: PackageId::from("lib"),
1563            version: crate::Version::new(0, 0, 0),
1564            digest,
1565            description: None,
1566            kind: TargetType::Library,
1567            mast: Arc::new(forest),
1568            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1569            sections: Default::default(),
1570            debug_sections_trusted: true,
1571        };
1572
1573        let (bytes, _spoofed_digest) =
1574            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-library-digest");
1575        let err = Package::read_from_bytes_unchecked(&bytes)
1576            .expect_err("expected package deserialization to reject inconsistent node digests");
1577        assert!(
1578            err.to_string()
1579                .contains("declared node id and digest do not correspond to a procedure root"),
1580            "expected package manifest validation failure, got: {err}"
1581        );
1582    }
1583
1584    #[test]
1585    fn regression_kernel_package_deserialisation_rejects_spoofed_mast_node_digests() {
1586        // Build mast for:
1587        //
1588        // pub proc k1
1589        //     push.1
1590        // end
1591        let (forest, node_id) =
1592            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1593        let digest = forest[node_id].digest();
1594
1595        let path = absolute_path("$kernel::k1");
1596        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1597            Arc::clone(&path),
1598            Some(node_id),
1599            digest,
1600            None,
1601        ))];
1602
1603        let package = Package {
1604            name: PackageId::from("kernel"),
1605            version: crate::Version::new(0, 0, 0),
1606            digest,
1607            description: None,
1608            kind: TargetType::Kernel,
1609            mast: Arc::new(forest),
1610            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1611            sections: Default::default(),
1612            debug_sections_trusted: true,
1613        };
1614
1615        let (bytes, _) =
1616            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1617        let err = Package::read_from_bytes(&bytes).expect_err(
1618            "expected kernel package deserialization to reject inconsistent node digests",
1619        );
1620        assert!(
1621            err.to_string().contains("invalid untrusted MAST forest"),
1622            "expected untrusted-MAST validation failure, got: {err}"
1623        );
1624        assert!(
1625            err.to_string().contains("hash mismatch for node"),
1626            "expected digest mismatch failure, got: {err}"
1627        );
1628    }
1629
1630    #[cfg(feature = "std")]
1631    #[test]
1632    fn package_deserialize_from_file_rejects_spoofed_kernel_mast_node_digests() {
1633        // Build mast for:
1634        //
1635        // pub proc k1
1636        //     push.1
1637        // end
1638        let (forest, node_id) =
1639            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1640        let digest = forest[node_id].digest();
1641
1642        let path = absolute_path("$kernel::k1");
1643        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1644            Arc::clone(&path),
1645            Some(node_id),
1646            digest,
1647            None,
1648        ))];
1649
1650        let package = Package {
1651            name: PackageId::from("kernel"),
1652            version: crate::Version::new(0, 0, 0),
1653            digest,
1654            description: None,
1655            kind: TargetType::Kernel,
1656            mast: Arc::new(forest),
1657            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1658            sections: Default::default(),
1659            debug_sections_trusted: true,
1660        };
1661
1662        let (bytes, _) =
1663            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1664        let file_path = std::env::temp_dir().join(format!(
1665            "miden-package-deserialize-{}-{}.masp",
1666            std::process::id(),
1667            "spoofed-kernel-digest"
1668        ));
1669        fs::write(&file_path, bytes).expect("failed to write tampered package file");
1670
1671        let err = Package::deserialize_from_file(&file_path)
1672            .expect_err("expected file deserialization to reject inconsistent node digests");
1673        fs::remove_file(&file_path).unwrap();
1674
1675        assert!(
1676            err.to_string().contains("invalid untrusted MAST forest"),
1677            "expected untrusted-MAST validation failure, got: {err}"
1678        );
1679        assert!(
1680            err.to_string().contains("hash mismatch for node"),
1681            "expected digest mismatch failure, got: {err}"
1682        );
1683    }
1684
1685    #[test]
1686    fn unchecked_kernel_package_deserialisation_accepts_spoofed_mast_node_digests() {
1687        // Build mast for:
1688        //
1689        // pub proc k1
1690        //     push.1
1691        // end
1692        let (forest, node_id) =
1693            build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1694        let digest = forest[node_id].digest();
1695
1696        let path = absolute_path("$kernel::k1");
1697        let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1698            Arc::clone(&path),
1699            Some(node_id),
1700            digest,
1701            None,
1702        ))];
1703
1704        let package = Package {
1705            name: PackageId::from("kernel"),
1706            version: crate::Version::new(0, 0, 0),
1707            digest,
1708            description: None,
1709            kind: TargetType::Kernel,
1710            mast: Arc::new(forest),
1711            manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1712            sections: Default::default(),
1713            debug_sections_trusted: true,
1714        };
1715
1716        let (bytes, _spoofed_digest) =
1717            build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1718        let err = Package::read_from_bytes_unchecked(&bytes).expect_err(
1719            "expected unchecked kernel deserialization to reject inconsistent node digests",
1720        );
1721        assert!(
1722            err.to_string()
1723                .contains("declared node id and digest do not correspond to a procedure root"),
1724            "expected package manifest validation failure, got: {err}"
1725        );
1726    }
1727
1728    fn read_usize_vint64(bytes: &[u8], offset: &mut usize) -> usize {
1729        // This test patches raw bytes in place, so it needs byte offsets that
1730        // ByteReader::read_usize does not expose.
1731        let first_byte = bytes.get(*offset).copied().expect("out-of-bounds vint64 peek");
1732        let length = first_byte.trailing_zeros() as usize + 1;
1733
1734        if length == 9 {
1735            *offset += 1;
1736            let end = (*offset).checked_add(8).expect("offset overflow while reading vint64");
1737            let chunk: [u8; 8] = bytes[*offset..end].try_into().expect("out-of-bounds vint64");
1738            *offset = end;
1739            let value = u64::from_le_bytes(chunk);
1740            usize::try_from(value).expect("encoded usize does not fit host usize")
1741        } else {
1742            let end = (*offset).checked_add(length).expect("offset overflow while reading vint64");
1743            let mut encoded = [0u8; 8];
1744            encoded[..length].copy_from_slice(&bytes[*offset..end]);
1745            *offset = end;
1746            let value = u64::from_le_bytes(encoded) >> length;
1747            usize::try_from(value).expect("encoded usize does not fit host usize")
1748        }
1749    }
1750
1751    fn locate_first_node_hash(bytes: &[u8]) -> (usize, usize) {
1752        // Header: magic[4] + flags[1] + version[3]
1753        let mut offset = 0usize;
1754        offset += 4;
1755        offset += 1;
1756        offset += 3;
1757
1758        let internal_node_count = read_usize_vint64(bytes, &mut offset);
1759        let external_node_count = read_usize_vint64(bytes, &mut offset);
1760        let node_count = internal_node_count
1761            .checked_add(external_node_count)
1762            .expect("node count overflow");
1763
1764        // Roots: len (usize) + elements (u32 LE)
1765        let roots_len = read_usize_vint64(bytes, &mut offset);
1766        offset += roots_len * 4;
1767
1768        // Basic block data: len (usize) + bytes
1769        let bb_len = read_usize_vint64(bytes, &mut offset);
1770        offset += bb_len;
1771
1772        offset += node_count * 8;
1773        offset += external_node_count * 32;
1774
1775        (offset, internal_node_count)
1776    }
1777
1778    fn build_package_bytes_with_spoofed_first_node_digest(
1779        lib: &Package,
1780        spoof_seed: &str,
1781    ) -> (Vec<u8>, Word) {
1782        use miden_core::serde::Serializable;
1783
1784        // Serialize the MastForest normally so the byte layout is stable.
1785        let forest = lib.mast_forest().as_ref();
1786        let original_digest = forest[MastNodeId::new_unchecked(0)].digest();
1787        let mut output_bytes = Vec::new();
1788        lib.write_header_into(&mut output_bytes);
1789        let forest_offset = output_bytes.len();
1790        forest.write_into(&mut output_bytes);
1791
1792        let (node_hashes_start, node_count) =
1793            locate_first_node_hash(&output_bytes[forest_offset..]);
1794        assert!(node_count > 0, "expected at least one node info entry");
1795
1796        // Patch node 0 digest in-place.
1797        let spoofed_digest = miden_core::utils::hash_string_to_word(spoof_seed);
1798        assert_ne!(spoofed_digest, original_digest, "spoofed digest must differ");
1799
1800        let mut spoofed_digest_bytes = Vec::new();
1801        spoofed_digest.write_into(&mut spoofed_digest_bytes);
1802        assert_eq!(spoofed_digest_bytes.len(), 32, "Word must serialize to 32 bytes");
1803
1804        let node0_digest_offset = forest_offset + node_hashes_start;
1805        output_bytes[node0_digest_offset..node0_digest_offset + 32]
1806            .copy_from_slice(&spoofed_digest_bytes);
1807
1808        lib.write_trailer_into(&mut output_bytes);
1809
1810        (output_bytes, spoofed_digest)
1811    }
1812}