miden_lib/account/interface/
component.rs

1use alloc::collections::BTreeMap;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4
5use miden_objects::account::{AccountId, AccountProcedureInfo, AccountStorage};
6use miden_objects::crypto::dsa::rpo_falcon512::PublicKey;
7use miden_objects::note::PartialNote;
8use miden_objects::{Felt, FieldElement, Word};
9
10use crate::AuthScheme;
11use crate::account::components::WellKnownComponent;
12use crate::account::interface::AccountInterfaceError;
13
14// ACCOUNT COMPONENT INTERFACE
15// ================================================================================================
16
17/// The enum holding all possible account interfaces which could be loaded to some account.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum AccountComponentInterface {
20    /// Exposes procedures from the [`BasicWallet`][crate::account::wallets::BasicWallet] module.
21    BasicWallet,
22    /// Exposes procedures from the
23    /// [`BasicFungibleFaucet`][crate::account::faucets::BasicFungibleFaucet] module.
24    ///
25    /// Internal value holds the storage slot index where faucet metadata is stored. This metadata
26    /// slot has a format of `[max_supply, faucet_decimals, token_symbol, 0]`.
27    BasicFungibleFaucet(u8),
28    /// Exposes procedures from the
29    /// [`AuthRpoFalcon512`][crate::account::auth::AuthRpoFalcon512] module.
30    ///
31    /// Internal value holds the storage slot index where the public key for the RpoFalcon512
32    /// authentication scheme is stored.
33    AuthRpoFalcon512(u8),
34    /// Exposes procedures from the
35    /// [`AuthRpoFalcon512Acl`][crate::account::auth::AuthRpoFalcon512Acl] module.
36    ///
37    /// Internal value holds the storage slot index where the public key for the RpoFalcon512
38    /// authentication scheme is stored.
39    AuthRpoFalcon512Acl(u8),
40    /// Exposes procedures from the multisig RpoFalcon512 authentication module.
41    ///
42    /// Internal value holds the storage slot index where the multisig configuration is stored.
43    AuthRpoFalcon512Multisig(u8),
44    /// Exposes procedures from the [`NoAuth`][crate::account::auth::NoAuth] module.
45    ///
46    /// This authentication scheme provides no cryptographic authentication and only increments
47    /// the nonce if the account state has actually changed during transaction execution.
48    AuthNoAuth,
49    /// A non-standard, custom interface which exposes the contained procedures.
50    ///
51    /// Custom interface holds procedures which are not part of some standard interface which is
52    /// used by this account. Each custom interface holds procedures with the same storage offset.
53    Custom(Vec<AccountProcedureInfo>),
54}
55
56impl AccountComponentInterface {
57    /// Returns a string line with the name of the [AccountComponentInterface] enum variant.
58    ///
59    /// In case of a [AccountComponentInterface::Custom] along with the name of the enum variant
60    /// the vector of shortened hex representations of the used procedures is returned, e.g.
61    /// `Custom([0x6d93447, 0x0bf23d8])`.
62    pub fn name(&self) -> String {
63        match self {
64            AccountComponentInterface::BasicWallet => "Basic Wallet".to_string(),
65            AccountComponentInterface::BasicFungibleFaucet(_) => {
66                "Basic Fungible Faucet".to_string()
67            },
68            AccountComponentInterface::AuthRpoFalcon512(_) => "RPO Falcon512".to_string(),
69            AccountComponentInterface::AuthRpoFalcon512Acl(_) => "RPO Falcon512 ACL".to_string(),
70            AccountComponentInterface::AuthRpoFalcon512Multisig(_) => {
71                "RPO Falcon512 Multisig".to_string()
72            },
73            AccountComponentInterface::AuthNoAuth => "No Auth".to_string(),
74            AccountComponentInterface::Custom(proc_info_vec) => {
75                let result = proc_info_vec
76                    .iter()
77                    .map(|proc_info| proc_info.mast_root().to_hex()[..9].to_string())
78                    .collect::<Vec<_>>()
79                    .join(", ");
80                format!("Custom([{result}])")
81            },
82        }
83    }
84
85    /// Returns true if this component interface is an authentication component.
86    ///
87    /// TODO: currently this can identify only standard auth components
88    pub fn is_auth_component(&self) -> bool {
89        matches!(
90            self,
91            AccountComponentInterface::AuthRpoFalcon512(_)
92                | AccountComponentInterface::AuthRpoFalcon512Acl(_)
93                | AccountComponentInterface::AuthRpoFalcon512Multisig(_)
94                | AccountComponentInterface::AuthNoAuth
95        )
96    }
97
98    /// Returns the authentication schemes associated with this component interface.
99    pub fn get_auth_schemes(&self, storage: &AccountStorage) -> Vec<AuthScheme> {
100        match self {
101            AccountComponentInterface::AuthRpoFalcon512(storage_index)
102            | AccountComponentInterface::AuthRpoFalcon512Acl(storage_index) => {
103                vec![AuthScheme::RpoFalcon512 {
104                    pub_key: PublicKey::new(
105                        storage
106                            .get_item(*storage_index)
107                            .expect("invalid storage index of the public key"),
108                    ),
109                }]
110            },
111            AccountComponentInterface::AuthRpoFalcon512Multisig(storage_index) => {
112                vec![extract_multisig_auth_scheme(storage, *storage_index)]
113            },
114            AccountComponentInterface::AuthNoAuth => vec![AuthScheme::NoAuth],
115            _ => vec![], // Non-auth components return empty vector
116        }
117    }
118
119    /// Creates a vector of [AccountComponentInterface] instances. This vector specifies the
120    /// components which were used to create an account with the provided procedures info array.
121    pub fn from_procedures(procedures: &[AccountProcedureInfo]) -> Vec<Self> {
122        let mut component_interface_vec = Vec::new();
123
124        let mut procedures: BTreeMap<_, _> = procedures
125            .iter()
126            .map(|procedure_info| (*procedure_info.mast_root(), procedure_info))
127            .collect();
128
129        // Well known component interfaces
130        // ----------------------------------------------------------------------------------------
131
132        // Get all available well known components which could be constructed from the `procedures`
133        // map and push them to the `component_interface_vec`
134        WellKnownComponent::extract_well_known_components(
135            &mut procedures,
136            &mut component_interface_vec,
137        );
138
139        // Custom component interfaces
140        // ----------------------------------------------------------------------------------------
141
142        let mut custom_interface_procs_map = BTreeMap::<u8, Vec<AccountProcedureInfo>>::new();
143        procedures.into_iter().for_each(|(_, proc_info)| {
144            match custom_interface_procs_map.get_mut(&proc_info.storage_offset()) {
145                Some(proc_vec) => proc_vec.push(*proc_info),
146                None => {
147                    custom_interface_procs_map.insert(proc_info.storage_offset(), vec![*proc_info]);
148                },
149            }
150        });
151
152        if !custom_interface_procs_map.is_empty() {
153            for proc_vec in custom_interface_procs_map.into_values() {
154                component_interface_vec.push(AccountComponentInterface::Custom(proc_vec));
155            }
156        }
157
158        component_interface_vec
159    }
160
161    /// Generates a body for the note creation of the `send_note` transaction script. The resulting
162    /// code could use different procedures for note creation, which depends on the used interface.
163    ///
164    /// The body consists of two sections:
165    /// - Pushing the note information on the stack.
166    /// - Creating a note:
167    ///   - For basic fungible faucet: pushing the amount of assets and distributing them.
168    ///   - For basic wallet: creating a note, pushing the assets on the stack and moving them to
169    ///     the created note.
170    ///
171    /// # Examples
172    ///
173    /// Example script for the [`AccountComponentInterface::BasicWallet`] with one note:
174    ///
175    /// ```masm
176    ///     push.{note_information}
177    ///     call.::miden::tx::create_note
178    ///
179    ///     push.{note asset}
180    ///     call.::miden::contracts::wallets::basic::move_asset_to_note dropw
181    ///     dropw dropw dropw drop
182    /// ```
183    ///
184    /// Example script for the [`AccountComponentInterface::BasicFungibleFaucet`] with one note:
185    ///
186    /// ```masm
187    ///     push.{note information}
188    ///
189    ///     push.{asset amount}
190    ///     call.::miden::contracts::faucets::basic_fungible::distribute dropw dropw drop
191    /// ```
192    ///
193    /// # Errors:
194    /// Returns an error if:
195    /// - the interface does not support the generation of the standard `send_note` procedure.
196    /// - the sender of the note isn't the account for which the script is being built.
197    /// - the note created by the faucet doesn't contain exactly one asset.
198    /// - a faucet tries to distribute an asset with a different faucet ID.
199    pub(crate) fn send_note_body(
200        &self,
201        sender_account_id: AccountId,
202        notes: &[PartialNote],
203    ) -> Result<String, AccountInterfaceError> {
204        let mut body = String::new();
205
206        for partial_note in notes {
207            if partial_note.metadata().sender() != sender_account_id {
208                return Err(AccountInterfaceError::InvalidSenderAccount(
209                    partial_note.metadata().sender(),
210                ));
211            }
212
213            body.push_str(&format!(
214                "push.{recipient}
215                push.{execution_hint}
216                push.{note_type}
217                push.{aux}
218                push.{tag}\n",
219                recipient = partial_note.recipient_digest(),
220                note_type = Felt::from(partial_note.metadata().note_type()),
221                execution_hint = Felt::from(partial_note.metadata().execution_hint()),
222                aux = partial_note.metadata().aux(),
223                tag = Felt::from(partial_note.metadata().tag()),
224            ));
225            // stack => [tag, aux, note_type, execution_hint, RECIPIENT]
226
227            match self {
228                AccountComponentInterface::BasicFungibleFaucet(_) => {
229                    if partial_note.assets().num_assets() != 1 {
230                        return Err(AccountInterfaceError::FaucetNoteWithoutAsset);
231                    }
232
233                    // SAFETY: We checked that the note contains exactly one asset
234                    let asset =
235                        partial_note.assets().iter().next().expect("note should contain an asset");
236
237                    if asset.faucet_id_prefix() != sender_account_id.prefix() {
238                        return Err(AccountInterfaceError::IssuanceFaucetMismatch(
239                            asset.faucet_id_prefix(),
240                        ));
241                    }
242
243                    body.push_str(&format!(
244                        "push.{amount}
245                        call.::miden::contracts::faucets::basic_fungible::distribute dropw dropw drop\n",
246                        amount = asset.unwrap_fungible().amount()
247                    ));
248                    // stack => []
249                },
250                AccountComponentInterface::BasicWallet => {
251                    body.push_str("call.::miden::tx::create_note\n");
252                    // stack => [note_idx]
253
254                    for asset in partial_note.assets().iter() {
255                        body.push_str(&format!(
256                            "push.{asset}
257                            call.::miden::contracts::wallets::basic::move_asset_to_note dropw\n",
258                            asset = Word::from(*asset)
259                        ));
260                        // stack => [note_idx]
261                    }
262
263                    body.push_str("dropw dropw dropw drop\n");
264                    // stack => []
265                },
266                _ => {
267                    return Err(AccountInterfaceError::UnsupportedInterface {
268                        interface: self.clone(),
269                    });
270                },
271            }
272        }
273
274        Ok(body)
275    }
276}
277
278// HELPER FUNCTIONS
279// ================================================================================================
280
281/// Extracts authentication scheme from a multisig component.
282fn extract_multisig_auth_scheme(storage: &AccountStorage, storage_index: u8) -> AuthScheme {
283    // Read the multisig configuration from the config slot
284    // Format: [threshold, num_approvers, 0, 0]
285    let config = storage
286        .get_item(storage_index)
287        .expect("invalid storage index of the multisig configuration");
288
289    let threshold = config[0].as_int() as u32;
290    let num_approvers = config[1].as_int() as u8;
291
292    // The public keys are stored in a map at the next slot (storage_index + 1)
293    let pub_keys_map_slot = storage_index + 1;
294
295    let mut pub_keys = Vec::new();
296
297    // Read each public key from the map
298    for key_index in 0..num_approvers {
299        let map_key = [Felt::new(key_index as u64), Felt::ZERO, Felt::ZERO, Felt::ZERO];
300
301        match storage.get_map_item(pub_keys_map_slot, map_key.into()) {
302            Ok(pub_key_word) => {
303                pub_keys.push(PublicKey::new(pub_key_word));
304            },
305            Err(_) => {
306                // If we can't read a public key, panic with a clear error message
307                panic!(
308                    "Failed to read public key {} from multisig configuration at storage index {}. \
309                        This indicates corrupted multisig storage or incorrect storage layout.",
310                    key_index, storage_index
311                );
312            },
313        }
314    }
315
316    AuthScheme::RpoFalcon512Multisig { threshold, pub_keys }
317}