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