1use alloc::vec::Vec;
2
3use miden_protocol::Word;
4use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
5use miden_protocol::account::component::{
6 AccountComponentMetadata,
7 SchemaType,
8 StorageSchema,
9 StorageSlotSchema,
10};
11use miden_protocol::account::{
12 AccountComponent,
13 AccountType,
14 StorageMap,
15 StorageMapKey,
16 StorageSlot,
17 StorageSlotName,
18};
19use miden_protocol::errors::AccountError;
20use miden_protocol::utils::sync::LazyLock;
21
22use super::multisig::{AuthMultisig, AuthMultisigConfig};
23use crate::account::components::multisig_psm_library;
24
25static PSM_PUBKEY_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
29 StorageSlotName::new("miden::standards::auth::psm::pub_key")
30 .expect("storage slot name should be valid")
31});
32
33static PSM_SCHEME_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
34 StorageSlotName::new("miden::standards::auth::psm::scheme")
35 .expect("storage slot name should be valid")
36});
37
38#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct AuthMultisigPsmConfig {
44 multisig: AuthMultisigConfig,
45 psm_config: PsmConfig,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct PsmConfig {
51 pub_key: PublicKeyCommitment,
52 auth_scheme: AuthScheme,
53}
54
55impl PsmConfig {
56 pub fn new(pub_key: PublicKeyCommitment, auth_scheme: AuthScheme) -> Self {
57 Self { pub_key, auth_scheme }
58 }
59
60 pub fn pub_key(&self) -> PublicKeyCommitment {
61 self.pub_key
62 }
63
64 pub fn auth_scheme(&self) -> AuthScheme {
65 self.auth_scheme
66 }
67
68 fn public_key_slot() -> &'static StorageSlotName {
69 &PSM_PUBKEY_SLOT_NAME
70 }
71
72 fn scheme_id_slot() -> &'static StorageSlotName {
73 &PSM_SCHEME_ID_SLOT_NAME
74 }
75
76 fn public_key_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
77 (
78 Self::public_key_slot().clone(),
79 StorageSlotSchema::map(
80 "Private state manager public keys",
81 SchemaType::u32(),
82 SchemaType::pub_key(),
83 ),
84 )
85 }
86
87 fn auth_scheme_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
88 (
89 Self::scheme_id_slot().clone(),
90 StorageSlotSchema::map(
91 "Private state manager scheme IDs",
92 SchemaType::u32(),
93 SchemaType::auth_scheme(),
94 ),
95 )
96 }
97
98 fn into_component_parts(self) -> (Vec<StorageSlot>, Vec<(StorageSlotName, StorageSlotSchema)>) {
99 let mut storage_slots = Vec::with_capacity(2);
100
101 let psm_public_key_entries =
103 [(StorageMapKey::from_raw(Word::from([0u32, 0, 0, 0])), Word::from(self.pub_key))];
104 storage_slots.push(StorageSlot::with_map(
105 Self::public_key_slot().clone(),
106 StorageMap::with_entries(psm_public_key_entries).unwrap(),
107 ));
108
109 let psm_scheme_id_entries = [(
111 StorageMapKey::from_raw(Word::from([0u32, 0, 0, 0])),
112 Word::from([self.auth_scheme as u32, 0, 0, 0]),
113 )];
114 storage_slots.push(StorageSlot::with_map(
115 Self::scheme_id_slot().clone(),
116 StorageMap::with_entries(psm_scheme_id_entries).unwrap(),
117 ));
118
119 let slot_metadata = vec![Self::public_key_slot_schema(), Self::auth_scheme_slot_schema()];
120
121 (storage_slots, slot_metadata)
122 }
123}
124
125impl AuthMultisigPsmConfig {
126 pub fn new(
131 approvers: Vec<(PublicKeyCommitment, AuthScheme)>,
132 default_threshold: u32,
133 psm_config: PsmConfig,
134 ) -> Result<Self, AccountError> {
135 let multisig = AuthMultisigConfig::new(approvers, default_threshold)?;
136 if multisig
137 .approvers()
138 .iter()
139 .any(|(approver, _)| *approver == psm_config.pub_key())
140 {
141 return Err(AccountError::other(
142 "private state manager public key must be different from approvers",
143 ));
144 }
145
146 Ok(Self { multisig, psm_config })
147 }
148
149 pub fn with_proc_thresholds(
152 mut self,
153 proc_thresholds: Vec<(Word, u32)>,
154 ) -> Result<Self, AccountError> {
155 self.multisig = self.multisig.with_proc_thresholds(proc_thresholds)?;
156 Ok(self)
157 }
158
159 pub fn approvers(&self) -> &[(PublicKeyCommitment, AuthScheme)] {
160 self.multisig.approvers()
161 }
162
163 pub fn default_threshold(&self) -> u32 {
164 self.multisig.default_threshold()
165 }
166
167 pub fn proc_thresholds(&self) -> &[(Word, u32)] {
168 self.multisig.proc_thresholds()
169 }
170
171 pub fn psm_config(&self) -> PsmConfig {
172 self.psm_config
173 }
174
175 fn into_parts(self) -> (AuthMultisigConfig, PsmConfig) {
176 (self.multisig, self.psm_config)
177 }
178}
179
180#[derive(Debug)]
191pub struct AuthMultisigPsm {
192 multisig: AuthMultisig,
193 psm_config: PsmConfig,
194}
195
196impl AuthMultisigPsm {
197 pub const NAME: &'static str = "miden::standards::components::auth::multisig_psm";
199
200 pub fn new(config: AuthMultisigPsmConfig) -> Result<Self, AccountError> {
202 let (multisig_config, psm_config) = config.into_parts();
203 Ok(Self {
204 multisig: AuthMultisig::new(multisig_config)?,
205 psm_config,
206 })
207 }
208
209 pub fn threshold_config_slot() -> &'static StorageSlotName {
211 AuthMultisig::threshold_config_slot()
212 }
213
214 pub fn approver_public_keys_slot() -> &'static StorageSlotName {
216 AuthMultisig::approver_public_keys_slot()
217 }
218
219 pub fn approver_scheme_ids_slot() -> &'static StorageSlotName {
221 AuthMultisig::approver_scheme_ids_slot()
222 }
223
224 pub fn executed_transactions_slot() -> &'static StorageSlotName {
226 AuthMultisig::executed_transactions_slot()
227 }
228
229 pub fn procedure_thresholds_slot() -> &'static StorageSlotName {
231 AuthMultisig::procedure_thresholds_slot()
232 }
233
234 pub fn psm_public_key_slot() -> &'static StorageSlotName {
236 PsmConfig::public_key_slot()
237 }
238
239 pub fn psm_scheme_id_slot() -> &'static StorageSlotName {
241 PsmConfig::scheme_id_slot()
242 }
243
244 pub fn threshold_config_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
246 AuthMultisig::threshold_config_slot_schema()
247 }
248
249 pub fn approver_public_keys_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
251 AuthMultisig::approver_public_keys_slot_schema()
252 }
253
254 pub fn approver_auth_scheme_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
256 AuthMultisig::approver_auth_scheme_slot_schema()
257 }
258
259 pub fn executed_transactions_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
261 AuthMultisig::executed_transactions_slot_schema()
262 }
263
264 pub fn procedure_thresholds_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
266 AuthMultisig::procedure_thresholds_slot_schema()
267 }
268
269 pub fn psm_public_key_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
271 PsmConfig::public_key_slot_schema()
272 }
273
274 pub fn psm_auth_scheme_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
276 PsmConfig::auth_scheme_slot_schema()
277 }
278
279 pub fn component_metadata() -> AccountComponentMetadata {
281 let storage_schema = StorageSchema::new([
282 Self::threshold_config_slot_schema(),
283 Self::approver_public_keys_slot_schema(),
284 Self::approver_auth_scheme_slot_schema(),
285 Self::executed_transactions_slot_schema(),
286 Self::procedure_thresholds_slot_schema(),
287 Self::psm_public_key_slot_schema(),
288 Self::psm_auth_scheme_slot_schema(),
289 ])
290 .expect("storage schema should be valid");
291
292 AccountComponentMetadata::new(Self::NAME, AccountType::all())
293 .with_description(
294 "Multisig authentication component with private state manager \
295 using hybrid signature schemes",
296 )
297 .with_storage_schema(storage_schema)
298 }
299}
300
301impl From<AuthMultisigPsm> for AccountComponent {
302 fn from(multisig: AuthMultisigPsm) -> Self {
303 let AuthMultisigPsm { multisig, psm_config } = multisig;
304 let multisig_component = AccountComponent::from(multisig);
305 let (psm_slots, psm_slot_metadata) = psm_config.into_component_parts();
306
307 let mut storage_slots = multisig_component.storage_slots().to_vec();
308 storage_slots.extend(psm_slots);
309
310 let mut slot_schemas: Vec<(StorageSlotName, StorageSlotSchema)> = multisig_component
311 .storage_schema()
312 .iter()
313 .map(|(slot_name, slot_schema)| (slot_name.clone(), slot_schema.clone()))
314 .collect();
315 slot_schemas.extend(psm_slot_metadata);
316
317 let storage_schema =
318 StorageSchema::new(slot_schemas).expect("storage schema should be valid");
319
320 let metadata = AccountComponentMetadata::new(
321 AuthMultisigPsm::NAME,
322 multisig_component.supported_types().clone(),
323 )
324 .with_description(multisig_component.metadata().description())
325 .with_version(multisig_component.metadata().version().clone())
326 .with_storage_schema(storage_schema);
327
328 AccountComponent::new(multisig_psm_library(), storage_slots, metadata).expect(
329 "Multisig auth component should satisfy the requirements of a valid account component",
330 )
331 }
332}
333
334#[cfg(test)]
338mod tests {
339 use alloc::string::ToString;
340
341 use miden_protocol::Word;
342 use miden_protocol::account::AccountBuilder;
343 use miden_protocol::account::auth::AuthSecretKey;
344
345 use super::*;
346 use crate::account::wallets::BasicWallet;
347
348 #[test]
350 fn test_multisig_component_setup() {
351 let sec_key_1 = AuthSecretKey::new_falcon512_poseidon2();
353 let sec_key_2 = AuthSecretKey::new_falcon512_poseidon2();
354 let sec_key_3 = AuthSecretKey::new_falcon512_poseidon2();
355 let psm_key = AuthSecretKey::new_ecdsa_k256_keccak();
356
357 let approvers = vec![
359 (sec_key_1.public_key().to_commitment(), sec_key_1.auth_scheme()),
360 (sec_key_2.public_key().to_commitment(), sec_key_2.auth_scheme()),
361 (sec_key_3.public_key().to_commitment(), sec_key_3.auth_scheme()),
362 ];
363
364 let threshold = 2u32;
365
366 let multisig_component = AuthMultisigPsm::new(
368 AuthMultisigPsmConfig::new(
369 approvers.clone(),
370 threshold,
371 PsmConfig::new(psm_key.public_key().to_commitment(), psm_key.auth_scheme()),
372 )
373 .expect("invalid multisig config"),
374 )
375 .expect("multisig component creation failed");
376
377 let account = AccountBuilder::new([0; 32])
379 .with_auth_component(multisig_component)
380 .with_component(BasicWallet)
381 .build()
382 .expect("account building failed");
383
384 let config_slot = account
386 .storage()
387 .get_item(AuthMultisigPsm::threshold_config_slot())
388 .expect("config storage slot access failed");
389 assert_eq!(config_slot, Word::from([threshold, approvers.len() as u32, 0, 0]));
390
391 for (i, (expected_pub_key, _)) in approvers.iter().enumerate() {
393 let stored_pub_key = account
394 .storage()
395 .get_map_item(
396 AuthMultisigPsm::approver_public_keys_slot(),
397 Word::from([i as u32, 0, 0, 0]),
398 )
399 .expect("approver public key storage map access failed");
400 assert_eq!(stored_pub_key, Word::from(*expected_pub_key));
401 }
402
403 for (i, (_, expected_auth_scheme)) in approvers.iter().enumerate() {
405 let stored_scheme_id = account
406 .storage()
407 .get_map_item(
408 AuthMultisigPsm::approver_scheme_ids_slot(),
409 Word::from([i as u32, 0, 0, 0]),
410 )
411 .expect("approver scheme ID storage map access failed");
412 assert_eq!(stored_scheme_id, Word::from([*expected_auth_scheme as u32, 0, 0, 0]));
413 }
414
415 let psm_public_key = account
417 .storage()
418 .get_map_item(AuthMultisigPsm::psm_public_key_slot(), Word::from([0u32, 0, 0, 0]))
419 .expect("private state manager public key storage map access failed");
420 assert_eq!(psm_public_key, Word::from(psm_key.public_key().to_commitment()));
421
422 let psm_scheme_id = account
423 .storage()
424 .get_map_item(AuthMultisigPsm::psm_scheme_id_slot(), Word::from([0u32, 0, 0, 0]))
425 .expect("private state manager scheme ID storage map access failed");
426 assert_eq!(psm_scheme_id, Word::from([psm_key.auth_scheme() as u32, 0, 0, 0]));
427 }
428
429 #[test]
431 fn test_multisig_component_minimum_threshold() {
432 let pub_key = AuthSecretKey::new_ecdsa_k256_keccak().public_key().to_commitment();
433 let psm_key = AuthSecretKey::new_falcon512_poseidon2();
434 let approvers = vec![(pub_key, AuthScheme::EcdsaK256Keccak)];
435 let threshold = 1u32;
436
437 let multisig_component = AuthMultisigPsm::new(
438 AuthMultisigPsmConfig::new(
439 approvers.clone(),
440 threshold,
441 PsmConfig::new(psm_key.public_key().to_commitment(), psm_key.auth_scheme()),
442 )
443 .expect("invalid multisig config"),
444 )
445 .expect("multisig component creation failed");
446
447 let account = AccountBuilder::new([0; 32])
448 .with_auth_component(multisig_component)
449 .with_component(BasicWallet)
450 .build()
451 .expect("account building failed");
452
453 let config_slot = account
455 .storage()
456 .get_item(AuthMultisigPsm::threshold_config_slot())
457 .expect("config storage slot access failed");
458 assert_eq!(config_slot, Word::from([threshold, approvers.len() as u32, 0, 0]));
459
460 let stored_pub_key = account
461 .storage()
462 .get_map_item(AuthMultisigPsm::approver_public_keys_slot(), Word::from([0u32, 0, 0, 0]))
463 .expect("approver pub keys storage map access failed");
464 assert_eq!(stored_pub_key, Word::from(pub_key));
465
466 let stored_scheme_id = account
467 .storage()
468 .get_map_item(AuthMultisigPsm::approver_scheme_ids_slot(), Word::from([0u32, 0, 0, 0]))
469 .expect("approver scheme IDs storage map access failed");
470 assert_eq!(stored_scheme_id, Word::from([AuthScheme::EcdsaK256Keccak as u32, 0, 0, 0]));
471 }
472
473 #[test]
475 fn test_multisig_component_with_psm() {
476 let sec_key_1 = AuthSecretKey::new_falcon512_poseidon2();
477 let sec_key_2 = AuthSecretKey::new_falcon512_poseidon2();
478 let psm_key = AuthSecretKey::new_ecdsa_k256_keccak();
479
480 let approvers = vec![
481 (sec_key_1.public_key().to_commitment(), sec_key_1.auth_scheme()),
482 (sec_key_2.public_key().to_commitment(), sec_key_2.auth_scheme()),
483 ];
484
485 let multisig_component = AuthMultisigPsm::new(
486 AuthMultisigPsmConfig::new(
487 approvers,
488 2,
489 PsmConfig::new(psm_key.public_key().to_commitment(), psm_key.auth_scheme()),
490 )
491 .expect("invalid multisig config"),
492 )
493 .expect("multisig component creation failed");
494
495 let account = AccountBuilder::new([0; 32])
496 .with_auth_component(multisig_component)
497 .with_component(BasicWallet)
498 .build()
499 .expect("account building failed");
500
501 let psm_public_key = account
502 .storage()
503 .get_map_item(AuthMultisigPsm::psm_public_key_slot(), Word::from([0u32, 0, 0, 0]))
504 .expect("private state manager public key storage map access failed");
505 assert_eq!(psm_public_key, Word::from(psm_key.public_key().to_commitment()));
506
507 let psm_scheme_id = account
508 .storage()
509 .get_map_item(AuthMultisigPsm::psm_scheme_id_slot(), Word::from([0u32, 0, 0, 0]))
510 .expect("private state manager scheme ID storage map access failed");
511 assert_eq!(psm_scheme_id, Word::from([psm_key.auth_scheme() as u32, 0, 0, 0]));
512 }
513
514 #[test]
516 fn test_multisig_component_error_cases() {
517 let pub_key = AuthSecretKey::new_ecdsa_k256_keccak().public_key().to_commitment();
518 let psm_key = AuthSecretKey::new_falcon512_poseidon2();
519 let approvers = vec![(pub_key, AuthScheme::EcdsaK256Keccak)];
520
521 let result = AuthMultisigPsmConfig::new(
523 approvers,
524 2,
525 PsmConfig::new(psm_key.public_key().to_commitment(), psm_key.auth_scheme()),
526 );
527
528 assert!(
529 result
530 .unwrap_err()
531 .to_string()
532 .contains("threshold cannot be greater than number of approvers")
533 );
534 }
535
536 #[test]
538 fn test_multisig_component_duplicate_approvers() {
539 let sec_key_1 = AuthSecretKey::new_ecdsa_k256_keccak();
541 let sec_key_2 = AuthSecretKey::new_ecdsa_k256_keccak();
542 let psm_key = AuthSecretKey::new_falcon512_poseidon2();
543
544 let approvers = vec![
546 (sec_key_1.public_key().to_commitment(), sec_key_1.auth_scheme()),
547 (sec_key_1.public_key().to_commitment(), sec_key_1.auth_scheme()),
548 (sec_key_2.public_key().to_commitment(), sec_key_2.auth_scheme()),
549 ];
550
551 let result = AuthMultisigPsmConfig::new(
552 approvers,
553 2,
554 PsmConfig::new(psm_key.public_key().to_commitment(), psm_key.auth_scheme()),
555 );
556 assert!(
557 result
558 .unwrap_err()
559 .to_string()
560 .contains("duplicate approver public keys are not allowed")
561 );
562 }
563
564 #[test]
566 fn test_multisig_component_psm_not_approver() {
567 let sec_key_1 = AuthSecretKey::new_ecdsa_k256_keccak();
568 let sec_key_2 = AuthSecretKey::new_ecdsa_k256_keccak();
569
570 let approvers = vec![
571 (sec_key_1.public_key().to_commitment(), sec_key_1.auth_scheme()),
572 (sec_key_2.public_key().to_commitment(), sec_key_2.auth_scheme()),
573 ];
574
575 let result = AuthMultisigPsmConfig::new(
576 approvers,
577 2,
578 PsmConfig::new(sec_key_1.public_key().to_commitment(), sec_key_1.auth_scheme()),
579 );
580
581 assert!(
582 result
583 .unwrap_err()
584 .to_string()
585 .contains("private state manager public key must be different from approvers")
586 );
587 }
588}