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
35procedure_root!(
37 BASIC_WALLET_RECEIVE_ASSET,
38 BasicWallet::NAME,
39 BasicWallet::RECEIVE_ASSET_PROC_NAME,
40 BasicWallet::code()
41);
42
43procedure_root!(
46 BASIC_WALLET_MOVE_ASSET_TO_NOTE,
47 BasicWallet::NAME,
48 BasicWallet::MOVE_ASSET_TO_NOTE_PROC_NAME,
49 BasicWallet::code()
50);
51
52procedure_root!(
54 BASIC_WALLET_CREATE_NOTE,
55 BasicWallet::NAME,
56 BasicWallet::CREATE_NOTE_PROC_NAME,
57 BasicWallet::code()
58);
59
60pub struct BasicWallet;
76
77impl BasicWallet {
78 pub const NAME: &'static str = "miden::standards::components::wallets::basic_wallet";
83
84 const RECEIVE_ASSET_PROC_NAME: &str = "receive_asset";
85 const MOVE_ASSET_TO_NOTE_PROC_NAME: &str = "move_asset_to_note";
86 const CREATE_NOTE_PROC_NAME: &str = "create_note";
87
88 pub const fn name() -> AccountComponentName {
90 AccountComponentName::from_static_str(Self::NAME)
91 }
92
93 pub fn code() -> &'static AccountComponentCode {
98 &BASIC_WALLET_CODE
99 }
100
101 pub fn receive_asset_root() -> AccountProcedureRoot {
103 *BASIC_WALLET_RECEIVE_ASSET
104 }
105
106 pub fn move_asset_to_note_root() -> AccountProcedureRoot {
108 *BASIC_WALLET_MOVE_ASSET_TO_NOTE
109 }
110
111 pub fn create_note_root() -> AccountProcedureRoot {
113 *BASIC_WALLET_CREATE_NOTE
114 }
115
116 pub fn component_metadata() -> AccountComponentMetadata {
118 AccountComponentMetadata::new(Self::NAME)
119 .with_description("Basic wallet component for receiving and sending assets")
120 }
121}
122
123impl From<BasicWallet> for AccountComponent {
124 fn from(_: BasicWallet) -> Self {
125 let metadata = BasicWallet::component_metadata();
126
127 AccountComponent::new(BasicWallet::code().clone(), vec![], metadata).expect(
128 "basic wallet component should satisfy the requirements of a valid account component",
129 )
130 }
131}
132
133pub fn create_basic_wallet(
147 init_seed: [u8; 32],
148 approver: Approver,
149 account_type: AccountType,
150) -> Result<Account, AccountError> {
151 let auth_component: AccountComponent = AuthSingleSig::new(approver).into();
152
153 create_wallet(init_seed, auth_component, account_type)
154}
155
156pub fn create_multisig_wallet(
170 init_seed: [u8; 32],
171 approver_set: ApproverSet,
172 proc_thresholds: Vec<(AccountProcedureRoot, u32)>,
173 account_type: AccountType,
174) -> Result<Account, AccountError> {
175 let default_threshold = approver_set.threshold().get();
176 if account_type == AccountType::Private
177 && proc_thresholds
178 .iter()
179 .any(|(_, proc_threshold)| *proc_threshold < default_threshold)
180 {
181 return Err(AccountError::other(
182 "private multisig wallets do not allow per-procedure thresholds below the default \
183 threshold, as a lower threshold would let a sub-quorum advance and withhold the \
184 private account state; use a guarded wallet to lower thresholds safely",
185 ));
186 }
187
188 let config = AuthMultisigConfig::new(approver_set).with_proc_thresholds(proc_thresholds)?;
189 let auth_component: AccountComponent = AuthMultisig::new(config)?.into();
190
191 create_wallet(init_seed, auth_component, account_type)
192}
193
194pub fn create_guarded_wallet(
201 init_seed: [u8; 32],
202 approver_set: ApproverSet,
203 proc_thresholds: Vec<(AccountProcedureRoot, u32)>,
204 guardian: GuardianConfig,
205 account_type: AccountType,
206) -> Result<Account, AccountError> {
207 let config = AuthGuardedMultisigConfig::new(approver_set, guardian)?
208 .with_proc_thresholds(proc_thresholds)?;
209 let auth_component: AccountComponent = AuthGuardedMultisig::new(config)?.into();
210
211 create_wallet(init_seed, auth_component, account_type)
212}
213
214fn create_wallet(
216 init_seed: [u8; 32],
217 auth_component: AccountComponent,
218 account_type: AccountType,
219) -> Result<Account, AccountError> {
220 AccountBuilder::new(init_seed)
221 .account_type(account_type)
222 .with_auth_component(auth_component)
223 .with_component(BasicWallet)
224 .build()
225}
226
227#[cfg(test)]
231mod tests {
232 use alloc::string::ToString;
233
234 use miden_protocol::account::auth::{self, PublicKeyCommitment};
235 use miden_protocol::utils::serde::{Deserializable, Serializable};
236 use miden_protocol::{ONE, Word};
237
238 use super::{
239 Account,
240 AccountType,
241 Approver,
242 ApproverSet,
243 GuardianConfig,
244 create_basic_wallet,
245 create_guarded_wallet,
246 create_multisig_wallet,
247 };
248 use crate::account::wallets::BasicWallet;
249
250 fn approver(seed: u32) -> Approver {
251 Approver::new(
252 PublicKeyCommitment::from(Word::from([seed, seed, seed, seed])),
253 auth::AuthScheme::Falcon512Poseidon2,
254 )
255 }
256
257 #[test]
258 fn test_create_basic_wallet() -> anyhow::Result<()> {
259 create_basic_wallet([1; 32], approver(1), AccountType::Public)?;
260 Ok(())
261 }
262
263 #[test]
264 fn test_serialize_basic_wallet() -> anyhow::Result<()> {
265 let approver = Approver::new(
266 PublicKeyCommitment::from(Word::from([ONE; 4])),
267 auth::AuthScheme::EcdsaK256Keccak,
268 );
269 let wallet = create_basic_wallet([1; 32], approver, AccountType::Public)?;
270
271 let bytes = wallet.to_bytes();
272 let deserialized_wallet = Account::read_from_bytes(&bytes)?;
273 assert_eq!(wallet, deserialized_wallet);
274
275 Ok(())
276 }
277
278 #[test]
279 fn test_create_multisig_wallet_public_allows_lower_override() -> anyhow::Result<()> {
280 let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
281 let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
282
283 create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Public)?;
285
286 Ok(())
287 }
288
289 #[test]
290 fn test_create_multisig_wallet_private_no_override_succeeds() -> anyhow::Result<()> {
291 let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
292
293 create_multisig_wallet([1; 32], approver_set, vec![], AccountType::Private)?;
295
296 Ok(())
297 }
298
299 #[test]
300 fn test_create_multisig_wallet_private_higher_override_succeeds() -> anyhow::Result<()> {
301 let approver_set = ApproverSet::new(vec![approver(1), approver(2), approver(3)], 2)?;
302 let proc_thresholds = vec![(BasicWallet::move_asset_to_note_root(), 3)];
304
305 create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Private)?;
306
307 Ok(())
308 }
309
310 #[test]
311 fn test_create_multisig_wallet_private_lower_override_rejected() -> anyhow::Result<()> {
312 let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
313 let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
314
315 let err =
316 create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Private)
317 .expect_err("private multisig with a below-default threshold must be rejected");
318
319 assert!(
320 err.to_string()
321 .contains("do not allow per-procedure thresholds below the default threshold")
322 );
323
324 Ok(())
325 }
326
327 #[test]
328 fn test_create_guarded_wallet_private_override_allowed() -> anyhow::Result<()> {
329 let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
330 let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
331 let guardian = GuardianConfig::new(approver(3));
332
333 create_guarded_wallet(
335 [1; 32],
336 approver_set,
337 proc_thresholds,
338 guardian,
339 AccountType::Private,
340 )?;
341
342 Ok(())
343 }
344
345 #[test]
347 fn get_faucet_procedures() {
348 let _receive_asset_root = BasicWallet::receive_asset_root();
349 let _move_asset_to_note_root = BasicWallet::move_asset_to_note_root();
350 }
351}