Skip to main content

miden_standards/account/wallets/
mod.rs

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