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