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