Skip to main content

miden_standards/account/wallets/
mod.rs

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