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