Skip to main content

miden_protocol/account/code/
mod.rs

1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use miden_core::mast::MastForest;
5use miden_core::prettier::PrettyPrint;
6
7use super::{
8    AccountError,
9    ByteReader,
10    ByteWriter,
11    Deserializable,
12    DeserializationError,
13    Felt,
14    Hasher,
15    Serializable,
16};
17use crate::Word;
18use crate::account::AccountComponent;
19#[cfg(any(feature = "testing", test))]
20use crate::account::AccountType;
21
22pub mod procedure;
23use procedure::{AccountProcedureRoot, PrintableProcedure};
24
25// ACCOUNT CODE
26// ================================================================================================
27
28/// The public interface of an account.
29///
30/// An account's public interface consists of a set of account procedures, each of which is
31/// identified and committed to by a MAST root. They are represented by [`AccountProcedureRoot`].
32///
33/// The set of procedures has an arbitrary order, i.e. they are not sorted. The only exception is
34/// the authentication procedure of the account, which is always at index 0. This procedure is
35/// automatically called at the end of a transaction to validate an account's state transition.
36///
37/// The code commits to the entire account interface by building a sequential hash of all procedure
38/// MAST roots. Specifically, each procedure contributes exactly 4 field elements to the sequence of
39/// elements to be hashed. Each procedure is represented by its MAST root:
40///
41/// ```text
42/// [PROCEDURE_MAST_ROOT]
43/// ```
44#[derive(Debug, Clone)]
45pub struct AccountCode {
46    mast: Arc<MastForest>,
47    procedures: Vec<AccountProcedureRoot>,
48    commitment: Word,
49}
50
51impl AccountCode {
52    // CONSTANTS
53    // --------------------------------------------------------------------------------------------
54
55    /// The minimum number of account interface procedures (one auth and at least one non-auth).
56    pub const MIN_NUM_PROCEDURES: usize = 2;
57
58    /// The maximum number of account interface procedures.
59    pub const MAX_NUM_PROCEDURES: usize = 256;
60
61    // CONSTRUCTORS
62    // --------------------------------------------------------------------------------------------
63
64    /// Creates a new [`AccountCode`] from the provided components' libraries.
65    ///
66    /// For testing use only.
67    #[cfg(any(feature = "testing", test))]
68    pub fn from_components(
69        components: &[AccountComponent],
70        account_type: AccountType,
71    ) -> Result<Self, AccountError> {
72        super::validate_components_support_account_type(components, account_type)?;
73        Self::from_components_unchecked(components)
74    }
75
76    /// Creates a new [`AccountCode`] from the provided components' libraries.
77    ///
78    /// # Warning
79    ///
80    /// This does not check whether the provided components are valid when combined.
81    ///
82    /// # Errors
83    ///
84    /// Returns an error if:
85    /// - The number of procedures in all merged libraries is 0 or exceeds
86    ///   [`AccountCode::MAX_NUM_PROCEDURES`].
87    /// - Two or more libraries export a procedure with the same MAST root.
88    /// - The first component doesn't contain exactly one authentication procedure.
89    /// - Other components contain authentication procedures.
90    /// - The number of [`StorageSlot`](crate::account::StorageSlot)s of a component or of all
91    ///   components exceeds 255.
92    /// - [`MastForest::merge`] fails on all libraries.
93    pub(super) fn from_components_unchecked(
94        components: &[AccountComponent],
95    ) -> Result<Self, AccountError> {
96        let (merged_mast_forest, _) =
97            MastForest::merge(components.iter().map(|component| component.mast_forest()))
98                .map_err(AccountError::AccountComponentMastForestMergeError)?;
99
100        let mut builder = AccountProcedureBuilder::new();
101        let mut components_iter = components.iter();
102
103        let first_component =
104            components_iter.next().ok_or(AccountError::AccountCodeNoAuthComponent)?;
105        builder.add_auth_component(first_component)?;
106
107        for component in components_iter {
108            builder.add_component(component)?;
109        }
110
111        let procedures = builder.build()?;
112
113        Ok(Self {
114            commitment: build_procedure_commitment(&procedures),
115            procedures,
116            mast: Arc::new(merged_mast_forest),
117        })
118    }
119
120    /// Returns a new [AccountCode] deserialized from the provided bytes.
121    ///
122    /// # Errors
123    /// Returns an error if account code deserialization fails.
124    pub fn from_bytes(bytes: &[u8]) -> Result<Self, AccountError> {
125        Self::read_from_bytes(bytes).map_err(AccountError::AccountCodeDeserializationError)
126    }
127
128    /// Returns a new [`AccountCode`] instantiated from the provided [`MastForest`] and a list of
129    /// [`AccountProcedureRoot`]s.
130    ///
131    /// # Panics
132    ///
133    /// Panics if:
134    /// - The number of procedures is smaller than 2 or greater than 256.
135    pub fn from_parts(mast: Arc<MastForest>, procedures: Vec<AccountProcedureRoot>) -> Self {
136        assert!(procedures.len() >= Self::MIN_NUM_PROCEDURES, "not enough account procedures");
137        assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures");
138
139        Self {
140            commitment: build_procedure_commitment(&procedures),
141            procedures,
142            mast,
143        }
144    }
145
146    // PUBLIC ACCESSORS
147    // --------------------------------------------------------------------------------------------
148
149    /// Returns a commitment to an account's public interface.
150    pub fn commitment(&self) -> Word {
151        self.commitment
152    }
153
154    /// Returns a reference to the [MastForest] backing this account code.
155    pub fn mast(&self) -> Arc<MastForest> {
156        self.mast.clone()
157    }
158
159    /// Returns a reference to the account procedure roots.
160    pub fn procedures(&self) -> &[AccountProcedureRoot] {
161        &self.procedures
162    }
163
164    /// Returns an iterator over the procedure MAST roots of this account code.
165    pub fn procedure_roots(&self) -> impl Iterator<Item = Word> + '_ {
166        self.procedures().iter().map(|procedure| *procedure.mast_root())
167    }
168
169    /// Returns the number of public interface procedures defined in this account code.
170    pub fn num_procedures(&self) -> usize {
171        self.procedures.len()
172    }
173
174    /// Returns true if a procedure with the specified MAST root is defined in this account code.
175    pub fn has_procedure(&self, mast_root: Word) -> bool {
176        self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
177    }
178
179    /// Returns the procedure root at the specified index.
180    pub fn get(&self, index: usize) -> Option<&AccountProcedureRoot> {
181        self.procedures.get(index)
182    }
183
184    /// Converts the procedure root in this [`AccountCode`] into a vector of field elements.
185    ///
186    /// This is done by first converting each procedure into 4 field elements as follows:
187    ///
188    /// ```text
189    /// [PROCEDURE_MAST_ROOT]
190    /// ```
191    ///
192    /// And then concatenating the resulting elements into a single vector.
193    pub fn as_elements(&self) -> Vec<Felt> {
194        procedures_as_elements(self.procedures())
195    }
196
197    /// Returns an iterator of printable representations for all procedures in this account code.
198    ///
199    /// # Returns
200    ///
201    /// An iterator yielding [`PrintableProcedure`] instances for all procedures in this account
202    /// code.
203    pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
204        self.procedures()
205            .iter()
206            .filter_map(move |proc_root| self.printable_procedure(proc_root).ok())
207    }
208
209    // HELPER FUNCTIONS
210    // --------------------------------------------------------------------------------------------
211
212    /// Returns a printable representation of the procedure with the specified MAST root.
213    ///
214    /// # Errors
215    /// Returns an error if no procedure with the specified root exists in this account code.
216    fn printable_procedure(
217        &self,
218        proc_root: &AccountProcedureRoot,
219    ) -> Result<PrintableProcedure, AccountError> {
220        let node_id = self
221            .mast
222            .find_procedure_root(*proc_root.mast_root())
223            .expect("procedure root should be present in the mast forest");
224
225        Ok(PrintableProcedure::new(self.mast.clone(), *proc_root, node_id))
226    }
227}
228
229// EQUALITY
230// ================================================================================================
231
232impl PartialEq for AccountCode {
233    fn eq(&self, other: &Self) -> bool {
234        // TODO: consider checking equality based only on the set of procedures
235        self.mast == other.mast && self.procedures == other.procedures
236    }
237}
238
239impl Ord for AccountCode {
240    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
241        self.commitment.cmp(&other.commitment)
242    }
243}
244
245impl PartialOrd for AccountCode {
246    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
247        Some(self.cmp(other))
248    }
249}
250
251impl Eq for AccountCode {}
252
253// SERIALIZATION
254// ================================================================================================
255
256impl Serializable for AccountCode {
257    fn write_into<W: ByteWriter>(&self, target: &mut W) {
258        self.mast.write_into(target);
259        // since the number of procedures is guaranteed to be between 2 and 256, we can store the
260        // number as a single byte - but we do have to subtract 1 to store 256 as 255.
261        target.write_u8((self.procedures.len() - 1) as u8);
262        target.write_many(self.procedures());
263    }
264
265    fn get_size_hint(&self) -> usize {
266        // TODO: Replace with proper calculation.
267        let mut mast_forest_target = Vec::new();
268        self.mast.write_into(&mut mast_forest_target);
269
270        // Size of the serialized procedures length.
271        let u8_size = 0u8.get_size_hint();
272        let mut size = u8_size + mast_forest_target.len();
273
274        for procedure in self.procedures() {
275            size += procedure.get_size_hint();
276        }
277
278        size
279    }
280}
281
282impl Deserializable for AccountCode {
283    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
284        let module = Arc::new(MastForest::read_from(source)?);
285        let num_procedures = (source.read_u8()? as usize) + 1;
286        let procedures = source
287            .read_many_iter(num_procedures)?
288            .collect::<Result<Vec<AccountProcedureRoot>, _>>()?;
289
290        Ok(Self::from_parts(module, procedures))
291    }
292}
293
294// PRETTY PRINT
295// ================================================================================================
296
297impl PrettyPrint for AccountCode {
298    fn render(&self) -> miden_core::prettier::Document {
299        use miden_core::prettier::*;
300        let mut partial = Document::Empty;
301        let len_procedures = self.num_procedures();
302
303        for (index, printable_procedure) in self.printable_procedures().enumerate() {
304            partial += indent(
305                0,
306                indent(
307                    4,
308                    text(format!("proc {}", printable_procedure.mast_root()))
309                        + nl()
310                        + printable_procedure.render(),
311                ) + nl()
312                    + const_text("end"),
313            );
314            if index < len_procedures - 1 {
315                partial += nl();
316            }
317        }
318        partial
319    }
320}
321
322// ACCOUNT PROCEDURE BUILDER
323// ================================================================================================
324
325/// A helper type for building the set of account procedures from account components.
326///
327/// In particular, this ensures that the auth procedure ends up at index 0.
328struct AccountProcedureBuilder {
329    procedures: Vec<AccountProcedureRoot>,
330}
331
332impl AccountProcedureBuilder {
333    fn new() -> Self {
334        Self { procedures: Vec::new() }
335    }
336
337    /// This method must be called before add_component is called.
338    fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
339        let mut auth_proc_count = 0;
340
341        for (proc_root, is_auth) in component.get_procedures() {
342            self.add_procedure(proc_root);
343
344            if is_auth {
345                let auth_proc_idx = self.procedures.len() - 1;
346                self.procedures.swap(0, auth_proc_idx);
347                auth_proc_count += 1;
348            }
349        }
350
351        if auth_proc_count == 0 {
352            return Err(AccountError::AccountCodeNoAuthComponent);
353        } else if auth_proc_count > 1 {
354            return Err(AccountError::AccountComponentMultipleAuthProcedures);
355        }
356
357        Ok(())
358    }
359
360    fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
361        for (proc_mast_root, is_auth) in component.get_procedures() {
362            if is_auth {
363                return Err(AccountError::AccountCodeMultipleAuthComponents);
364            }
365            self.add_procedure(proc_mast_root);
366        }
367
368        Ok(())
369    }
370
371    fn add_procedure(&mut self, proc_mast_root: Word) {
372        // Allow procedures with the same MAST root from different components, but only add them
373        // once.
374        let proc_root = AccountProcedureRoot::from_raw(proc_mast_root);
375        if !self.procedures.contains(&proc_root) {
376            self.procedures.push(proc_root);
377        }
378    }
379
380    fn build(self) -> Result<Vec<AccountProcedureRoot>, AccountError> {
381        if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
382            Err(AccountError::AccountCodeNoProcedures)
383        } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
384            Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
385        } else {
386            Ok(self.procedures)
387        }
388    }
389}
390
391// HELPER FUNCTIONS
392// ================================================================================================
393
394/// Computes the commitment to the given procedures
395pub(crate) fn build_procedure_commitment(procedures: &[AccountProcedureRoot]) -> Word {
396    let elements = procedures_as_elements(procedures);
397    Hasher::hash_elements(&elements)
398}
399
400/// Converts given procedures into field elements
401pub(crate) fn procedures_as_elements(procedures: &[AccountProcedureRoot]) -> Vec<Felt> {
402    procedures.iter().flat_map(AccountProcedureRoot::as_elements).copied().collect()
403}
404
405// TESTS
406// ================================================================================================
407
408#[cfg(test)]
409mod tests {
410
411    use assert_matches::assert_matches;
412    use miden_assembly::Assembler;
413
414    use super::{AccountCode, Deserializable, Serializable};
415    use crate::account::code::build_procedure_commitment;
416    use crate::account::component::AccountComponentMetadata;
417    use crate::account::{AccountComponent, AccountType};
418    use crate::errors::AccountError;
419    use crate::testing::account_code::CODE;
420    use crate::testing::noop_auth_component::NoopAuthComponent;
421
422    #[test]
423    fn test_serde_account_code() {
424        let code = AccountCode::mock();
425        let serialized = code.to_bytes();
426        let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
427        assert_eq!(deserialized, code)
428    }
429
430    #[test]
431    fn test_account_code_procedure_root() {
432        let code = AccountCode::mock();
433        let procedure_root = build_procedure_commitment(code.procedures());
434        assert_eq!(procedure_root, code.commitment())
435    }
436
437    #[test]
438    fn test_account_code_only_auth_component() {
439        let err = AccountCode::from_components(
440            &[NoopAuthComponent.into()],
441            AccountType::RegularAccountUpdatableCode,
442        )
443        .unwrap_err();
444
445        assert_matches!(err, AccountError::AccountCodeNoProcedures);
446    }
447
448    #[test]
449    fn test_account_code_no_auth_component() {
450        let library = Assembler::default().assemble_library([CODE]).unwrap();
451        let metadata = AccountComponentMetadata::new("test::no_auth", AccountType::all());
452        let component = AccountComponent::new(library, vec![], metadata).unwrap();
453
454        let err =
455            AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
456                .unwrap_err();
457
458        assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
459    }
460
461    #[test]
462    fn test_account_code_multiple_auth_components() {
463        let err = AccountCode::from_components(
464            &[NoopAuthComponent.into(), NoopAuthComponent.into()],
465            AccountType::RegularAccountUpdatableCode,
466        )
467        .unwrap_err();
468
469        assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
470    }
471
472    #[test]
473    fn test_account_component_multiple_auth_procedures() {
474        use miden_assembly::Assembler;
475
476        let code_with_multiple_auth = "
477            @auth_script
478            pub proc auth_basic
479                push.1 drop
480            end
481
482            @auth_script
483            pub proc auth_secondary
484                push.0 drop
485            end
486        ";
487
488        let library = Assembler::default().assemble_library([code_with_multiple_auth]).unwrap();
489        let metadata = AccountComponentMetadata::new("test::multiple_auth", AccountType::all());
490        let component = AccountComponent::new(library, vec![], metadata).unwrap();
491
492        let err =
493            AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
494                .unwrap_err();
495
496        assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
497    }
498}