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