miden_standards/account/wallets/
mod.rs1use alloc::vec::Vec;
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;
13
14use crate::account::account_component_code;
15use crate::account::auth::{
16 Approver,
17 ApproverSet,
18 AuthGuardedMultisig,
19 AuthGuardedMultisigConfig,
20 AuthMultisig,
21 AuthMultisigConfig,
22 AuthSingleSig,
23 GuardianConfig,
24};
25use crate::procedure_root;
26
27mod note_creator;
28pub use note_creator::NoteCreator;
29
30account_component_code!(BASIC_WALLET_CODE, "miden-standards-wallets-basic-wallet.masp");
34
35const BASIC_WALLET_LIBRARY_PATH: &str = "miden::standards::components::wallets::basic_wallet";
41
42procedure_root!(
44 BASIC_WALLET_RECEIVE_ASSET,
45 BASIC_WALLET_LIBRARY_PATH,
46 BasicWallet::RECEIVE_ASSET_PROC_NAME,
47 BasicWallet::code()
48);
49
50procedure_root!(
53 BASIC_WALLET_MOVE_ASSET_TO_NOTE,
54 BASIC_WALLET_LIBRARY_PATH,
55 BasicWallet::MOVE_ASSET_TO_NOTE_PROC_NAME,
56 BasicWallet::code()
57);
58
59procedure_root!(
61 BASIC_WALLET_CREATE_NOTE,
62 BASIC_WALLET_LIBRARY_PATH,
63 BasicWallet::CREATE_NOTE_PROC_NAME,
64 BasicWallet::code()
65);
66
67pub struct BasicWallet;
83
84impl BasicWallet {
85 pub const NAME: &'static str = "miden::standards::wallets::basic_wallet";
90
91 const RECEIVE_ASSET_PROC_NAME: &str = "receive_asset";
92 const MOVE_ASSET_TO_NOTE_PROC_NAME: &str = "move_asset_to_note";
93 const CREATE_NOTE_PROC_NAME: &str = "create_note";
94
95 pub const fn name() -> AccountComponentName {
97 AccountComponentName::from_static_str(Self::NAME)
98 }
99
100 pub fn code() -> &'static AccountComponentCode {
105 &BASIC_WALLET_CODE
106 }
107
108 pub fn receive_asset_root() -> AccountProcedureRoot {
110 *BASIC_WALLET_RECEIVE_ASSET
111 }
112
113 pub fn move_asset_to_note_root() -> AccountProcedureRoot {
115 *BASIC_WALLET_MOVE_ASSET_TO_NOTE
116 }
117
118 pub fn create_note_root() -> AccountProcedureRoot {
120 *BASIC_WALLET_CREATE_NOTE
121 }
122
123 pub fn component_metadata() -> AccountComponentMetadata {
125 AccountComponentMetadata::new(Self::NAME)
126 .with_description("Basic wallet component for receiving and sending assets")
127 }
128}
129
130impl From<BasicWallet> for AccountComponent {
131 fn from(_: BasicWallet) -> Self {
132 let metadata = BasicWallet::component_metadata();
133
134 AccountComponent::new(BasicWallet::code().clone(), vec![], metadata).expect(
135 "basic wallet component should satisfy the requirements of a valid account component",
136 )
137 }
138}
139
140pub fn create_basic_wallet(
154 init_seed: [u8; 32],
155 approver: Approver,
156 account_type: AccountType,
157) -> Result<Account, AccountError> {
158 let auth_component: AccountComponent = AuthSingleSig::new(approver).into();
159
160 create_wallet(init_seed, auth_component, account_type)
161}
162
163pub fn create_multisig_wallet(
177 init_seed: [u8; 32],
178 approver_set: ApproverSet,
179 proc_thresholds: Vec<(AccountProcedureRoot, u32)>,
180 account_type: AccountType,
181) -> Result<Account, AccountError> {
182 let default_threshold = approver_set.threshold().get();
183 if account_type == AccountType::Private
184 && proc_thresholds
185 .iter()
186 .any(|(_, proc_threshold)| *proc_threshold < default_threshold)
187 {
188 return Err(AccountError::other(
189 "private multisig wallets do not allow per-procedure thresholds below the default \
190 threshold, as a lower threshold would let a sub-quorum advance and withhold the \
191 private account state; use a guarded wallet to lower thresholds safely",
192 ));
193 }
194
195 let config = AuthMultisigConfig::new(approver_set).with_proc_thresholds(proc_thresholds)?;
196 let auth_component: AccountComponent = AuthMultisig::new(config)?.into();
197
198 create_wallet(init_seed, auth_component, account_type)
199}
200
201pub fn create_guarded_wallet(
208 init_seed: [u8; 32],
209 approver_set: ApproverSet,
210 proc_thresholds: Vec<(AccountProcedureRoot, u32)>,
211 guardian: GuardianConfig,
212 account_type: AccountType,
213) -> Result<Account, AccountError> {
214 let config = AuthGuardedMultisigConfig::new(approver_set, guardian)?
215 .with_proc_thresholds(proc_thresholds)?;
216 let auth_component: AccountComponent = AuthGuardedMultisig::new(config)?.into();
217
218 create_wallet(init_seed, auth_component, account_type)
219}
220
221fn create_wallet(
223 init_seed: [u8; 32],
224 auth_component: AccountComponent,
225 account_type: AccountType,
226) -> Result<Account, AccountError> {
227 AccountBuilder::new(init_seed)
228 .account_type(account_type)
229 .with_component(auth_component)
230 .with_component(BasicWallet)
231 .build()
232}
233
234#[cfg(test)]
238mod tests {
239 use alloc::string::ToString;
240
241 use miden_protocol::account::auth::{self, PublicKeyCommitment};
242 use miden_protocol::utils::serde::{Deserializable, Serializable};
243 use miden_protocol::{ONE, Word};
244
245 use super::{
246 Account,
247 AccountType,
248 Approver,
249 ApproverSet,
250 AuthMultisig,
251 GuardianConfig,
252 create_basic_wallet,
253 create_guarded_wallet,
254 create_multisig_wallet,
255 };
256 use crate::account::wallets::BasicWallet;
257
258 fn approver(seed: u32) -> Approver {
259 Approver::new(
260 PublicKeyCommitment::from(Word::from([seed, seed, seed, seed])),
261 auth::AuthScheme::Falcon512Poseidon2,
262 )
263 }
264
265 #[test]
266 fn test_create_basic_wallet() -> anyhow::Result<()> {
267 create_basic_wallet([1; 32], approver(1), AccountType::Public)?;
268 Ok(())
269 }
270
271 #[test]
272 fn test_serialize_basic_wallet() -> anyhow::Result<()> {
273 let approver = Approver::new(
274 PublicKeyCommitment::from(Word::from([ONE; 4])),
275 auth::AuthScheme::EcdsaK256Keccak,
276 );
277 let wallet = create_basic_wallet([1; 32], approver, AccountType::Public)?;
278
279 let bytes = wallet.to_bytes();
280 let deserialized_wallet = Account::read_from_bytes(&bytes)?;
281 assert_eq!(wallet, deserialized_wallet);
282
283 Ok(())
284 }
285
286 #[test]
287 fn test_create_multisig_wallet_public_allows_lower_override() -> anyhow::Result<()> {
288 let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
289 let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
290
291 create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Public)?;
293
294 Ok(())
295 }
296
297 #[test]
298 fn test_create_multisig_wallet_private_no_override_succeeds() -> anyhow::Result<()> {
299 let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
300
301 create_multisig_wallet([1; 32], approver_set, vec![], AccountType::Private)?;
303
304 Ok(())
305 }
306
307 #[test]
308 fn test_create_multisig_wallet_private_higher_override_succeeds() -> anyhow::Result<()> {
309 let approver_set = ApproverSet::new(vec![approver(1), approver(2), approver(3)], 2)?;
310 let proc_thresholds = vec![
314 (BasicWallet::move_asset_to_note_root(), 3),
315 (AuthMultisig::set_procedure_threshold_root(), 3),
316 ];
317
318 create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Private)?;
319
320 Ok(())
321 }
322
323 #[test]
324 fn test_create_multisig_wallet_private_lower_override_rejected() -> anyhow::Result<()> {
325 let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
326 let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
327
328 let err =
329 create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Private)
330 .expect_err("private multisig with a below-default threshold must be rejected");
331
332 assert!(
333 err.to_string()
334 .contains("do not allow per-procedure thresholds below the default threshold")
335 );
336
337 Ok(())
338 }
339
340 #[test]
341 fn test_create_guarded_wallet_private_override_allowed() -> anyhow::Result<()> {
342 let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
343 let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
344 let guardian = GuardianConfig::new(approver(3));
345
346 create_guarded_wallet(
348 [1; 32],
349 approver_set,
350 proc_thresholds,
351 guardian,
352 AccountType::Private,
353 )?;
354
355 Ok(())
356 }
357
358 #[test]
360 fn get_faucet_procedures() {
361 let _receive_asset_root = BasicWallet::receive_asset_root();
362 let _move_asset_to_note_root = BasicWallet::move_asset_to_note_root();
363 }
364}