1use alloc::vec::Vec;
2
3use miden_objects::account::{AccountCode, AccountComponent, StorageMap, StorageSlot};
4use miden_objects::crypto::dsa::rpo_falcon512::PublicKey;
5use miden_objects::{AccountError, Word};
6
7use crate::account::components::{
8 multisig_library,
9 no_auth_library,
10 rpo_falcon_512_acl_library,
11 rpo_falcon_512_library,
12};
13
14pub struct AuthRpoFalcon512 {
28 public_key: PublicKey,
29}
30
31impl AuthRpoFalcon512 {
32 pub fn new(public_key: PublicKey) -> Self {
34 Self { public_key }
35 }
36}
37
38impl From<AuthRpoFalcon512> for AccountComponent {
39 fn from(falcon: AuthRpoFalcon512) -> Self {
40 AccountComponent::new(
41 rpo_falcon_512_library(),
42 vec![StorageSlot::Value(falcon.public_key.into())],
43 )
44 .expect("falcon component should satisfy the requirements of a valid account component")
45 .with_supports_all_types()
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct AuthRpoFalcon512AclConfig {
52 pub auth_trigger_procedures: Vec<Word>,
54 pub allow_unauthorized_output_notes: bool,
57 pub allow_unauthorized_input_notes: bool,
60}
61
62impl AuthRpoFalcon512AclConfig {
63 pub fn new() -> Self {
66 Self {
67 auth_trigger_procedures: vec![],
68 allow_unauthorized_output_notes: false,
69 allow_unauthorized_input_notes: false,
70 }
71 }
72
73 pub fn with_auth_trigger_procedures(mut self, procedures: Vec<Word>) -> Self {
75 self.auth_trigger_procedures = procedures;
76 self
77 }
78
79 pub fn with_allow_unauthorized_output_notes(mut self, allow: bool) -> Self {
81 self.allow_unauthorized_output_notes = allow;
82 self
83 }
84
85 pub fn with_allow_unauthorized_input_notes(mut self, allow: bool) -> Self {
87 self.allow_unauthorized_input_notes = allow;
88 self
89 }
90}
91
92impl Default for AuthRpoFalcon512AclConfig {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98pub struct AuthRpoFalcon512Acl {
153 public_key: PublicKey,
154 config: AuthRpoFalcon512AclConfig,
155}
156
157impl AuthRpoFalcon512Acl {
158 pub fn new(
164 public_key: PublicKey,
165 config: AuthRpoFalcon512AclConfig,
166 ) -> Result<Self, AccountError> {
167 let max_procedures = AccountCode::MAX_NUM_PROCEDURES;
168 if config.auth_trigger_procedures.len() > max_procedures {
169 return Err(AccountError::other(format!(
170 "Cannot track more than {max_procedures} procedures (account limit)"
171 )));
172 }
173
174 Ok(Self { public_key, config })
175 }
176}
177
178impl From<AuthRpoFalcon512Acl> for AccountComponent {
179 fn from(falcon: AuthRpoFalcon512Acl) -> Self {
180 let mut storage_slots = Vec::with_capacity(3);
181
182 storage_slots.push(StorageSlot::Value(falcon.public_key.into()));
184
185 let num_procs = falcon.config.auth_trigger_procedures.len() as u32;
188 storage_slots.push(StorageSlot::Value(Word::from([
189 num_procs,
190 u32::from(falcon.config.allow_unauthorized_output_notes),
191 u32::from(falcon.config.allow_unauthorized_input_notes),
192 0,
193 ])));
194
195 let map_entries = falcon
199 .config
200 .auth_trigger_procedures
201 .iter()
202 .enumerate()
203 .map(|(i, proc_root)| (Word::from([i as u32, 0, 0, 0]), *proc_root));
204
205 storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap()));
207
208 AccountComponent::new(rpo_falcon_512_acl_library(), storage_slots)
209 .expect(
210 "ACL auth component should satisfy the requirements of a valid account component",
211 )
212 .with_supports_all_types()
213 }
214}
215
216pub struct NoAuth;
231
232impl NoAuth {
233 pub fn new() -> Self {
235 Self
236 }
237}
238
239impl Default for NoAuth {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245impl From<NoAuth> for AccountComponent {
246 fn from(_: NoAuth) -> Self {
247 AccountComponent::new(no_auth_library(), vec![])
248 .expect("NoAuth component should satisfy the requirements of a valid account component")
249 .with_supports_all_types()
250 }
251}
252
253#[derive(Debug)]
267pub struct AuthRpoFalcon512Multisig {
268 threshold: u32,
269 approvers: Vec<PublicKey>,
270}
271
272impl AuthRpoFalcon512Multisig {
273 pub fn new(threshold: u32, approvers: Vec<PublicKey>) -> Result<Self, AccountError> {
279 if threshold == 0 {
280 return Err(AccountError::other("threshold must be at least 1"));
281 }
282
283 if threshold > approvers.len() as u32 {
284 return Err(AccountError::other(
285 "threshold cannot be greater than number of approvers",
286 ));
287 }
288
289 Ok(Self { threshold, approvers })
290 }
291}
292
293impl From<AuthRpoFalcon512Multisig> for AccountComponent {
294 fn from(multisig: AuthRpoFalcon512Multisig) -> Self {
295 let mut storage_slots = Vec::with_capacity(3);
296
297 let num_approvers = multisig.approvers.len() as u32;
299 storage_slots.push(StorageSlot::Value(Word::from([
300 multisig.threshold,
301 num_approvers,
302 0,
303 0,
304 ])));
305
306 let map_entries = multisig
308 .approvers
309 .iter()
310 .enumerate()
311 .map(|(i, pub_key)| (Word::from([i as u32, 0, 0, 0]), (*pub_key).into()));
312
313 storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap()));
315
316 let executed_transactions = StorageMap::default();
318 storage_slots.push(StorageSlot::Map(executed_transactions));
319
320 AccountComponent::new(multisig_library(), storage_slots)
321 .expect("Multisig auth component should satisfy the requirements of a valid account component")
322 .with_supports_all_types()
323 }
324}
325
326#[cfg(test)]
330mod tests {
331 use alloc::string::ToString;
332
333 use miden_objects::Word;
334 use miden_objects::account::AccountBuilder;
335
336 use super::*;
337 use crate::account::components::WellKnownComponent;
338 use crate::account::wallets::BasicWallet;
339
340 struct AclTestConfig {
342 with_procedures: bool,
344 allow_unauthorized_output_notes: bool,
346 allow_unauthorized_input_notes: bool,
348 expected_slot_1: Word,
350 }
351
352 fn get_basic_wallet_procedures() -> Vec<Word> {
354 let procedures: Vec<Word> = WellKnownComponent::BasicWallet.procedure_digests().collect();
356
357 assert_eq!(procedures.len(), 2);
358 procedures
359 }
360
361 fn test_acl_component(config: AclTestConfig) {
363 let public_key = PublicKey::new(Word::empty());
364
365 let mut acl_config = AuthRpoFalcon512AclConfig::new()
367 .with_allow_unauthorized_output_notes(config.allow_unauthorized_output_notes)
368 .with_allow_unauthorized_input_notes(config.allow_unauthorized_input_notes);
369
370 let auth_trigger_procedures = if config.with_procedures {
371 let procedures = get_basic_wallet_procedures();
372 acl_config = acl_config.with_auth_trigger_procedures(procedures.clone());
373 procedures
374 } else {
375 vec![]
376 };
377
378 let component =
380 AuthRpoFalcon512Acl::new(public_key, acl_config).expect("component creation failed");
381
382 let (account, _) = AccountBuilder::new([0; 32])
383 .with_auth_component(component)
384 .with_component(BasicWallet)
385 .build()
386 .expect("account building failed");
387
388 let public_key_slot = account.storage().get_item(0).expect("storage slot 0 access failed");
390 assert_eq!(public_key_slot, Word::from(public_key));
391
392 let slot_1 = account.storage().get_item(1).expect("storage slot 1 access failed");
394 assert_eq!(slot_1, config.expected_slot_1);
395
396 if config.with_procedures {
398 for (i, expected_proc_root) in auth_trigger_procedures.iter().enumerate() {
399 let proc_root = account
400 .storage()
401 .get_map_item(2, Word::from([i as u32, 0, 0, 0]))
402 .expect("storage map access failed");
403 assert_eq!(proc_root, *expected_proc_root);
404 }
405 } else {
406 let proc_root = account
408 .storage()
409 .get_map_item(2, Word::empty())
410 .expect("storage map access failed");
411 assert_eq!(proc_root, Word::empty());
412 }
413 }
414
415 #[test]
417 fn test_rpo_falcon_512_acl_no_procedures() {
418 test_acl_component(AclTestConfig {
419 with_procedures: false,
420 allow_unauthorized_output_notes: false,
421 allow_unauthorized_input_notes: false,
422 expected_slot_1: Word::empty(), });
424 }
425
426 #[test]
428 fn test_rpo_falcon_512_acl_with_two_procedures() {
429 test_acl_component(AclTestConfig {
430 with_procedures: true,
431 allow_unauthorized_output_notes: false,
432 allow_unauthorized_input_notes: false,
433 expected_slot_1: Word::from([2u32, 0, 0, 0]),
434 });
435 }
436
437 #[test]
439 fn test_rpo_falcon_512_acl_with_allow_unauthorized_output_notes() {
440 test_acl_component(AclTestConfig {
441 with_procedures: false,
442 allow_unauthorized_output_notes: true,
443 allow_unauthorized_input_notes: false,
444 expected_slot_1: Word::from([0u32, 1, 0, 0]),
445 });
446 }
447
448 #[test]
450 fn test_rpo_falcon_512_acl_with_procedures_and_allow_unauthorized_output_notes() {
451 test_acl_component(AclTestConfig {
452 with_procedures: true,
453 allow_unauthorized_output_notes: true,
454 allow_unauthorized_input_notes: false,
455 expected_slot_1: Word::from([2u32, 1, 0, 0]),
456 });
457 }
458
459 #[test]
461 fn test_rpo_falcon_512_acl_with_allow_unauthorized_input_notes() {
462 test_acl_component(AclTestConfig {
463 with_procedures: false,
464 allow_unauthorized_output_notes: false,
465 allow_unauthorized_input_notes: true,
466 expected_slot_1: Word::from([0u32, 0, 1, 0]),
467 });
468 }
469
470 #[test]
472 fn test_rpo_falcon_512_acl_with_both_allow_flags() {
473 test_acl_component(AclTestConfig {
474 with_procedures: true,
475 allow_unauthorized_output_notes: true,
476 allow_unauthorized_input_notes: true,
477 expected_slot_1: Word::from([2u32, 1, 1, 0]),
478 });
479 }
480
481 #[test]
482 fn test_no_auth_component() {
483 let (_account, _) = AccountBuilder::new([0; 32])
485 .with_auth_component(NoAuth)
486 .with_component(BasicWallet)
487 .build()
488 .expect("account building failed");
489 }
490
491 #[test]
496 fn test_multisig_component_setup() {
497 let pub_key_1 = PublicKey::new(Word::from([1u32, 0, 0, 0]));
499 let pub_key_2 = PublicKey::new(Word::from([2u32, 0, 0, 0]));
500 let pub_key_3 = PublicKey::new(Word::from([3u32, 0, 0, 0]));
501 let approvers = vec![pub_key_1, pub_key_2, pub_key_3];
502 let threshold = 2u32;
503
504 let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone())
506 .expect("multisig component creation failed");
507
508 let (account, _) = AccountBuilder::new([0; 32])
510 .with_auth_component(multisig_component)
511 .with_component(BasicWallet)
512 .build()
513 .expect("account building failed");
514
515 let threshold_slot = account.storage().get_item(0).expect("storage slot 0 access failed");
517 assert_eq!(threshold_slot, Word::from([threshold, approvers.len() as u32, 0, 0]));
518
519 for (i, expected_pub_key) in approvers.iter().enumerate() {
521 let stored_pub_key = account
522 .storage()
523 .get_map_item(1, Word::from([i as u32, 0, 0, 0]))
524 .expect("storage map access failed");
525 assert_eq!(stored_pub_key, Word::from(*expected_pub_key));
526 }
527 }
528
529 #[test]
531 fn test_multisig_component_minimum_threshold() {
532 let pub_key = PublicKey::new(Word::from([42u32, 0, 0, 0]));
533 let approvers = vec![pub_key];
534 let threshold = 1u32;
535
536 let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone())
537 .expect("multisig component creation failed");
538
539 let (account, _) = AccountBuilder::new([0; 32])
540 .with_auth_component(multisig_component)
541 .with_component(BasicWallet)
542 .build()
543 .expect("account building failed");
544
545 let threshold_slot = account.storage().get_item(0).expect("storage slot 0 access failed");
547 assert_eq!(threshold_slot, Word::from([threshold, approvers.len() as u32, 0, 0]));
548
549 let stored_pub_key = account
550 .storage()
551 .get_map_item(1, Word::from([0u32, 0, 0, 0]))
552 .expect("storage map access failed");
553 assert_eq!(stored_pub_key, Word::from(pub_key));
554 }
555
556 #[test]
558 fn test_multisig_component_error_cases() {
559 let pub_key = PublicKey::new(Word::from([1u32, 0, 0, 0]));
560 let approvers = vec![pub_key];
561
562 let result = AuthRpoFalcon512Multisig::new(0, approvers.clone());
564 assert!(result.unwrap_err().to_string().contains("threshold must be at least 1"));
565
566 let result = AuthRpoFalcon512Multisig::new(2, approvers);
568 assert!(
569 result
570 .unwrap_err()
571 .to_string()
572 .contains("threshold cannot be greater than number of approvers")
573 );
574 }
575}