Skip to main content

miden_standards/account/interface/
component.rs

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