1mod id;
2mod manifest;
3mod section;
4#[cfg(test)]
5mod seed_gen;
6mod serialization;
7mod target_type;
8
9use alloc::{
10 boxed::Box,
11 format,
12 string::{String, ToString},
13 sync::Arc,
14 vec::Vec,
15};
16
17use miden_assembly_syntax::{
18 KernelLibrary, Library, Report, ast::QualifiedProcedureName, library::ModuleInfo,
19};
20use miden_core::{
21 Word,
22 crypto::hash::Poseidon2,
23 program::Kernel,
24 serde::{ByteWriter, Deserializable, Serializable},
25};
26#[cfg(feature = "serde")]
27use serde::{Deserialize, Serialize};
28
29pub use self::{
30 id::PackageId,
31 manifest::{
32 ConstantExport, ManifestValidationError, PackageExport, PackageManifest, ProcedureExport,
33 TypeExport,
34 },
35 section::{InvalidSectionIdError, Section, SectionId},
36 target_type::{InvalidTargetTypeError, TargetType},
37};
38use crate::{Dependency, Version};
39
40#[derive(Debug, Clone, Eq, PartialEq)]
52#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
53pub struct Package {
54 pub name: PackageId,
56 pub version: Version,
58 #[cfg_attr(feature = "serde", serde(default))]
60 pub description: Option<String>,
61 pub kind: TargetType,
63 pub mast: Arc<Library>,
70 pub manifest: PackageManifest,
73 #[cfg_attr(feature = "serde", serde(default))]
76 pub sections: Vec<Section>,
77}
78
79impl Package {
81 pub fn from_library(
83 name: PackageId,
84 version: Version,
85 kind: TargetType,
86 library: Arc<Library>,
87 dependencies: impl IntoIterator<Item = Dependency>,
88 ) -> Box<Package> {
89 let manifest = PackageManifest::from_library(&library)
90 .with_dependencies(dependencies)
91 .expect("package dependencies should be unique");
92
93 Box::new(Self {
94 name,
95 version,
96 description: None,
97 kind,
98 mast: library,
99 manifest,
100 sections: Vec::new(),
101 })
102 }
103}
104
105impl Package {
107 pub const EXTENSION: &str = "masp";
109
110 pub fn digest(&self) -> Word {
112 *self.mast.digest()
113 }
114
115 pub fn content_digest(&self) -> Word {
123 let mut bytes = Vec::new();
124 self.write_content_digest_preimage(&mut bytes, None);
125 Poseidon2::hash(&bytes)
126 }
127
128 fn write_content_digest_preimage<W: ByteWriter>(
129 &self,
130 target: &mut W,
131 kernel_digest: Option<&Word>,
132 ) {
133 target.write_bytes(b"miden.package.content.v2");
134 self.digest().write_into(target);
135 self.name.write_into(target);
136 self.version.to_string().write_into(target);
137 target.write_u8(self.kind.into());
138 self.manifest.write_into(target);
139 self.write_content_digest_sections(target);
140 target.write_bool(kernel_digest.is_some());
141 if let Some(kernel_digest) = kernel_digest {
142 kernel_digest.write_into(target);
143 }
144 }
145
146 fn write_content_digest_sections<W: ByteWriter>(&self, target: &mut W) {
147 let semantic_sections = self
148 .sections
149 .iter()
150 .filter(|section| section.id == SectionId::ACCOUNT_COMPONENT_METADATA)
151 .collect::<Vec<_>>();
152 target.write_usize(semantic_sections.len());
153 for section in semantic_sections {
154 section.write_into(target);
155 }
156 }
157
158 pub fn is_program(&self) -> bool {
160 self.kind.is_executable()
161 }
162
163 pub fn is_library(&self) -> bool {
165 self.kind.is_library()
166 }
167
168 pub fn is_kernel(&self) -> bool {
170 matches!(self.kind, TargetType::Kernel)
171 }
172
173 pub fn kernel_module_info(&self) -> Result<ModuleInfo, Report> {
175 self.mast
176 .module_infos()
177 .find(|mi| mi.path().is_kernel_path())
178 .ok_or_else(|| Report::msg("invalid kernel package: does not contain kernel module"))
179 }
180
181 pub fn to_kernel(&self) -> Result<Kernel, Report> {
183 let exports = self
184 .manifest
185 .exports()
186 .filter_map(|export| {
187 if export.namespace().is_kernel_path()
188 && let PackageExport::Procedure(p) = export
189 {
190 Some(p.digest)
191 } else {
192 None
193 }
194 })
195 .collect::<Vec<_>>();
196 if exports.is_empty() {
197 return Err(Report::msg(
198 "invalid kernel package: does not export any kernel procedures",
199 ));
200 }
201 Kernel::new(&exports).map_err(|err| Report::msg(format!("invalid kernel package: {err}")))
202 }
203
204 pub fn try_into_kernel_library(&self) -> Result<KernelLibrary, Report> {
208 if !self.is_kernel() {
209 return Err(Report::msg(format!(
210 "expected package '{}' to contain a kernel, but kind was '{}'",
211 self.name, self.kind
212 )));
213 }
214
215 KernelLibrary::try_from(self.mast.clone()).map_err(|error| Report::msg(error.to_string()))
216 }
217
218 #[doc(hidden)]
220 pub fn try_into_program(&self) -> Result<miden_core::program::Program, Report> {
221 use miden_assembly_syntax::{Path as MasmPath, ast};
222 use miden_core::program::Program;
223
224 if !self.is_program() {
225 return Err(Report::msg(format!(
226 "cannot convert package of type {} to Executable",
227 self.kind
228 )));
229 }
230 let main_path = MasmPath::exec_path().join(ast::ProcedureName::MAIN_PROC_NAME);
231 if let Some(digest) = self.mast.get_procedure_root_by_path(&main_path)
232 && let Some(entrypoint) = self.mast.mast_forest().find_procedure_root(digest)
233 {
234 let mast_forest = self.mast.mast_forest().clone();
235 let kernel_dependency = self.kernel_runtime_dependency()?.cloned();
236 match (self.try_embedded_kernel_library()?, kernel_dependency) {
237 (Some(kernel_library), _) => Ok(Program::with_kernel(
238 mast_forest,
239 entrypoint,
240 kernel_library.kernel().clone(),
241 )),
242 (None, Some(kernel_dependency)) => Err(Report::msg(format!(
243 "package '{}' declares kernel runtime dependency '{}@{}#{}', but does not embed the kernel package required to reconstruct a program",
244 self.name,
245 kernel_dependency.name,
246 kernel_dependency.version,
247 kernel_dependency.digest
248 ))),
249 (None, None) => Ok(Program::new(mast_forest, entrypoint)),
250 }
251 } else {
252 Err(Report::msg(format!(
253 "malformed executable package: no procedure root for '{main_path}'"
254 )))
255 }
256 }
257
258 #[doc(hidden)]
260 pub fn unwrap_program(&self) -> miden_core::program::Program {
261 assert_eq!(self.kind, TargetType::Executable);
262 self.try_into_program().unwrap_or_else(|err| panic!("{err}"))
263 }
264
265 #[doc(hidden)]
266 pub fn try_embedded_kernel_package(&self) -> Result<Option<Self>, Report> {
267 let Some(kernel_package) = self.embedded_kernel_package()? else {
268 return Ok(None);
269 };
270 self.validate_embedded_kernel_dependency(&kernel_package)?;
271 Ok(Some(kernel_package))
272 }
273
274 fn try_embedded_kernel_library(&self) -> Result<Option<KernelLibrary>, Report> {
275 let Some(kernel_package) = self.try_embedded_kernel_package()? else {
276 return Ok(None);
277 };
278 kernel_package.try_into_kernel_library().map(Some)
279 }
280
281 fn embedded_kernel_package(&self) -> Result<Option<Self>, Report> {
289 let mut sections = self.sections.iter().filter(|section| section.id == SectionId::KERNEL);
290 let Some(section) = sections.next() else {
291 return Ok(None);
292 };
293 if sections.next().is_some() {
294 return Err(Report::msg(format!(
295 "package '{}' contains multiple '{}' sections",
296 self.name,
297 SectionId::KERNEL
298 )));
299 }
300
301 Self::read_from_bytes(section.data.as_ref()).map(Some).map_err(|error| {
302 Report::msg(format!(
303 "failed to decode embedded kernel package for '{}': {error}",
304 self.name
305 ))
306 })
307 }
308
309 fn validate_embedded_kernel_dependency(&self, kernel_package: &Self) -> Result<(), Report> {
310 if !kernel_package.is_kernel() {
311 return Err(Report::msg(format!(
312 "package '{}' embeds '{}', but its kind is '{}'",
313 self.name, kernel_package.name, kernel_package.kind
314 )));
315 }
316
317 let Some(kernel_dependency) = self.kernel_runtime_dependency()? else {
318 return Err(Report::msg(format!(
319 "package '{}' embeds a kernel package, but does not declare a kernel runtime dependency",
320 self.name
321 )));
322 };
323
324 if kernel_dependency.name != kernel_package.name
325 || kernel_dependency.version != kernel_package.version
326 || kernel_dependency.digest != kernel_package.digest()
327 {
328 return Err(Report::msg(format!(
329 "package '{}' declares kernel runtime dependency '{}@{}#{}', but that does not match the embedded kernel package '{}@{}#{}'",
330 self.name,
331 kernel_dependency.name,
332 kernel_dependency.version,
333 kernel_dependency.digest,
334 kernel_package.name,
335 kernel_package.version,
336 kernel_package.digest()
337 )));
338 }
339
340 Ok(())
341 }
342
343 pub fn to_dependency(&self) -> Dependency {
344 Dependency {
345 name: self.name.clone(),
346 version: self.version.clone(),
347 kind: self.kind,
348 digest: self.digest(),
349 }
350 }
351
352 pub fn kernel_runtime_dependency(&self) -> Result<Option<&Dependency>, Report> {
357 let mut kernel_dependencies = self
358 .manifest
359 .dependencies()
360 .filter(|dependency| dependency.kind == TargetType::Kernel);
361 let Some(kernel_dependency) = kernel_dependencies.next() else {
362 return Ok(None);
363 };
364 if kernel_dependencies.next().is_some() {
365 return Err(Report::msg(format!(
366 "package '{}' declares multiple kernel runtime dependencies",
367 self.name
368 )));
369 }
370
371 Ok(Some(kernel_dependency))
372 }
373
374 pub fn make_executable(&self, entrypoint: &QualifiedProcedureName) -> Result<Self, Report> {
387 use miden_assembly_syntax::{
388 Path as MasmPath, ast as masm,
389 library::{self, LibraryExport},
390 };
391 if !self.is_library() {
392 return Err(Report::msg("expected library but got an executable"));
393 }
394
395 let entrypoint_namespace = entrypoint.namespace().to_absolute();
396 let module = self
397 .mast
398 .module_infos()
399 .find(|info| info.path() == entrypoint_namespace.as_ref())
400 .ok_or_else(|| {
401 Report::msg(format!(
402 "invalid entrypoint: library does not contain a module named '{}'",
403 entrypoint.namespace()
404 ))
405 })?;
406 if let Some(digest) = module.get_procedure_digest_by_name(entrypoint.name()) {
407 let mast_forest = self.mast.mast_forest().clone();
408 let node_id = mast_forest.find_procedure_root(digest).ok_or_else(|| {
409 Report::msg(
410 "invalid entrypoint: malformed library - procedure exported, but digest has \
411 no node in the forest",
412 )
413 })?;
414
415 let exec_path: Arc<MasmPath> =
416 MasmPath::exec_path().join(masm::ProcedureName::MAIN_PROC_NAME).into();
417 Ok(Self {
418 name: self.name.clone(),
419 version: self.version.clone(),
420 description: self.description.clone(),
421 kind: TargetType::Executable,
422 mast: Arc::new(Library::new(
423 mast_forest,
424 alloc::collections::BTreeMap::from_iter([(
425 exec_path.clone(),
426 LibraryExport::Procedure(library::ProcedureExport {
427 node: node_id,
428 path: exec_path,
429 signature: None,
430 attributes: Default::default(),
431 }),
432 )]),
433 )?),
434 manifest: PackageManifest::new(
435 self.manifest
436 .get_procedures_by_digest(&digest)
437 .cloned()
438 .map(PackageExport::Procedure),
439 )
440 .and_then(|manifest| {
441 manifest.with_dependencies(self.manifest.dependencies().cloned())
442 })
443 .expect("executable package manifest should remain valid"),
444 sections: self.sections.clone(),
445 })
446 } else {
447 Err(Report::msg(format!(
448 "invalid entrypoint: library does not export '{entrypoint}'"
449 )))
450 }
451 }
452
453 pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
457 self.mast.mast_forest().procedure_name(digest)
458 }
459
460 pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
462 self.mast.mast_forest().procedure_names()
463 }
464
465 #[cfg(feature = "std")]
467 pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
468 use miden_core::serde::Serializable;
469
470 let path = path.as_ref();
471 if let Some(dir) = path.parent() {
472 std::fs::create_dir_all(dir)?;
473 }
474
475 let mut file = std::fs::File::create(path)?;
476 <Self as Serializable>::write_into(self, &mut file);
477 Ok(())
478 }
479
480 #[cfg(feature = "std")]
482 pub fn write_masp_file(&self, dir: impl AsRef<std::path::Path>) -> std::io::Result<()> {
483 let dir = dir.as_ref();
484 let package_name: &str = &self.name;
485 self.write_to_file(dir.join(package_name).with_extension(Self::EXTENSION))
486 .map_err(|err| std::io::Error::other(err.to_string()))
487 }
488}
489
490#[cfg(feature = "arbitrary")]
491impl Package {
492 pub fn generate(
493 name: PackageId,
494 version: Version,
495 kind: TargetType,
496 dependencies: impl IntoIterator<Item = Dependency>,
497 ) -> Box<Self> {
498 let library = arbitrary_library();
499
500 Self::from_library(name, version, kind, library, dependencies)
501 }
502}
503
504#[cfg(feature = "arbitrary")]
505fn arbitrary_library() -> Arc<Library> {
506 use proptest::prelude::*;
507
508 let mut runner = proptest::test_runner::TestRunner::deterministic();
509 let value_tree = <Library as Arbitrary>::arbitrary().new_tree(&mut runner).unwrap();
510 Arc::new(value_tree.current())
511}
512
513#[cfg(test)]
517mod tests {
518 use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec};
519
520 use miden_assembly_syntax::{
521 Library,
522 ast::{Path as AstPath, PathBuf},
523 library::{LibraryExport, ProcedureExport as LibraryProcedureExport},
524 };
525 use miden_core::{
526 mast::{BasicBlockNodeBuilder, MastForest, MastForestContributor, MastNodeId},
527 operations::Operation,
528 serde::Serializable,
529 };
530
531 use super::*;
532 use crate::{Dependency, Version};
533
534 fn build_forest() -> (MastForest, MastNodeId) {
535 let mut forest = MastForest::new();
536 let node_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
537 .add_to_forest(&mut forest)
538 .expect("failed to build basic block");
539 forest.make_root(node_id);
540 (forest, node_id)
541 }
542
543 fn absolute_path(name: &str) -> Arc<AstPath> {
544 let path = PathBuf::new(name).expect("invalid path");
545 let path = path.as_path().to_absolute().into_owned();
546 Arc::from(path.into_boxed_path())
547 }
548
549 fn build_library(export: &str) -> Arc<Library> {
550 let (forest, node_id) = build_forest();
551 let path = absolute_path(export);
552 let export = LibraryProcedureExport::new(node_id, Arc::clone(&path));
553
554 let mut exports = BTreeMap::new();
555 exports.insert(path, LibraryExport::Procedure(export));
556
557 Arc::new(Library::new(Arc::new(forest), exports).expect("failed to build library"))
558 }
559
560 fn build_package(
561 name: &str,
562 kind: TargetType,
563 export: &str,
564 dependencies: impl IntoIterator<Item = Dependency>,
565 sections: Vec<Section>,
566 ) -> Package {
567 let mut package = *Package::from_library(
568 PackageId::from(name),
569 Version::new(1, 0, 0),
570 kind,
571 build_library(export),
572 dependencies,
573 );
574 package.sections = sections;
575 package
576 }
577
578 fn build_kernel_package(name: &str) -> Package {
579 build_package(name, TargetType::Kernel, &format!("{name}::boot"), [], Vec::new())
580 }
581
582 #[test]
583 fn to_kernel_rejects_empty_kernel_exports() {
584 let mut package = build_package("kernel", TargetType::Kernel, "$kernel::boot", [], vec![]);
585 package.manifest =
586 PackageManifest::new([]).expect("empty package manifest should be valid");
587
588 let error = package
589 .to_kernel()
590 .expect_err("kernel packages without exported procedures should be rejected");
591
592 assert!(
593 error
594 .to_string()
595 .contains("invalid kernel package: does not export any kernel procedures")
596 );
597 }
598
599 fn kernel_dependency(package: &Package) -> Dependency {
600 Dependency {
601 name: package.name.clone(),
602 kind: TargetType::Kernel,
603 version: package.version.clone(),
604 digest: package.digest(),
605 }
606 }
607
608 #[test]
609 fn embedded_kernel_package_rejects_duplicate_kernel_sections() {
610 let kernel = build_kernel_package("kernel");
611 let kernel_bytes = kernel.to_bytes();
612 let package = build_package(
613 "app",
614 TargetType::Library,
615 "app::entry",
616 vec![kernel_dependency(&kernel)],
617 vec![
618 Section::new(SectionId::KERNEL, kernel_bytes.clone()),
619 Section::new(SectionId::KERNEL, kernel_bytes),
620 ],
621 );
622
623 let error = package
624 .try_embedded_kernel_package()
625 .expect_err("duplicate kernel sections should be rejected");
626
627 assert!(error.to_string().contains("multiple 'kernel' sections"));
628 }
629
630 #[test]
631 fn embedded_kernel_package_rejects_multiple_kernel_runtime_dependencies() {
632 let kernel_a = build_kernel_package("kernel-a");
633 let kernel_b = build_kernel_package("kernel-b");
634 let package = build_package(
635 "app",
636 TargetType::Library,
637 "app::entry",
638 vec![kernel_dependency(&kernel_a), kernel_dependency(&kernel_b)],
639 vec![Section::new(SectionId::KERNEL, kernel_a.to_bytes())],
640 );
641
642 let error = package
643 .try_embedded_kernel_package()
644 .expect_err("multiple kernel runtime dependencies should be rejected");
645
646 assert!(error.to_string().contains("declares multiple kernel runtime dependencies"));
647 }
648}