miden_lib/account/wallets/
mod.rs

1use alloc::string::String;
2
3use miden_objects::account::{
4    Account,
5    AccountBuilder,
6    AccountComponent,
7    AccountStorageMode,
8    AccountType,
9};
10use miden_objects::assembly::{ProcedureName, QualifiedProcedureName};
11use miden_objects::utils::sync::LazyLock;
12use miden_objects::{AccountError, Word};
13use thiserror::Error;
14
15use super::AuthScheme;
16use crate::account::auth::{AuthRpoFalcon512, AuthRpoFalcon512Multisig};
17use crate::account::components::basic_wallet_library;
18
19// BASIC WALLET
20// ================================================================================================
21
22// Initialize the digest of the `receive_asset` procedure of the Basic Wallet only once.
23static BASIC_WALLET_RECEIVE_ASSET: LazyLock<Word> = LazyLock::new(|| {
24    let receive_asset_proc_name = QualifiedProcedureName::new(
25        Default::default(),
26        ProcedureName::new(BasicWallet::RECEIVE_ASSET_PROC_NAME)
27            .expect("failed to create name for 'receive_asset' procedure"),
28    );
29    basic_wallet_library()
30        .get_procedure_root_by_name(receive_asset_proc_name)
31        .expect("Basic Wallet should contain 'receive_asset' procedure")
32});
33
34// Initialize the digest of the `move_asset_to_note` procedure of the Basic Wallet only once.
35static BASIC_WALLET_MOVE_ASSET_TO_NOTE: LazyLock<Word> = LazyLock::new(|| {
36    let move_asset_to_note_proc_name = QualifiedProcedureName::new(
37        Default::default(),
38        ProcedureName::new(BasicWallet::MOVE_ASSET_TO_NOTE_PROC_NAME)
39            .expect("failed to create name for 'move_asset_to_note' procedure"),
40    );
41    basic_wallet_library()
42        .get_procedure_root_by_name(move_asset_to_note_proc_name)
43        .expect("Basic Wallet should contain 'move_asset_to_note' procedure")
44});
45
46/// An [`AccountComponent`] implementing a basic wallet.
47///
48/// It reexports the procedures from `miden::contracts::wallets::basic`. When linking against this
49/// component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be available to the
50/// assembler which is the case when using [`TransactionKernel::assembler()`][kasm]. The procedures
51/// of this component are:
52/// - `receive_asset`, which can be used to add an asset to the account.
53/// - `move_asset_to_note`, which can be used to remove the specified asset from the account and add
54///   it to the output note with the specified index.
55///
56/// All methods require authentication. Thus, this component must be combined with a component
57/// providing authentication.
58///
59/// This component supports all account types.
60///
61/// [kasm]: crate::transaction::TransactionKernel::assembler
62pub struct BasicWallet;
63
64impl BasicWallet {
65    // CONSTANTS
66    // --------------------------------------------------------------------------------------------
67    const RECEIVE_ASSET_PROC_NAME: &str = "receive_asset";
68    const MOVE_ASSET_TO_NOTE_PROC_NAME: &str = "move_asset_to_note";
69
70    // PUBLIC ACCESSORS
71    // --------------------------------------------------------------------------------------------
72
73    /// Returns the digest of the `receive_asset` wallet procedure.
74    pub fn receive_asset_digest() -> Word {
75        *BASIC_WALLET_RECEIVE_ASSET
76    }
77
78    /// Returns the digest of the `move_asset_to_note` wallet procedure.
79    pub fn move_asset_to_note_digest() -> Word {
80        *BASIC_WALLET_MOVE_ASSET_TO_NOTE
81    }
82}
83
84impl From<BasicWallet> for AccountComponent {
85    fn from(_: BasicWallet) -> Self {
86        AccountComponent::new(basic_wallet_library(), vec![])
87          .expect("basic wallet component should satisfy the requirements of a valid account component")
88          .with_supports_all_types()
89    }
90}
91
92// BASIC WALLET ERROR
93// ================================================================================================
94
95/// Basic wallet related errors.
96#[derive(Debug, Error)]
97pub enum BasicWalletError {
98    #[error("unsupported authentication scheme: {0}")]
99    UnsupportedAuthScheme(String),
100    #[error("account creation failed")]
101    AccountError(#[source] AccountError),
102}
103
104/// Creates a new account with basic wallet interface, the specified authentication scheme and the
105/// account storage type. Basic wallets can be specified to have either mutable or immutable code.
106///
107/// The basic wallet interface exposes three procedures:
108/// - `receive_asset`, which can be used to add an asset to the account.
109/// - `move_asset_to_note`, which can be used to remove the specified asset from the account and add
110///   it to the output note with the specified index.
111///
112/// All methods require authentication. The authentication procedure is defined by the specified
113/// authentication scheme.
114pub fn create_basic_wallet(
115    init_seed: [u8; 32],
116    auth_scheme: AuthScheme,
117    account_type: AccountType,
118    account_storage_mode: AccountStorageMode,
119) -> Result<(Account, Word), BasicWalletError> {
120    if matches!(account_type, AccountType::FungibleFaucet | AccountType::NonFungibleFaucet) {
121        return Err(BasicWalletError::AccountError(AccountError::other(
122            "basic wallet accounts cannot have a faucet account type",
123        )));
124    }
125
126    let auth_component: AccountComponent = match auth_scheme {
127        AuthScheme::RpoFalcon512 { pub_key } => AuthRpoFalcon512::new(pub_key).into(),
128        AuthScheme::RpoFalcon512Multisig { threshold, pub_keys } => {
129            AuthRpoFalcon512Multisig::new(threshold, pub_keys)
130                .map_err(BasicWalletError::AccountError)?
131                .into()
132        },
133        AuthScheme::NoAuth => {
134            return Err(BasicWalletError::UnsupportedAuthScheme(
135                "basic wallets cannot be created with NoAuth authentication scheme".into(),
136            ));
137        },
138        AuthScheme::Unknown => {
139            return Err(BasicWalletError::UnsupportedAuthScheme(
140                "basic wallets cannot be created with Unknown authentication scheme".into(),
141            ));
142        },
143    };
144
145    let (account, account_seed) = AccountBuilder::new(init_seed)
146        .account_type(account_type)
147        .storage_mode(account_storage_mode)
148        .with_auth_component(auth_component)
149        .with_component(BasicWallet)
150        .build()
151        .map_err(BasicWalletError::AccountError)?;
152
153    Ok((account, account_seed))
154}
155
156// TESTS
157// ================================================================================================
158
159#[cfg(test)]
160mod tests {
161
162    use miden_objects::crypto::dsa::rpo_falcon512;
163    use miden_objects::{ONE, Word};
164    use miden_processor::utils::{Deserializable, Serializable};
165
166    use super::{Account, AccountStorageMode, AccountType, AuthScheme, create_basic_wallet};
167    use crate::account::wallets::BasicWallet;
168
169    #[test]
170    fn test_create_basic_wallet() {
171        let pub_key = rpo_falcon512::PublicKey::new(Word::from([ONE; 4]));
172        let wallet = create_basic_wallet(
173            [1; 32],
174            AuthScheme::RpoFalcon512 { pub_key },
175            AccountType::RegularAccountImmutableCode,
176            AccountStorageMode::Public,
177        );
178
179        wallet.unwrap_or_else(|err| {
180            panic!("{}", err);
181        });
182    }
183
184    #[test]
185    fn test_serialize_basic_wallet() {
186        let pub_key = rpo_falcon512::PublicKey::new(Word::from([ONE; 4]));
187        let wallet = create_basic_wallet(
188            [1; 32],
189            AuthScheme::RpoFalcon512 { pub_key },
190            AccountType::RegularAccountImmutableCode,
191            AccountStorageMode::Public,
192        )
193        .unwrap()
194        .0;
195
196        let bytes = wallet.to_bytes();
197        let deserialized_wallet = Account::read_from_bytes(&bytes).unwrap();
198        assert_eq!(wallet, deserialized_wallet);
199    }
200
201    /// Check that the obtaining of the basic wallet procedure digests does not panic.
202    #[test]
203    fn get_faucet_procedures() {
204        let _receive_asset_digest = BasicWallet::receive_asset_digest();
205        let _move_asset_to_note_digest = BasicWallet::move_asset_to_note_digest();
206    }
207}