Skip to main content

miden_standards/account/wallets/
mod.rs

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