miden_core/
program.rs

1use alloc::{sync::Arc, vec::Vec};
2use core::fmt;
3
4use miden_crypto::{hash::rpo::RpoDigest, Felt, WORD_SIZE};
5use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
6
7use super::Kernel;
8use crate::{
9    mast::{MastForest, MastNode, MastNodeId},
10    utils::ToElements,
11};
12
13// PROGRAM
14// ===============================================================================================
15
16/// An executable program for Miden VM.
17///
18/// A program consists of a MAST forest, an entrypoint defining the MAST node at which the program
19/// execution begins, and a definition of the kernel against which the program must be executed
20/// (the kernel can be an empty kernel).
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct Program {
23    mast_forest: Arc<MastForest>,
24    /// The "entrypoint" is the node where execution of the program begins.
25    entrypoint: MastNodeId,
26    kernel: Kernel,
27}
28
29/// Constructors
30impl Program {
31    /// Construct a new [`Program`] from the given MAST forest and entrypoint. The kernel is assumed
32    /// to be empty.
33    ///
34    /// # Panics:
35    /// - if `mast_forest` doesn't contain the specified entrypoint.
36    /// - if the specified entrypoint is not a procedure root in the `mast_forest`.
37    pub fn new(mast_forest: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
38        Self::with_kernel(mast_forest, entrypoint, Kernel::default())
39    }
40
41    /// Construct a new [`Program`] from the given MAST forest, entrypoint, and kernel.
42    ///
43    /// # Panics:
44    /// - if `mast_forest` doesn't contain the specified entrypoint.
45    /// - if the specified entrypoint is not a procedure root in the `mast_forest`.
46    pub fn with_kernel(
47        mast_forest: Arc<MastForest>,
48        entrypoint: MastNodeId,
49        kernel: Kernel,
50    ) -> Self {
51        assert!(mast_forest.get_node_by_id(entrypoint).is_some(), "invalid entrypoint");
52        assert!(mast_forest.is_procedure_root(entrypoint), "entrypoint not a procedure");
53
54        Self { mast_forest, entrypoint, kernel }
55    }
56}
57
58// ------------------------------------------------------------------------------------------------
59/// Public accessors
60impl Program {
61    /// Returns the hash of the program's entrypoint.
62    ///
63    /// Equivalently, returns the hash of the root of the entrypoint procedure.
64    pub fn hash(&self) -> RpoDigest {
65        self.mast_forest[self.entrypoint].digest()
66    }
67
68    /// Returns the entrypoint associated with this program.
69    pub fn entrypoint(&self) -> MastNodeId {
70        self.entrypoint
71    }
72
73    /// Returns a reference to the underlying [`MastForest`].
74    pub fn mast_forest(&self) -> &Arc<MastForest> {
75        &self.mast_forest
76    }
77
78    /// Returns the kernel associated with this program.
79    pub fn kernel(&self) -> &Kernel {
80        &self.kernel
81    }
82
83    /// Returns the [`MastNode`] associated with the provided [`MastNodeId`] if valid, or else
84    /// `None`.
85    ///
86    /// This is the fallible version of indexing (e.g. `program[node_id]`).
87    #[inline(always)]
88    pub fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> {
89        self.mast_forest.get_node_by_id(node_id)
90    }
91
92    /// Returns the [`MastNodeId`] of the procedure root associated with a given digest, if any.
93    #[inline(always)]
94    pub fn find_procedure_root(&self, digest: RpoDigest) -> Option<MastNodeId> {
95        self.mast_forest.find_procedure_root(digest)
96    }
97
98    /// Returns the number of procedures in this program.
99    pub fn num_procedures(&self) -> u32 {
100        self.mast_forest.num_procedures()
101    }
102}
103
104// ------------------------------------------------------------------------------------------------
105/// Serialization
106#[cfg(feature = "std")]
107impl Program {
108    /// Writes this [Program] to the provided file path.
109    pub fn write_to_file<P>(&self, path: P) -> std::io::Result<()>
110    where
111        P: AsRef<std::path::Path>,
112    {
113        let path = path.as_ref();
114        if let Some(dir) = path.parent() {
115            std::fs::create_dir_all(dir)?;
116        }
117
118        // NOTE: We're protecting against unwinds here due to i/o errors that will get turned into
119        // panics if writing to the underlying file fails. This is because ByteWriter does not have
120        // fallible APIs, thus WriteAdapter has to panic if writes fail. This could be fixed, but
121        // that has to happen upstream in winterfell
122        std::panic::catch_unwind(|| match std::fs::File::create(path) {
123            Ok(ref mut file) => {
124                self.write_into(file);
125                Ok(())
126            },
127            Err(err) => Err(err),
128        })
129        .map_err(|p| {
130            match p.downcast::<std::io::Error>() {
131                // SAFETY: It is guaranteed to be safe to read Box<std::io::Error>
132                Ok(err) => unsafe { core::ptr::read(&*err) },
133                // Propagate unknown panics
134                Err(err) => std::panic::resume_unwind(err),
135            }
136        })?
137    }
138}
139
140impl Serializable for Program {
141    fn write_into<W: ByteWriter>(&self, target: &mut W) {
142        self.mast_forest.write_into(target);
143        self.kernel.write_into(target);
144        target.write_u32(self.entrypoint.as_u32());
145    }
146}
147
148impl Deserializable for Program {
149    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
150        let mast_forest = Arc::new(source.read()?);
151        let kernel = source.read()?;
152        let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?;
153
154        if !mast_forest.is_procedure_root(entrypoint) {
155            return Err(DeserializationError::InvalidValue(format!(
156                "entrypoint {entrypoint} is not a procedure"
157            )));
158        }
159
160        Ok(Self::with_kernel(mast_forest, entrypoint, kernel))
161    }
162}
163
164// ------------------------------------------------------------------------------------------------
165// Pretty-printing
166
167impl crate::prettier::PrettyPrint for Program {
168    fn render(&self) -> crate::prettier::Document {
169        use crate::prettier::*;
170        let entrypoint = self.mast_forest[self.entrypoint()].to_pretty_print(&self.mast_forest);
171
172        indent(4, const_text("begin") + nl() + entrypoint.render()) + nl() + const_text("end")
173    }
174}
175
176impl fmt::Display for Program {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        use crate::prettier::PrettyPrint;
179        self.pretty_print(f)
180    }
181}
182
183// PROGRAM INFO
184// ===============================================================================================
185
186/// A program information set consisting of its MAST root and set of kernel procedure roots used
187/// for its compilation.
188///
189/// This will be used as public inputs of the proof so we bind its verification to the kernel and
190/// root used to execute the program. This way, we extend the correctness of the proof to the
191/// security guarantees provided by the kernel. We also allow the user to easily prove the
192/// membership of a given kernel procedure for a given proof, without compromising its
193/// zero-knowledge properties.
194#[derive(Debug, Clone, Default, PartialEq, Eq)]
195pub struct ProgramInfo {
196    program_hash: RpoDigest,
197    kernel: Kernel,
198}
199
200impl ProgramInfo {
201    /// Creates a new instance of a program info.
202    pub const fn new(program_hash: RpoDigest, kernel: Kernel) -> Self {
203        Self { program_hash, kernel }
204    }
205
206    /// Returns the program hash computed from its code block root.
207    pub const fn program_hash(&self) -> &RpoDigest {
208        &self.program_hash
209    }
210
211    /// Returns the program kernel used during the compilation.
212    pub const fn kernel(&self) -> &Kernel {
213        &self.kernel
214    }
215
216    /// Returns the list of procedures of the kernel used during the compilation.
217    pub fn kernel_procedures(&self) -> &[RpoDigest] {
218        self.kernel.proc_hashes()
219    }
220}
221
222impl From<Program> for ProgramInfo {
223    fn from(program: Program) -> Self {
224        let program_hash = program.hash();
225        let kernel = program.kernel().clone();
226
227        Self { program_hash, kernel }
228    }
229}
230
231// ------------------------------------------------------------------------------------------------
232// Serialization
233
234impl Serializable for ProgramInfo {
235    fn write_into<W: ByteWriter>(&self, target: &mut W) {
236        self.program_hash.write_into(target);
237        self.kernel.write_into(target);
238    }
239}
240
241impl Deserializable for ProgramInfo {
242    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
243        let program_hash = source.read()?;
244        let kernel = source.read()?;
245        Ok(Self { program_hash, kernel })
246    }
247}
248
249// ------------------------------------------------------------------------------------------------
250// ToElements implementation
251
252impl ToElements for ProgramInfo {
253    fn to_elements(&self) -> Vec<Felt> {
254        let num_kernel_proc_elements = self.kernel.proc_hashes().len() * WORD_SIZE;
255        let mut result = Vec::with_capacity(WORD_SIZE + num_kernel_proc_elements);
256
257        // append program hash elements
258        result.extend_from_slice(self.program_hash.as_elements());
259
260        // append kernel procedure hash elements
261        for proc_hash in self.kernel.proc_hashes() {
262            result.extend_from_slice(proc_hash.as_elements());
263        }
264        result
265    }
266}