miden_core/
program.rs

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