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