miden_lib/account/interface/
component.rs

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