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