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