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