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