1use 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
72const MAGIC_PACKAGE: &[u8; 5] = b"MASP\0";
77
78const VERSION: [u8; 3] = [6, 0, 0];
82
83const PACKAGE_BYTE_READ_BUDGET_MULTIPLIER: usize = 64;
88
89impl Package {
93 #[doc(hidden)]
94 pub fn write_header_into<W: ByteWriter>(&self, target: &mut W) {
95 target.write_bytes(MAGIC_PACKAGE);
97 target.write_bytes(&VERSION);
98
99 self.name.write_into(target);
101
102 self.version.to_string().write_into(target);
104
105 self.description.write_into(target);
107
108 target.write_u8(self.kind.into());
110 }
111
112 #[doc(hidden)]
113 pub fn write_trailer_into<W: ByteWriter>(&self, target: &mut W) {
114 self.manifest.write_into(target);
116
117 target.write_usize(self.sections.len());
119 for section in self.sections.iter() {
120 section.write_into(target);
121 }
122 }
123
124 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 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 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 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 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 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 let name = PackageId::read_from(source)?;
235
236 let version = String::read_from(source)?
238 .parse::<crate::Version>()
239 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
240
241 let description = Option::<String>::read_from(source)?;
243
244 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 let manifest = PackageManifest::read_from_safe(source, &mast)?;
262
263 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 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#[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 target.write_usize(self.num_exports());
489 for export in self.exports() {
490 export.write_into(target);
491 }
492
493 target.write_usize(self.num_modules());
495 for module in self.modules() {
496 module.write_into(target);
497 }
498
499 target.write_usize(self.num_dependencies());
501 for dep in self.dependencies() {
502 dep.write_into(target);
503 }
504
505 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 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 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 let dependencies = Vec::<Dependency>::read_from(source)?;
545
546 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 let exports_len = source.read_usize()?;
571 let exports = source.read_many_iter(exports_len)?.collect::<Result<Vec<_>, _>>()?;
572
573 let modules_len = source.read_usize()?;
575 let modules = source.read_many_iter(modules_len)?.collect::<Result<Vec<_>, _>>()?;
576
577 let dependencies = Vec::<Dependency>::read_from(source)?;
579
580 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
601impl 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
635impl 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 if let Some(node) = node
733 && digest != mast[node].digest()
734 {
735 return Err(DeserializationError::InvalidValue(
736 ManifestValidationError::InvalidProcedureExport { path }.to_string(),
737 ));
738 }
739 let signature = if source.read_bool()? {
740 Some(FunctionType::read_from(source)?)
741 } else {
742 None
743 };
744 let attributes = AttributeSet::read_from(source)?;
745 Ok(Self {
746 path,
747 node,
748 source_node,
749 digest,
750 signature,
751 attributes,
752 })
753 }
754}
755
756impl Deserializable for ProcedureExport {
757 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
758 use miden_assembly_syntax::ast::types::FunctionType;
759 let path = PathBuf::read_from(source)?.into_boxed_path().into();
760 let node = if source.read_bool()? {
761 Some(MastNodeId::new_unchecked(source.read_u32()?))
762 } else {
763 None
764 };
765 let source_node = if source.read_bool()? {
766 Some(DebugSourceNodeId::read_from(source)?)
767 } else {
768 None
769 };
770 let digest = Word::read_from(source)?;
771 let signature = if source.read_bool()? {
772 Some(FunctionType::read_from(source)?)
773 } else {
774 None
775 };
776 let attributes = AttributeSet::read_from(source)?;
777 Ok(Self {
778 path,
779 node,
780 source_node,
781 digest,
782 signature,
783 attributes,
784 })
785 }
786}
787
788impl Serializable for ConstantExport {
789 fn write_into<W: ByteWriter>(&self, target: &mut W) {
790 self.path.write_into(target);
791 self.value.write_into(target);
792 }
793}
794
795impl Deserializable for ConstantExport {
796 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
797 let path = PathBuf::read_from(source)?.into_boxed_path().into();
798 let value = ast::ConstantValue::read_from(source)?;
799 Ok(Self { path, value })
800 }
801}
802
803impl Serializable for TypeExport {
804 fn write_into<W: ByteWriter>(&self, target: &mut W) {
805 self.path.write_into(target);
806 self.ty.write_into(target);
807 }
808}
809
810impl Deserializable for TypeExport {
811 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
812 use miden_assembly_syntax::ast::types::Type;
813 let path = PathBuf::read_from(source)?.into_boxed_path().into();
814 let ty = Type::read_from(source)?;
815 Ok(Self { path, ty })
816 }
817}
818
819#[cfg(test)]
820mod tests {
821 #[cfg(feature = "std")]
822 use alloc::format;
823 use alloc::{
824 string::{String, ToString},
825 sync::Arc,
826 vec,
827 vec::Vec,
828 };
829 use core::assert_matches;
830 use std::collections::BTreeMap;
831 #[cfg(feature = "std")]
832 use std::fs;
833
834 use miden_assembly_syntax::ast::{Ident, Path as AstPath, PathBuf, ProcedureName};
835 use miden_core::{
836 Felt, Word,
837 advice::AdviceMap,
838 mast::{
839 BasicBlockNodeBuilder, DenseMastForestBuilder, MastForest, MastNode, MastNodeExt,
840 MastNodeId,
841 },
842 operations::Operation,
843 serde::{
844 BudgetedReader, ByteWriter, Deserializable, DeserializationError, Serializable,
845 SliceReader,
846 },
847 utils::IndexVec,
848 };
849
850 use super::{
851 MAGIC_PACKAGE, PACKAGE_BYTE_READ_BUDGET_MULTIPLIER, Package, PackageManifest, Section,
852 VERSION,
853 };
854 use crate::{
855 Dependency, ManifestValidationError, PackageExport, PackageId, PackageModule,
856 PackageSubmodule, ProcedureExport, SectionId, TargetType,
857 debug_info::{
858 DebugSourceAsmOp, DebugSourceGraphSection, DebugSourceMapSection, DebugSourceNode,
859 DebugSourceNodeId,
860 },
861 };
862
863 fn build_single_node_forest(
864 operations: Vec<Operation>,
865 make_root: bool,
866 ) -> (MastForest, MastNodeId) {
867 let mut builder = DenseMastForestBuilder::new();
868 let node_id = builder
869 .push_node(BasicBlockNodeBuilder::new(operations))
870 .expect("failed to build basic block");
871 if make_root {
872 builder.mark_root(node_id);
873 }
874 let (forest, remapping) = builder.finish_with_id_map().expect("forest should be valid");
875 let node_id = remapping.get(node_id).expect("node should be retained");
876 (forest, node_id)
877 }
878
879 fn build_forest() -> (MastForest, MastNodeId) {
880 build_single_node_forest(vec![Operation::Add], true)
881 }
882
883 fn absolute_path(name: &str) -> Arc<AstPath> {
884 let path = PathBuf::new(name).expect("invalid path");
885 let path = path.as_path().to_absolute().unwrap().into_owned();
886 Arc::from(path.into_boxed_path())
887 }
888
889 fn build_package_exports() -> (Arc<MastForest>, Vec<PackageExport>) {
890 let (forest, node_id) = build_forest();
891 let path = absolute_path("test::proc");
892 let export =
893 ProcedureExport::new(Arc::clone(&path), Some(node_id), forest[node_id].digest(), None);
894
895 (Arc::new(forest), vec![PackageExport::Procedure(export)])
896 }
897
898 fn build_package() -> Package {
899 let (mast, exports) = build_package_exports();
900
901 Package::create(
902 PackageId::from("test_pkg"),
903 crate::Version::new(0, 0, 0),
904 TargetType::Library,
905 mast,
906 exports,
907 None,
908 )
909 .expect("test package should be valid")
910 }
911
912 fn build_package_with_debug_info() -> Package {
913 let mut nodes = IndexVec::<MastNodeId, MastNode>::new();
914 let node = BasicBlockNodeBuilder::new(vec![Operation::Add])
915 .build()
916 .expect("failed to build basic block");
917 let digest = node.digest();
918 let node_id = nodes.push(node.into()).expect("failed to add basic block");
919 let source_node = DebugSourceNodeId::from(0);
920
921 let mast = Arc::new(
922 MastForest::from_raw_parts(nodes, vec![node_id], AdviceMap::default())
923 .expect("forest should be valid"),
924 );
925 let path = absolute_path("test::proc");
926 let exports = vec![PackageExport::Procedure(
927 ProcedureExport::new(path, Some(node_id), digest, None)
928 .with_source_node(Some(source_node)),
929 )];
930 let mut package = Package::create(
931 PackageId::from("test_pkg"),
932 crate::Version::new(0, 0, 0),
933 TargetType::Library,
934 mast,
935 exports,
936 None,
937 )
938 .expect("test package should be valid");
939 let source_graph = DebugSourceGraphSection::from_parts(
940 vec![DebugSourceNode::new(node_id, Vec::new(), 0, 1)],
941 vec![source_node],
942 );
943 let source_map = DebugSourceMapSection::from_parts(
944 vec![DebugSourceAsmOp::new(source_node, 0, None, "trusted".into(), "add".into(), 1)],
945 Vec::new(),
946 );
947 package
948 .sections
949 .push(Section::new(SectionId::DEBUG_SOURCE_GRAPH, source_graph.to_bytes()));
950 package
951 .sections
952 .push(Section::new(SectionId::DEBUG_SOURCE_MAP, source_map.to_bytes()));
953 package
954 }
955
956 fn build_dependency() -> Dependency {
957 Dependency {
958 name: PackageId::from("dep"),
959 kind: TargetType::Library,
960 version: crate::Version::new(1, 0, 0),
961 digest: Default::default(),
962 }
963 }
964
965 fn package_bytes_with_sections_count(count: usize) -> Vec<u8> {
966 let package = build_package();
967 let mut bytes = Vec::new();
968
969 bytes.write_bytes(MAGIC_PACKAGE);
970 bytes.write_bytes(&VERSION);
971 package.name.write_into(&mut bytes);
972 package.version.to_string().write_into(&mut bytes);
973 package.description.write_into(&mut bytes);
974 bytes.write_u8(package.kind.into());
975 package.mast.write_into(&mut bytes);
976 package.manifest.write_into(&mut bytes);
977 bytes.write_usize(count);
978
979 bytes
980 }
981
982 #[test]
983 fn package_serialization_roundtrip() {
984 use proptest::{
985 prelude::*,
986 test_runner::{Config, TestRunner},
987 };
988
989 let cases = 128;
992 TestRunner::new(Config::with_cases(cases))
993 .run(&any::<Package>(), move |package| {
994 let bytes = package.to_bytes();
995 let deserialized = Package::read_from_bytes(&bytes).unwrap();
996 let mut expected = package;
997 expected.sections.retain(|section| !section.id.is_debug());
998 prop_assert_eq!(expected.to_bytes(), deserialized.to_bytes());
999 Ok(())
1000 })
1001 .unwrap_or_else(|err| {
1002 panic!("{err}");
1003 });
1004 }
1005
1006 #[test]
1007 fn executable_package_entrypoint_roundtrips() {
1008 let (forest, node_id) = build_forest();
1009 let entrypoint =
1010 Arc::from(AstPath::exec_path().join(ProcedureName::MAIN_PROC_NAME).into_boxed_path());
1011 let export = ProcedureExport::new(
1012 Arc::clone(&entrypoint),
1013 Some(node_id),
1014 forest[node_id].digest(),
1015 None,
1016 );
1017 let package = Package::create(
1018 PackageId::from("test_pkg"),
1019 crate::Version::new(0, 0, 0),
1020 TargetType::Executable,
1021 Arc::new(forest),
1022 [PackageExport::Procedure(export)],
1023 None,
1024 )
1025 .expect("executable package should be valid");
1026
1027 let deserialized = Package::read_from_bytes(&package.to_bytes())
1028 .expect("executable package should deserialize without duplicate entrypoint errors");
1029
1030 assert_eq!(deserialized.manifest.entrypoint(), Some(entrypoint));
1031 }
1032
1033 #[test]
1034 fn package_checked_deserialization_discards_untrusted_debug_sections() {
1035 let package = build_package_with_debug_info();
1036 let bytes = package.to_bytes();
1037
1038 let deserialized = Package::read_from_bytes(&bytes).unwrap();
1039
1040 assert!(
1041 !deserialized.sections.iter().any(|section| section.id.is_debug()),
1042 "untrusted package reads should discard debug sections"
1043 );
1044 assert!(deserialized.debug_info().unwrap().is_none());
1045 let debug_source_map_id = SectionId::DEBUG_SOURCE_MAP.as_str().as_bytes();
1046 assert!(
1047 !deserialized
1048 .to_bytes()
1049 .windows(debug_source_map_id.len())
1050 .any(|window| window == debug_source_map_id),
1051 "discarded debug sections should not be reserialized"
1052 );
1053 }
1054
1055 #[test]
1056 fn package_trusted_deserialization_preserves_trusted_debug_sections() {
1057 let package = build_package_with_debug_info();
1058 let bytes = package.to_bytes();
1059
1060 let deserialized = Package::read_from_bytes_trusted(&bytes).unwrap();
1061
1062 assert!(
1063 deserialized
1064 .sections
1065 .iter()
1066 .any(|section| section.id == SectionId::DEBUG_SOURCE_MAP)
1067 );
1068 assert!(deserialized.debug_info().unwrap().is_some());
1069 }
1070
1071 #[test]
1072 fn package_unchecked_deserialization_preserves_trusted_debug_sections() {
1073 let package = build_package_with_debug_info();
1074 let bytes = package.to_bytes();
1075
1076 let deserialized = Package::read_from_bytes_unchecked(&bytes).unwrap();
1077
1078 assert!(
1079 deserialized
1080 .sections
1081 .iter()
1082 .any(|section| section.id == SectionId::DEBUG_SOURCE_MAP)
1083 );
1084 assert!(deserialized.debug_info().unwrap().is_some());
1085 }
1086
1087 #[cfg(feature = "std")]
1088 #[test]
1089 fn package_deserialize_from_file_discards_untrusted_debug_sections() {
1090 let package = build_package_with_debug_info();
1091 let path = std::env::temp_dir().join(format!(
1092 "miden-package-deserialize-{}-{}.masp",
1093 std::process::id(),
1094 "debug-sections"
1095 ));
1096 package.write_to_file(&path).unwrap();
1097
1098 let deserialized = Package::deserialize_from_file(&path).unwrap();
1099 fs::remove_file(&path).unwrap();
1100
1101 assert!(
1102 !deserialized.sections.iter().any(|section| section.id.is_debug()),
1103 "untrusted package file reads should discard debug sections"
1104 );
1105 assert!(deserialized.debug_info().unwrap().is_none());
1106 }
1107
1108 #[cfg(feature = "std")]
1109 #[test]
1110 fn package_deserialize_from_file_trusted_preserves_trusted_debug_sections() {
1111 let package = build_package_with_debug_info();
1112 let path = std::env::temp_dir().join(format!(
1113 "miden-package-deserialize-{}-{}.masp",
1114 std::process::id(),
1115 "trusted-debug-sections"
1116 ));
1117 package.write_to_file(&path).unwrap();
1118
1119 let deserialized = Package::deserialize_from_file_trusted(&path).unwrap();
1120 fs::remove_file(&path).unwrap();
1121
1122 assert!(
1123 deserialized
1124 .sections
1125 .iter()
1126 .any(|section| section.id == SectionId::DEBUG_SOURCE_MAP)
1127 );
1128 assert!(deserialized.debug_info().unwrap().is_some());
1129 }
1130
1131 #[test]
1132 fn package_content_digest_changes_when_identity_fields_change() {
1133 let package = build_package();
1134 let digest = package.content_digest();
1135
1136 let renamed = Package {
1137 name: PackageId::from("renamed_pkg"),
1138 ..package.clone()
1139 };
1140 assert_ne!(digest, renamed.content_digest());
1141
1142 let versioned = Package {
1143 version: crate::Version::new(1, 2, 3),
1144 ..package.clone()
1145 };
1146 assert_ne!(digest, versioned.content_digest());
1147
1148 let executable = Package { kind: TargetType::Executable, ..package };
1149 assert_ne!(digest, executable.content_digest());
1150 }
1151
1152 #[test]
1153 fn package_content_digest_changes_when_manifest_changes() {
1154 let package = build_package();
1155 let digest = package.content_digest();
1156
1157 let mut with_dependency = package;
1158 with_dependency
1159 .manifest
1160 .add_dependency(Dependency {
1161 name: PackageId::from("dep_pkg"),
1162 kind: TargetType::Library,
1163 version: crate::Version::new(1, 0, 0),
1164 digest: Word::from([1_u32, 2, 3, 4]),
1165 })
1166 .expect("test dependency should be unique");
1167 assert_ne!(digest, with_dependency.content_digest());
1168 }
1169
1170 #[test]
1171 fn package_content_digest_changes_when_account_component_metadata_changes() {
1172 let package = build_package();
1173 let digest = package.content_digest();
1174
1175 let with_metadata = Package {
1176 sections: vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![1, 2, 3, 4])],
1177 ..package.clone()
1178 };
1179 assert_ne!(digest, with_metadata.content_digest());
1180
1181 let with_different_metadata = Package {
1182 sections: vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![4, 3, 2, 1])],
1183 ..package
1184 };
1185 assert_ne!(with_metadata.content_digest(), with_different_metadata.content_digest());
1186 }
1187
1188 #[test]
1189 fn package_content_digest_ignores_description_and_opaque_custom_sections_for_now() {
1190 let package = build_package();
1191 let digest = package.content_digest();
1192
1193 let described = Package {
1194 description: Some(String::from("human-facing package description")),
1195 ..package.clone()
1196 };
1197 assert_eq!(digest, described.content_digest());
1198
1199 let with_section = Package {
1200 sections: vec![Section::new(
1201 SectionId::custom("opaque").expect("valid custom section id"),
1202 vec![1, 2, 3, 4],
1203 )],
1204 ..package
1205 };
1206 assert_eq!(digest, with_section.content_digest());
1207 }
1208
1209 #[test]
1210 fn package_manifest_rejects_over_budget_dependencies() {
1211 let mut bytes = Vec::new();
1212 bytes.write_usize(0);
1213 bytes.write_usize(0);
1214 bytes.write_usize(2);
1215
1216 let mut reader = BudgetedReader::new(SliceReader::new(&bytes), 2);
1217 let err = PackageManifest::read_from(&mut reader).unwrap_err();
1218 assert!(matches!(err, DeserializationError::InvalidValue(_)));
1219 }
1220
1221 #[test]
1222 fn package_rejects_over_budget_sections() {
1223 let bytes = package_bytes_with_sections_count(2);
1224 let mut reader = BudgetedReader::new(SliceReader::new(&bytes), bytes.len());
1225 let err = Package::read_from(&mut reader).unwrap_err();
1226 assert!(matches!(err, DeserializationError::InvalidValue(_)));
1227 }
1228
1229 #[test]
1230 fn package_read_from_bytes_rejects_fuzzed_oom_payload() {
1231 let payload = [
1234 0x4d, 0x41, 0x53, 0x50, 0x00, 0x04, 0x00, 0x00, 0x11, 0x74, 0x65, 0x73, 0x74, 0x5f,
1235 0x70, 0x6b, 0x67, 0x0b, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x00, 0x00, 0x4d, 0x41, 0x53,
1236 0x54, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x17, 0x03, 0x22,
1237 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1238 0x00, 0x00, 0x30, 0x2f, 0x08, 0x0a, 0x21, 0xa9, 0xb6, 0xf6, 0x1a, 0x52, 0x30, 0xc5,
1239 0x64, 0xc7, 0xdb, 0x4d, 0x83, 0x0b, 0x32, 0x58, 0x89, 0x88, 0xb2, 0x78, 0x69, 0xbb,
1240 0x23, 0xa6, 0x18, 0x9c, 0xc9, 0x35, 0x2d, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1241 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1242 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x00, 0x0c, 0x00, 0x3a, 0x3a, 0x74, 0x65, 0x73,
1243 0x74, 0x3a, 0x3a, 0x70, 0x72, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
1244 0x0f, 0x03, 0x0f, 0x01, 0x00, 0x00, 0x17, 0x03, 0x22, 0x01, 0x00, 0x00, 0x00, 0x01,
1245 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0xc9, 0x35, 0x2d, 0x01, 0x00, 0x03, 0x0f, 0x03,
1246 0x0f, 0x01, 0x01, 0x01,
1247 ];
1248
1249 let result = Package::read_from_bytes(&payload);
1250 assert!(result.is_err());
1251
1252 let mut vec_payload = vec![0];
1255 vec_payload.extend_from_slice(&1000u64.to_le_bytes());
1256 let budget = vec_payload.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
1257 let result = Vec::<Package>::read_from_bytes_with_budget(&vec_payload, budget);
1258 assert!(result.is_err());
1259
1260 let mut option_payload = vec![1];
1261 option_payload.extend_from_slice(&payload);
1262 let budget = option_payload.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
1263 let result = Option::<Package>::read_from_bytes_with_budget(&option_payload, budget);
1264 assert!(result.is_err());
1265 }
1266
1267 #[test]
1270 fn package_rejects_non_root_export() {
1271 let (forest, node_id) = build_single_node_forest(vec![Operation::Add], false);
1272 let digest = forest[node_id].digest();
1273
1274 let path = absolute_path("test::proc");
1275 let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1276 Arc::clone(&path),
1277 Some(node_id),
1278 digest,
1279 None,
1280 ))];
1281
1282 let package = Package {
1283 name: PackageId::from("test_pkg"),
1284 version: crate::Version::new(0, 0, 0),
1285 digest,
1286 description: None,
1287 kind: TargetType::Library,
1288 mast: Arc::new(forest),
1289 manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1290 sections: Default::default(),
1291 debug_sections_trusted: true,
1292 };
1293
1294 let mut tampered_bytes = Vec::new();
1296 package.write_into(&mut tampered_bytes);
1297
1298 let result = Package::read_from_bytes(&tampered_bytes);
1300 assert!(
1301 result.is_err(),
1302 "deserialization should reject exports referencing non-root nodes"
1303 );
1304 let err_msg = result.unwrap_err().to_string();
1305 assert!(
1306 err_msg.contains("node id and digest do not correspond to a procedure root"),
1307 "error should mention missing procedure root, got: {err_msg}"
1308 );
1309 }
1310
1311 #[test]
1312 fn package_manifest_new_rejects_duplicate_export_paths() {
1313 let path = absolute_path("test::proc");
1314 let exports = vec![
1315 PackageExport::Procedure(ProcedureExport::new(
1316 path.clone(),
1317 None,
1318 Word::default(),
1319 None,
1320 )),
1321 PackageExport::Procedure(ProcedureExport::new(
1322 path.clone(),
1323 None,
1324 Word::default(),
1325 None,
1326 )),
1327 ];
1328
1329 let err = PackageManifest::new(exports)
1330 .expect_err("duplicate export paths should be rejected by constructors");
1331 assert_matches!(err, ManifestValidationError::DuplicateExport(err_path) if err_path == path);
1332 }
1333
1334 #[test]
1335 fn package_manifest_roundtrips_module_surfaces() {
1336 let export = PackageExport::Procedure(ProcedureExport::new(
1337 absolute_path("test::api::foo"),
1338 None,
1339 Word::default(),
1340 None,
1341 ));
1342 let module = PackageModule::new(
1343 absolute_path("test"),
1344 [PackageSubmodule::new(Ident::new("api").unwrap())],
1345 );
1346 let child = PackageModule::new(absolute_path("test::api"), []);
1347
1348 let manifest = PackageManifest::new([export])
1349 .and_then(|manifest| manifest.with_modules([module, child]))
1350 .expect("manifest should be valid");
1351 let bytes = manifest.to_bytes();
1352 let decoded = PackageManifest::read_from_bytes(&bytes).expect("manifest should roundtrip");
1353
1354 let root = decoded
1355 .get_module(absolute_path("test").as_ref())
1356 .expect("root module surface should be present");
1357 assert_eq!(root.submodules().len(), 1);
1358 assert_eq!(root.submodules()[0].name.as_str(), "api");
1359 assert!(decoded.get_module(absolute_path("test::api").as_ref()).is_some());
1360 }
1361
1362 #[test]
1363 fn package_manifest_add_dependency_rejects_duplicate_dependencies() {
1364 let mut manifest = PackageManifest {
1365 exports: Default::default(),
1366 modules: Default::default(),
1367 dependencies: Default::default(),
1368 entrypoint: None,
1369 };
1370 let dependency = build_dependency();
1371
1372 manifest
1373 .add_dependency(dependency.clone())
1374 .expect("first dependency should be accepted");
1375 let err = manifest
1376 .add_dependency(dependency)
1377 .expect_err("duplicate dependencies should be rejected by helpers");
1378 assert_matches!(err, ManifestValidationError::DuplicateDependency(pkgid) if pkgid == "dep");
1379 }
1380
1381 #[test]
1382 fn package_manifest_rejects_duplicate_export_paths() {
1383 let path = absolute_path("test::proc");
1384 let export =
1385 PackageExport::Procedure(ProcedureExport::new(path, None, Word::default(), None));
1386
1387 let mut bytes = Vec::new();
1388 bytes.write_usize(2);
1389 export.write_into(&mut bytes);
1390 export.write_into(&mut bytes);
1391 bytes.write_usize(0);
1392 bytes.write_usize(0);
1393 bytes.write_bool(false);
1394
1395 let mut reader = SliceReader::new(&bytes);
1396 let err = PackageManifest::read_from(&mut reader)
1397 .expect_err("duplicate export paths should be rejected during deserialization");
1398 assert!(matches!(err, DeserializationError::InvalidValue(_)));
1399 }
1400
1401 #[test]
1402 fn package_manifest_rejects_duplicate_dependencies() {
1403 let dependency = build_dependency();
1404
1405 let mut bytes = Vec::new();
1406 bytes.write_usize(0);
1407 bytes.write_usize(0);
1408 bytes.write_usize(2);
1409 dependency.write_into(&mut bytes);
1410 dependency.write_into(&mut bytes);
1411 bytes.write_bool(false);
1412
1413 let mut reader = SliceReader::new(&bytes);
1414 let err = PackageManifest::read_from(&mut reader)
1415 .expect_err("duplicate dependencies should be rejected during deserialization");
1416 assert!(matches!(err, DeserializationError::InvalidValue(_)));
1417 }
1418
1419 #[test]
1420 fn package_manifest_deserialization_rejects_malformed_quoted_procedure_leaf() {
1421 let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1422 let exports = BTreeMap::from_iter([(
1423 bad.clone(),
1424 PackageExport::Procedure(ProcedureExport::new(bad, None, Default::default(), None)),
1425 )]);
1426
1427 let manifest = PackageManifest {
1428 exports,
1429 modules: Default::default(),
1430 dependencies: Default::default(),
1431 entrypoint: None,
1432 };
1433
1434 let bytes = manifest.to_bytes();
1435
1436 let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1437 "expected malformed procedure export leaf name rejection during deserialization",
1438 );
1439 let message = alloc::format!("{err}");
1440 assert_matches!(
1441 message,
1442 msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1443 );
1444 }
1445
1446 #[test]
1447 fn package_manifest_deserialization_rejects_malformed_quoted_constant_leaf() {
1448 let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1449 let exports = BTreeMap::from_iter([(
1450 bad.clone(),
1451 PackageExport::Constant(crate::ConstantExport {
1452 path: bad,
1453 value: miden_assembly_syntax::ast::ConstantValue::Int(
1454 miden_debug_types::Span::unknown(1u32.into()),
1455 ),
1456 }),
1457 )]);
1458
1459 let manifest = PackageManifest {
1460 exports,
1461 modules: Default::default(),
1462 dependencies: Default::default(),
1463 entrypoint: None,
1464 };
1465
1466 let bytes = manifest.to_bytes();
1467
1468 let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1469 "expected malformed constant export leaf name rejection during deserialization",
1470 );
1471 let message = alloc::format!("{err}");
1472 assert_matches!(
1473 message,
1474 msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1475 );
1476 }
1477
1478 #[test]
1479 fn package_manifest_deserialization_rejects_malformed_quoted_type_leaf() {
1480 let bad = Arc::<AstPath>::from(AstPath::validate(r#"::foo::"bad name""#).unwrap());
1481 let exports = BTreeMap::from_iter([(
1482 bad.clone(),
1483 PackageExport::Type(crate::TypeExport {
1484 path: bad,
1485 ty: miden_assembly_syntax::ast::types::Type::Felt,
1486 }),
1487 )]);
1488
1489 let manifest = PackageManifest {
1490 exports,
1491 modules: Default::default(),
1492 dependencies: Default::default(),
1493 entrypoint: None,
1494 };
1495
1496 let bytes = manifest.to_bytes();
1497
1498 let err = PackageManifest::read_from_bytes(&bytes).expect_err(
1499 "expected malformed type export leaf name rejection during deserialization",
1500 );
1501 let message = alloc::format!("{err}");
1502 assert_matches!(
1503 message,
1504 msg if msg.contains("invalid export path '::foo::\"bad name\"': invalid item path component"),
1505 );
1506 }
1507
1508 #[test]
1509 fn regression_package_deserialisation_rejects_spoofed_mast_node_digests() {
1510 let (forest, node_id) =
1516 build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1517 let digest = forest[node_id].digest();
1518
1519 let path = absolute_path("lib::p");
1520 let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1521 Arc::clone(&path),
1522 Some(node_id),
1523 digest,
1524 None,
1525 ))];
1526
1527 let package = Package {
1528 name: PackageId::from("lib"),
1529 version: crate::Version::new(0, 0, 0),
1530 digest,
1531 description: None,
1532 kind: TargetType::Library,
1533 mast: Arc::new(forest),
1534 manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1535 sections: Default::default(),
1536 debug_sections_trusted: true,
1537 };
1538
1539 let (bytes, _) =
1540 build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-library-digest");
1541 let err = Package::read_from_bytes(&bytes)
1542 .expect_err("expected package deserialization to reject inconsistent node digests");
1543 assert!(
1544 err.to_string().contains("invalid untrusted MAST forest"),
1545 "expected untrusted-MAST validation failure, got: {err}"
1546 );
1547 assert!(
1548 err.to_string().contains("hash mismatch for node"),
1549 "expected digest mismatch failure, got: {err}"
1550 );
1551 }
1552
1553 #[test]
1554 fn unchecked_package_deserialisation_rejects_spoofed_mast_node_digests() {
1555 let (forest, node_id) =
1561 build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1562 let digest = forest[node_id].digest();
1563
1564 let path = absolute_path("lib::p");
1565 let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1566 Arc::clone(&path),
1567 Some(node_id),
1568 digest,
1569 None,
1570 ))];
1571
1572 let package = Package {
1573 name: PackageId::from("lib"),
1574 version: crate::Version::new(0, 0, 0),
1575 digest,
1576 description: None,
1577 kind: TargetType::Library,
1578 mast: Arc::new(forest),
1579 manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1580 sections: Default::default(),
1581 debug_sections_trusted: true,
1582 };
1583
1584 let (bytes, _spoofed_digest) =
1585 build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-library-digest");
1586 let err = Package::read_from_bytes_unchecked(&bytes)
1587 .expect_err("expected package deserialization to reject inconsistent node digests");
1588 assert!(
1589 err.to_string()
1590 .contains("declared node id and digest do not correspond to a procedure root"),
1591 "expected package manifest validation failure, got: {err}"
1592 );
1593 }
1594
1595 #[test]
1596 fn regression_kernel_package_deserialisation_rejects_spoofed_mast_node_digests() {
1597 let (forest, node_id) =
1603 build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1604 let digest = forest[node_id].digest();
1605
1606 let path = absolute_path("$kernel::k1");
1607 let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1608 Arc::clone(&path),
1609 Some(node_id),
1610 digest,
1611 None,
1612 ))];
1613
1614 let package = Package {
1615 name: PackageId::from("kernel"),
1616 version: crate::Version::new(0, 0, 0),
1617 digest,
1618 description: None,
1619 kind: TargetType::Kernel,
1620 mast: Arc::new(forest),
1621 manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1622 sections: Default::default(),
1623 debug_sections_trusted: true,
1624 };
1625
1626 let (bytes, _) =
1627 build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1628 let err = Package::read_from_bytes(&bytes).expect_err(
1629 "expected kernel package deserialization to reject inconsistent node digests",
1630 );
1631 assert!(
1632 err.to_string().contains("invalid untrusted MAST forest"),
1633 "expected untrusted-MAST validation failure, got: {err}"
1634 );
1635 assert!(
1636 err.to_string().contains("hash mismatch for node"),
1637 "expected digest mismatch failure, got: {err}"
1638 );
1639 }
1640
1641 #[cfg(feature = "std")]
1642 #[test]
1643 fn package_deserialize_from_file_rejects_spoofed_kernel_mast_node_digests() {
1644 let (forest, node_id) =
1650 build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1651 let digest = forest[node_id].digest();
1652
1653 let path = absolute_path("$kernel::k1");
1654 let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1655 Arc::clone(&path),
1656 Some(node_id),
1657 digest,
1658 None,
1659 ))];
1660
1661 let package = Package {
1662 name: PackageId::from("kernel"),
1663 version: crate::Version::new(0, 0, 0),
1664 digest,
1665 description: None,
1666 kind: TargetType::Kernel,
1667 mast: Arc::new(forest),
1668 manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1669 sections: Default::default(),
1670 debug_sections_trusted: true,
1671 };
1672
1673 let (bytes, _) =
1674 build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1675 let file_path = std::env::temp_dir().join(format!(
1676 "miden-package-deserialize-{}-{}.masp",
1677 std::process::id(),
1678 "spoofed-kernel-digest"
1679 ));
1680 fs::write(&file_path, bytes).expect("failed to write tampered package file");
1681
1682 let err = Package::deserialize_from_file(&file_path)
1683 .expect_err("expected file deserialization to reject inconsistent node digests");
1684 fs::remove_file(&file_path).unwrap();
1685
1686 assert!(
1687 err.to_string().contains("invalid untrusted MAST forest"),
1688 "expected untrusted-MAST validation failure, got: {err}"
1689 );
1690 assert!(
1691 err.to_string().contains("hash mismatch for node"),
1692 "expected digest mismatch failure, got: {err}"
1693 );
1694 }
1695
1696 #[test]
1697 fn unchecked_kernel_package_deserialisation_accepts_spoofed_mast_node_digests() {
1698 let (forest, node_id) =
1704 build_single_node_forest(vec![Operation::Push(Felt::from_u32(1))], false);
1705 let digest = forest[node_id].digest();
1706
1707 let path = absolute_path("$kernel::k1");
1708 let exports = vec![PackageExport::Procedure(ProcedureExport::new(
1709 Arc::clone(&path),
1710 Some(node_id),
1711 digest,
1712 None,
1713 ))];
1714
1715 let package = Package {
1716 name: PackageId::from("kernel"),
1717 version: crate::Version::new(0, 0, 0),
1718 digest,
1719 description: None,
1720 kind: TargetType::Kernel,
1721 mast: Arc::new(forest),
1722 manifest: PackageManifest::new(exports).expect("test manifest should be valid"),
1723 sections: Default::default(),
1724 debug_sections_trusted: true,
1725 };
1726
1727 let (bytes, _spoofed_digest) =
1728 build_package_bytes_with_spoofed_first_node_digest(&package, "spoofed-kernel-digest");
1729 let err = Package::read_from_bytes_unchecked(&bytes).expect_err(
1730 "expected unchecked kernel deserialization to reject inconsistent node digests",
1731 );
1732 assert!(
1733 err.to_string()
1734 .contains("declared node id and digest do not correspond to a procedure root"),
1735 "expected package manifest validation failure, got: {err}"
1736 );
1737 }
1738
1739 fn read_usize_vint64(bytes: &[u8], offset: &mut usize) -> usize {
1740 let first_byte = bytes.get(*offset).copied().expect("out-of-bounds vint64 peek");
1743 let length = first_byte.trailing_zeros() as usize + 1;
1744
1745 if length == 9 {
1746 *offset += 1;
1747 let end = (*offset).checked_add(8).expect("offset overflow while reading vint64");
1748 let chunk: [u8; 8] = bytes[*offset..end].try_into().expect("out-of-bounds vint64");
1749 *offset = end;
1750 let value = u64::from_le_bytes(chunk);
1751 usize::try_from(value).expect("encoded usize does not fit host usize")
1752 } else {
1753 let end = (*offset).checked_add(length).expect("offset overflow while reading vint64");
1754 let mut encoded = [0u8; 8];
1755 encoded[..length].copy_from_slice(&bytes[*offset..end]);
1756 *offset = end;
1757 let value = u64::from_le_bytes(encoded) >> length;
1758 usize::try_from(value).expect("encoded usize does not fit host usize")
1759 }
1760 }
1761
1762 fn locate_first_node_hash(bytes: &[u8]) -> (usize, usize) {
1763 let mut offset = 0usize;
1765 offset += 4;
1766 offset += 1;
1767 offset += 3;
1768
1769 let internal_node_count = read_usize_vint64(bytes, &mut offset);
1770 let external_node_count = read_usize_vint64(bytes, &mut offset);
1771 let node_count = internal_node_count
1772 .checked_add(external_node_count)
1773 .expect("node count overflow");
1774
1775 let roots_len = read_usize_vint64(bytes, &mut offset);
1777 offset += roots_len * 4;
1778
1779 let bb_len = read_usize_vint64(bytes, &mut offset);
1781 offset += bb_len;
1782
1783 offset += node_count * 8;
1784 offset += external_node_count * 32;
1785
1786 (offset, internal_node_count)
1787 }
1788
1789 fn build_package_bytes_with_spoofed_first_node_digest(
1790 lib: &Package,
1791 spoof_seed: &str,
1792 ) -> (Vec<u8>, Word) {
1793 use miden_core::serde::Serializable;
1794
1795 let forest = lib.mast_forest().as_ref();
1797 let original_digest = forest[MastNodeId::new_unchecked(0)].digest();
1798 let mut output_bytes = Vec::new();
1799 lib.write_header_into(&mut output_bytes);
1800 let forest_offset = output_bytes.len();
1801 forest.write_into(&mut output_bytes);
1802
1803 let (node_hashes_start, node_count) =
1804 locate_first_node_hash(&output_bytes[forest_offset..]);
1805 assert!(node_count > 0, "expected at least one node info entry");
1806
1807 let spoofed_digest = miden_core::utils::hash_string_to_word(spoof_seed);
1809 assert_ne!(spoofed_digest, original_digest, "spoofed digest must differ");
1810
1811 let mut spoofed_digest_bytes = Vec::new();
1812 spoofed_digest.write_into(&mut spoofed_digest_bytes);
1813 assert_eq!(spoofed_digest_bytes.len(), 32, "Word must serialize to 32 bytes");
1814
1815 let node0_digest_offset = forest_offset + node_hashes_start;
1816 output_bytes[node0_digest_offset..node0_digest_offset + 32]
1817 .copy_from_slice(&spoofed_digest_bytes);
1818
1819 lib.write_trailer_into(&mut output_bytes);
1820
1821 (output_bytes, spoofed_digest)
1822 }
1823}