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.read_many::<AccountProcedureRoot>(num_procedures)?;
287
288        Ok(Self::from_parts(module, procedures))
289    }
290}
291
292// PRETTY PRINT
293// ================================================================================================
294
295impl PrettyPrint for AccountCode {
296    fn render(&self) -> miden_core::prettier::Document {
297        use miden_core::prettier::*;
298        let mut partial = Document::Empty;
299        let len_procedures = self.num_procedures();
300
301        for (index, printable_procedure) in self.printable_procedures().enumerate() {
302            partial += indent(
303                0,
304                indent(
305                    4,
306                    text(format!("proc {}", printable_procedure.mast_root()))
307                        + nl()
308                        + printable_procedure.render(),
309                ) + nl()
310                    + const_text("end"),
311            );
312            if index < len_procedures - 1 {
313                partial += nl();
314            }
315        }
316        partial
317    }
318}
319
320// ACCOUNT PROCEDURE BUILDER
321// ================================================================================================
322
323/// A helper type for building the set of account procedures from account components.
324///
325/// In particular, this ensures that the auth procedure ends up at index 0.
326struct AccountProcedureBuilder {
327    procedures: Vec<AccountProcedureRoot>,
328}
329
330impl AccountProcedureBuilder {
331    fn new() -> Self {
332        Self { procedures: Vec::new() }
333    }
334
335    /// This method must be called before add_component is called.
336    fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
337        let mut auth_proc_count = 0;
338
339        for (proc_root, is_auth) in component.get_procedures() {
340            self.add_procedure(proc_root);
341
342            if is_auth {
343                let auth_proc_idx = self.procedures.len() - 1;
344                self.procedures.swap(0, auth_proc_idx);
345                auth_proc_count += 1;
346            }
347        }
348
349        if auth_proc_count == 0 {
350            return Err(AccountError::AccountCodeNoAuthComponent);
351        } else if auth_proc_count > 1 {
352            return Err(AccountError::AccountComponentMultipleAuthProcedures);
353        }
354
355        Ok(())
356    }
357
358    fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
359        for (proc_mast_root, is_auth) in component.get_procedures() {
360            if is_auth {
361                return Err(AccountError::AccountCodeMultipleAuthComponents);
362            }
363            self.add_procedure(proc_mast_root);
364        }
365
366        Ok(())
367    }
368
369    fn add_procedure(&mut self, proc_mast_root: Word) {
370        // Allow procedures with the same MAST root from different components, but only add them
371        // once.
372        let proc_root = AccountProcedureRoot::from_raw(proc_mast_root);
373        if !self.procedures.contains(&proc_root) {
374            self.procedures.push(proc_root);
375        }
376    }
377
378    fn build(self) -> Result<Vec<AccountProcedureRoot>, AccountError> {
379        if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
380            Err(AccountError::AccountCodeNoProcedures)
381        } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
382            Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
383        } else {
384            Ok(self.procedures)
385        }
386    }
387}
388
389// HELPER FUNCTIONS
390// ================================================================================================
391
392/// Computes the commitment to the given procedures
393pub(crate) fn build_procedure_commitment(procedures: &[AccountProcedureRoot]) -> Word {
394    let elements = procedures_as_elements(procedures);
395    Hasher::hash_elements(&elements)
396}
397
398/// Converts given procedures into field elements
399pub(crate) fn procedures_as_elements(procedures: &[AccountProcedureRoot]) -> Vec<Felt> {
400    procedures.iter().flat_map(AccountProcedureRoot::as_elements).copied().collect()
401}
402
403// TESTS
404// ================================================================================================
405
406#[cfg(test)]
407mod tests {
408
409    use assert_matches::assert_matches;
410    use miden_assembly::Assembler;
411
412    use super::{AccountCode, Deserializable, Serializable};
413    use crate::account::code::build_procedure_commitment;
414    use crate::account::{AccountComponent, AccountType};
415    use crate::errors::AccountError;
416    use crate::testing::account_code::CODE;
417    use crate::testing::noop_auth_component::NoopAuthComponent;
418
419    #[test]
420    fn test_serde_account_code() {
421        let code = AccountCode::mock();
422        let serialized = code.to_bytes();
423        let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
424        assert_eq!(deserialized, code)
425    }
426
427    #[test]
428    fn test_account_code_procedure_root() {
429        let code = AccountCode::mock();
430        let procedure_root = build_procedure_commitment(code.procedures());
431        assert_eq!(procedure_root, code.commitment())
432    }
433
434    #[test]
435    fn test_account_code_only_auth_component() {
436        let err = AccountCode::from_components(
437            &[NoopAuthComponent.into()],
438            AccountType::RegularAccountUpdatableCode,
439        )
440        .unwrap_err();
441
442        assert_matches!(err, AccountError::AccountCodeNoProcedures);
443    }
444
445    #[test]
446    fn test_account_code_no_auth_component() {
447        let library = Assembler::default().assemble_library([CODE]).unwrap();
448        let component = AccountComponent::new(library, vec![]).unwrap().with_supports_all_types();
449
450        let err =
451            AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
452                .unwrap_err();
453
454        assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
455    }
456
457    #[test]
458    fn test_account_code_multiple_auth_components() {
459        let err = AccountCode::from_components(
460            &[NoopAuthComponent.into(), NoopAuthComponent.into()],
461            AccountType::RegularAccountUpdatableCode,
462        )
463        .unwrap_err();
464
465        assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
466    }
467
468    #[test]
469    fn test_account_component_multiple_auth_procedures() {
470        use miden_assembly::Assembler;
471
472        let code_with_multiple_auth = "
473            pub proc auth_basic
474                push.1 drop
475            end
476
477            pub proc auth_secondary
478                push.0 drop
479            end
480        ";
481
482        let library = Assembler::default().assemble_library([code_with_multiple_auth]).unwrap();
483        let component = AccountComponent::new(library, vec![]).unwrap().with_supports_all_types();
484
485        let err =
486            AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
487                .unwrap_err();
488
489        assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
490    }
491}