From 019e17ee9118ddab6d0e9682d2894d86b31d99a1 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 30 Nov 2024 11:11:04 +0100 Subject: [PATCH] impl templates with tests --- .../src/matrices/builder/matrix_builder.rs | 1 - .../builder/matrix_builder_unit_tests.rs | 16 +- .../matrices/matrix_of_factor_source_ids.rs | 623 ++++++++++++++++-- crates/rules/src/move_to_sargon.rs | 21 + .../roles/abstract_role_builder_or_built.rs | 5 + ...security_structure_of_factor_source_ids.rs | 16 +- 6 files changed, 627 insertions(+), 55 deletions(-) diff --git a/crates/rules/src/matrices/builder/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs index f80327c9..5ac95baa 100644 --- a/crates/rules/src/matrices/builder/matrix_builder.rs +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -16,7 +16,6 @@ pub type MatrixBuilder = AbstractMatrixBuilderOrBuilt< pub type MatrixTemplate = AbstractMatrixBuilt; - // ================== // ===== PUBLIC ===== // ================== diff --git a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs index a5197f3f..a8944e51 100644 --- a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs +++ b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs @@ -1566,9 +1566,9 @@ mod shield_configs { let mut sut = make(); // Primary - // TODO: Ask Matt about this, does he mean Threshold(1) or Override? - sut.add_factor_source_to_primary_override(FactorSourceID::sample_ledger()) + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) .unwrap(); + sut.set_threshold(1).unwrap(); // Recovery sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) @@ -1585,9 +1585,9 @@ mod shield_configs { built, MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( - 0, - [], + 1, [FactorSourceID::sample_ledger(),], + [], ), RoleWithFactorSourceIds::recovery_with_factors([ FactorSourceID::sample_ledger_other(), @@ -1606,9 +1606,9 @@ mod shield_configs { let mut sut = make(); // Primary - // TODO: Ask Matt about this, does he mean Threshold(1) or Override? - sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) .unwrap(); + sut.set_threshold(1).unwrap(); // Recovery sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) @@ -1625,9 +1625,9 @@ mod shield_configs { built, MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( - 0, - [], + 1, [FactorSourceID::sample_device(),], + [], ), RoleWithFactorSourceIds::recovery_with_factors([ FactorSourceID::sample_ledger(), diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index c033720a..a51f596e 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -241,11 +241,12 @@ impl MatrixOfFactorSourceIds { let mut builder = MatrixBuilder::new(); // Primary - // TODO: Ask Matt about this, does he mean Threshold(1) or Override? builder - .add_factor_source_to_primary_override(FactorSourceID::sample_ledger()) + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) .unwrap(); + builder.set_threshold(1).unwrap(); + // Recovery builder .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) @@ -265,10 +266,10 @@ impl MatrixOfFactorSourceIds { let mut builder = MatrixBuilder::new(); // Primary - // TODO: Ask Matt about this, does he mean Threshold(1) or Override? builder - .add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) .unwrap(); + builder.set_threshold(1).unwrap(); // Recovery builder @@ -538,30 +539,578 @@ impl MatrixOfFactorSourceIds { mod test_templates { use super::*; - #[test] - fn temp() { - let c = MatrixTemplate::config_90(); - let m = c - .fulfill(vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_device_other(), - FactorSourceID::sample_ledger(), - FactorSourceID::sample_ledger_other(), - FactorSourceID::sample_arculus(), - FactorSourceID::sample_arculus_other(), - FactorSourceID::sample_passphrase(), - FactorSourceID::sample_passphrase_other(), - FactorSourceID::sample_security_questions(), - FactorSourceID::sample_security_questions_other(), - FactorSourceID::sample_trusted_contact(), - FactorSourceID::sample_trusted_contact_other(), - ]) + fn test_template(template: MatrixTemplate, expected: MatrixOfFactorSourceIds) { + let m = template + .fulfill(*ALL_FACTOR_SOURCE_ID_SAMPLES_INC_NON_HD) .unwrap(); - pretty_assertions::assert_eq!(m, MatrixOfFactorSourceIds::sample_config_90()); + pretty_assertions::assert_eq!(m, expected); + } + + #[test] + fn template_config_11() { + test_template( + MatrixTemplate::config_11(), + MatrixOfFactorSourceIds::sample_config_11(), + ) + } + + #[test] + fn template_config_12() { + test_template( + MatrixTemplate::config_12(), + MatrixOfFactorSourceIds::sample_config_12(), + ) + } + + #[test] + fn template_config_13() { + test_template( + MatrixTemplate::config_13(), + MatrixOfFactorSourceIds::sample_config_13(), + ) + } + + #[test] + fn template_config_14() { + test_template( + MatrixTemplate::config_14(), + MatrixOfFactorSourceIds::sample_config_14(), + ) + } + + #[test] + fn template_config_15() { + test_template( + MatrixTemplate::config_15(), + MatrixOfFactorSourceIds::sample_config_15(), + ) + } + + #[test] + fn template_config_21() { + test_template( + MatrixTemplate::config_21(), + MatrixOfFactorSourceIds::sample_config_21(), + ) + } + + #[test] + fn template_config_22() { + test_template( + MatrixTemplate::config_22(), + MatrixOfFactorSourceIds::sample_config_22(), + ) + } + + #[test] + fn template_config_23() { + test_template( + MatrixTemplate::config_23(), + MatrixOfFactorSourceIds::sample_config_23(), + ) + } + + #[test] + fn template_config_24() { + test_template( + MatrixTemplate::config_24(), + MatrixOfFactorSourceIds::sample_config_24(), + ) + } + + #[test] + fn template_config_30() { + test_template( + MatrixTemplate::config_30(), + MatrixOfFactorSourceIds::sample_config_30(), + ) + } + + #[test] + fn template_config_40() { + test_template( + MatrixTemplate::config_40(), + MatrixOfFactorSourceIds::sample_config_40(), + ) + } + + #[test] + fn template_config_51() { + test_template( + MatrixTemplate::config_51(), + MatrixOfFactorSourceIds::sample_config_51(), + ) + } + + #[test] + fn template_config_52() { + test_template( + MatrixTemplate::config_52(), + MatrixOfFactorSourceIds::sample_config_52(), + ) + } + + #[test] + fn template_config_60() { + test_template( + MatrixTemplate::config_60(), + MatrixOfFactorSourceIds::sample_config_60(), + ) + } + + #[test] + fn template_config_70() { + test_template( + MatrixTemplate::config_70(), + MatrixOfFactorSourceIds::sample_config_70(), + ) + } + + #[test] + fn template_config_80() { + test_template( + MatrixTemplate::config_80(), + MatrixOfFactorSourceIds::sample_config_80(), + ) + } + + #[test] + fn template_config_90() { + test_template( + MatrixTemplate::config_90(), + MatrixOfFactorSourceIds::sample_config_90(), + ) } } impl MatrixTemplate { + pub fn config_11() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::password(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_12() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::ledger(0), + FactorSourceTemplate::password(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::password(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_13() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::password(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::password(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_14() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 1, + vec![FactorSourceTemplate::device(0)], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::ledger(0)], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::password(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_15() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 1, + vec![FactorSourceTemplate::ledger(0)], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::device(0)], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::password(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_21() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::ledger(0), + FactorSourceTemplate::ledger(1), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::device(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_22() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::ledger(0), + FactorSourceTemplate::ledger(1), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::ledger(0), + FactorSourceTemplate::ledger(1), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::device(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_23() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 1, + vec![FactorSourceTemplate::ledger(0)], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::ledger(1)], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::device(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_24() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 1, + vec![FactorSourceTemplate::device(0)], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::ledger(0)], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::ledger(1)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_30() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::ledger(0), + FactorSourceTemplate::ledger(1), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::password(0), + ], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_40() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::password(0), + FactorSourceTemplate::password(1), + FactorSourceTemplate::off_device_mnemonic(0), + ], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_51() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::password(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::trusted_contact(0)], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::password(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_52() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::password(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::trusted_contact(0), + FactorSourceTemplate::trusted_contact(1), + FactorSourceTemplate::device(0), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::password(0), + FactorSourceTemplate::password(1), + FactorSourceTemplate::off_device_mnemonic(0), + ], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_60() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 1, + vec![FactorSourceTemplate::device(0)], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::trusted_contact(0)], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::security_questions(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_70() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::trusted_contact(0), + FactorSourceTemplate::ledger(0), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::device(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn config_80() -> Self { + Self { + built: PhantomData, + primary_role: PrimaryRoleTemplate::with_factors( + 2, + vec![ + FactorSourceTemplate::device(0), + FactorSourceTemplate::ledger(0), + ], + vec![], + ), + recovery_role: RecoveryRoleTemplate::with_factors( + 0, + vec![], + vec![ + FactorSourceTemplate::ledger(0), + FactorSourceTemplate::device(0), + ], + ), + confirmation_role: ConfirmationRoleTemplate::with_factors( + 0, + vec![], + vec![FactorSourceTemplate::security_questions(0)], + ), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + pub fn config_90() -> Self { Self { built: PhantomData, @@ -598,7 +1147,7 @@ impl AbstractBuiltRoleWithFactor { ) -> Result, CommonError> { let mut fulfill = |xs: &Vec| -> Result, CommonError> { - xs.into_iter() + xs.iter() .map(|f| factor_source_id_assigner.next(f)) .collect::, CommonError>>() }; @@ -622,15 +1171,13 @@ impl FactorSourceIdAssigner { } fn next(&mut self, template: &FactorSourceTemplate) -> Result { if let Some(existing) = self.map.get(template) { - println!("🎭 existing for ID={:?}, using: {:?}", template, *existing); Ok(*existing) } else if let Some(index_of_next) = self - .factor_source_ids - .iter() - .position(|f| f.get_factor_source_kind() == template.kind) + .factor_source_ids + .iter() + .position(|f| f.get_factor_source_kind() == template.kind) { let next = self.factor_source_ids.remove(index_of_next); - println!("🎭 Consuming and using new: for template={:?}, next: {:?}", template, next); self.map.insert(template.clone(), next); Ok(next) } else { @@ -815,19 +1362,19 @@ mod tests { assert_eq_after_json_roundtrip( &sut, r#" - { + { "primaryRole": { - "threshold": 0, - "thresholdFactors": [], - "overrideFactors": [ - { + "threshold": 1, + "thresholdFactors": [ + { "discriminator": "fromHash", "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } } - } - ] + ], + "overrideFactors": [] }, "recoveryRole": { "threshold": 0, diff --git a/crates/rules/src/move_to_sargon.rs b/crates/rules/src/move_to_sargon.rs index e413f3d2..e12a0237 100644 --- a/crates/rules/src/move_to_sargon.rs +++ b/crates/rules/src/move_to_sargon.rs @@ -371,6 +371,27 @@ pub trait MnemonicWithPassphraseSamples: Sized { use once_cell::sync::Lazy; +#[allow(dead_code)] +pub(crate) static ALL_FACTOR_SOURCE_ID_SAMPLES_INC_NON_HD: Lazy<[FactorSourceID; 14]> = + Lazy::new(|| { + [ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other(), + FactorSourceID::sample_arculus(), + FactorSourceID::sample_arculus_other(), + FactorSourceID::sample_password(), + FactorSourceID::sample_password_other(), + FactorSourceID::sample_passphrase(), + FactorSourceID::sample_passphrase_other(), + FactorSourceID::sample_security_questions(), + FactorSourceID::sample_device_other(), + FactorSourceID::sample_security_questions_other(), + FactorSourceID::sample_trusted_contact(), + FactorSourceID::sample_trusted_contact_other(), + ] + }); + pub(crate) static MNEMONIC_BY_ID_MAP: Lazy< IndexMap, > = Lazy::new(|| { diff --git a/crates/rules/src/roles/abstract_role_builder_or_built.rs b/crates/rules/src/roles/abstract_role_builder_or_built.rs index dfb48ce8..da0921e1 100644 --- a/crates/rules/src/roles/abstract_role_builder_or_built.rs +++ b/crates/rules/src/roles/abstract_role_builder_or_built.rs @@ -42,6 +42,11 @@ impl FactorSourceTemplate { Self::new(FactorSourceKind::Password, id) } + /// Radix Wallet (UI) calls this "Passphrase" + pub fn off_device_mnemonic(id: u8) -> Self { + Self::new(FactorSourceKind::OffDeviceMnemonic, id) + } + pub fn trusted_contact(id: u8) -> Self { Self::new(FactorSourceKind::TrustedContact, id) } diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs index 404cbd9f..cf05b586 100644 --- a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -120,17 +120,17 @@ mod tests { }, "matrixOfFactors": { "primaryRole": { - "threshold": 0, - "thresholdFactors": [], - "overrideFactors": [ + "threshold": 1, + "thresholdFactors": [ { - "discriminator": "fromHash", - "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" } } - ] + ], + "overrideFactors": [] }, "recoveryRole": { "threshold": 0,