From 87a24004b797cec0071edb60492eef3d3c6d3d06 Mon Sep 17 00:00:00 2001 From: Alexander Cyon <116169792+CyonAlexRDX@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:54:26 +0100 Subject: [PATCH] FIP: Support mix of Accounts & Personas (#306) FIP: Support mix of Accounts & Personas + ROLA key support --- Cargo.lock | 4 +- .../MFA/SecurityShieldsBuilderTests.swift | 7 + crates/sargon-uniffi/Cargo.toml | 2 +- .../src/core/error/common_error.rs | 9 + .../src/keys_collector/derivation_purpose.rs | 4 + .../security_shield_builder.rs | 32 ++ .../security_shield_builder_invalid_reason.rs | 3 + ...security_structure_of_factor_source_ids.rs | 3 + .../security_structure_of_factor_sources.rs | 3 + crates/sargon/Cargo.toml | 2 +- crates/sargon/src/core/error/common_error.rs | 9 + .../types/keys/key_agreement/private_key.rs | 1 - .../agnostic_paths/derivation_preset.rs | 61 ++- .../agnostic_paths/index_agnostic_path.rs | 45 ++ .../quantified_derivation_preset.rs | 92 ++++ .../agnostic_paths/quantities.rs | 32 ++ .../factor_instances_cache.rs | 467 ++++++++++++------ .../factor_instances_cache/keyed_instances.rs | 17 +- ...entity_index_profile_analyzing_assigner.rs | 117 +++++ ...ion_entity_index_with_ephemeral_offsets.rs | 2 + .../provider/factor_instances_provider.rs | 415 +++++++++++----- .../factor_instances_provider_unit_tests.rs | 360 ++++++++++---- .../factor_instances_provider_outcome.rs | 59 ++- ...ernal_factor_instances_provider_outcome.rs | 108 +++- ...r_instances_provider_outcome_for_factor.rs | 34 +- .../provider_adopters/cache_filler.rs | 74 ++- ...curify_entity_factor_instances_provider.rs | 118 +++-- ...rtual_entity_creating_instance_provider.rs | 269 +++++++--- .../types/appendable_collection.rs | 2 +- .../types/factor_instances.rs | 23 +- .../bip39/mnemonic.rs | 3 - .../collector/derivation_purpose.rs | 52 +- .../test_derivation_interactor.rs | 2 +- .../profile/logic/account/query_accounts.rs | 14 + .../sargon/src/profile/logic/create_entity.rs | 14 +- .../profile_network_get_entities.rs | 26 +- .../src/profile/logic/profile_networks.rs | 6 +- .../automatic_shield_builder.rs | 74 ++- .../automatic_shield_builder/mod.rs | 2 +- .../{proto_matrix.rs => proto_shield.rs} | 8 +- .../matrices/matrix_of_factor_instances.rs | 39 +- .../role_with_factor_instances.rs | 7 +- .../security_shield_builder.rs | 41 +- .../security_shield_builder_invalid_reason.rs | 3 + .../abstract_security_structure_of_factors.rs | 16 +- ...security_structure_of_factor_source_ids.rs | 28 +- .../security_structure_of_factor_sources.rs | 20 +- .../supporting_types/account_or_persona.rs | 6 + .../v100/address/non_fungible_global_id.rs | 1 - .../profile/v100/app_preferences/security.rs | 14 + .../profile/v100/entity/account/account.rs | 3 + .../profile/v100/entity/has_security_state.rs | 162 ++++++ .../profile/v100/entity/persona/persona.rs | 3 + .../v100/networks/network/profile_network.rs | 36 +- .../profile/v100/networks/profile_networks.rs | 11 +- crates/sargon/src/profile/v100/profile.rs | 25 +- .../signing/collector/signatures_collector.rs | 6 +- .../petition_types/petition_for_entity.rs | 2 + .../petition_for_transaction.rs | 1 + .../client/factor_instances_cache_client.rs | 84 +++- .../system/sargon_os/profile_state_holder.rs | 7 + .../system/sargon_os/sargon_os_accounts.rs | 231 +++++++-- .../src/system/sargon_os/sargon_os_factors.rs | 22 +- .../src/types/samples/account_samples.rs | 12 +- .../src/types/samples/persona_samples.rs | 11 +- .../low_level/v1/transaction_intent.rs | 1 - crates/sargon/tests/integration/main.rs | 3 + 67 files changed, 2633 insertions(+), 737 deletions(-) rename crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/{proto_matrix.rs => proto_shield.rs} (81%) diff --git a/Cargo.lock b/Cargo.lock index dc127ed3a..9f9f88052 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,7 +2761,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.1.92" +version = "1.1.93" dependencies = [ "actix-rt", "aes-gcm", @@ -2816,7 +2816,7 @@ dependencies = [ [[package]] name = "sargon-uniffi" -version = "1.1.92" +version = "1.1.93" dependencies = [ "actix-rt", "assert-json-diff", diff --git a/apple/Tests/TestCases/Profile/MFA/SecurityShieldsBuilderTests.swift b/apple/Tests/TestCases/Profile/MFA/SecurityShieldsBuilderTests.swift index d259f0097..7061ba22b 100644 --- a/apple/Tests/TestCases/Profile/MFA/SecurityShieldsBuilderTests.swift +++ b/apple/Tests/TestCases/Profile/MFA/SecurityShieldsBuilderTests.swift @@ -104,6 +104,9 @@ struct ShieldTests { #expect(builder.validate() == .ConfirmationRoleMustHaveAtLeastOneFactor) builder.addFactorSourceToConfirmationOverride(factorSourceId: .sampleArculus) + + builder.setAuthenticationSigningFactor(new: .sampleDevice) + #expect(builder.validate() == nil) #expect((try? builder.build()) != nil) } @@ -173,6 +176,8 @@ struct ShieldTests { builder.addFactorSourceToRecoveryOverride(factorSourceId: .sampleArculus) builder.addFactorSourceToConfirmationOverride(factorSourceId: .sampleArculusOther) + builder.setAuthenticationSigningFactor(new: .sampleDevice) + let shield = try! builder.build() #expect(shield.matrixOfFactors.primaryRole.overrideFactors.isEmpty) @@ -205,6 +210,8 @@ struct ShieldTests { builder.removeFactorFromPrimary(factorSourceId: .sampleArculusOther) builder.removeFactorFromRecovery(factorSourceId: .sampleLedgerOther) + builder.setAuthenticationSigningFactor(new: .sampleDevice) + // Validate #expect(builder.validate() == nil) diff --git a/crates/sargon-uniffi/Cargo.toml b/crates/sargon-uniffi/Cargo.toml index 604fc535e..4bba6a821 100644 --- a/crates/sargon-uniffi/Cargo.toml +++ b/crates/sargon-uniffi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon-uniffi" # Don't forget to update version in crates/sargon/Cargo.toml -version = "1.1.92" +version = "1.1.93" edition = "2021" build = "build.rs" diff --git a/crates/sargon-uniffi/src/core/error/common_error.rs b/crates/sargon-uniffi/src/core/error/common_error.rs index 759c67792..5816cadb0 100644 --- a/crates/sargon-uniffi/src/core/error/common_error.rs +++ b/crates/sargon-uniffi/src/core/error/common_error.rs @@ -862,6 +862,15 @@ pub enum CommonError { #[error("Cannot securify entity that has provisional security config")] CannotSecurifyEntityHasProvisionalSecurityConfig = 10241, + + #[error("Too few FactorInstances derived")] + TooFewFactorInstancesDerived = 10242, + + #[error("Missing Authentication Signing FactorInstance mapping SecurityStructureOfFactorSources into SecurityStructureOfFactorInstances.")] + MissingRolaKeyForSecurityStructureOfFactorInstances = 10243, + + #[error("SecurityStateAccessController address mismatch")] + SecurityStateAccessControllerAddressMismatch = 10244, } #[uniffi::export] diff --git a/crates/sargon-uniffi/src/keys_collector/derivation_purpose.rs b/crates/sargon-uniffi/src/keys_collector/derivation_purpose.rs index 53dfc05e8..8e81de364 100644 --- a/crates/sargon-uniffi/src/keys_collector/derivation_purpose.rs +++ b/crates/sargon-uniffi/src/keys_collector/derivation_purpose.rs @@ -23,6 +23,10 @@ pub enum DerivationPurpose { /// for identity MFA SecurifyingPersona, + /// When applying a security shield to accounts and personas mixed, initiates keys collection + /// for account MFA + SecurifyingAccountsAndPersonas, + /// When adding a new factor source, initiates keys collection /// for collecting various factor instances. PreDerivingKeys, diff --git a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder.rs b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder.rs index 981b08eb6..7077057a8 100644 --- a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder.rs +++ b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder.rs @@ -109,6 +109,11 @@ impl SecurityShieldBuilder { self.get(|builder| builder.get_name()) } + pub fn get_authentication_signing_factor(&self) -> Option { + self.get(|builder| builder.get_authentication_signing_factor()) + .map(|x| x.into()) + } + pub fn get_primary_threshold_factors(&self) -> Vec { self.get_factors(|builder| builder.get_primary_threshold_factors()) } @@ -135,6 +140,17 @@ impl SecurityShieldBuilder { self.set(|builder| builder.set_name(&name)); } + pub fn set_authentication_signing_factor( + &self, + new: Option, + ) { + self.set(|builder| { + builder.set_authentication_signing_factor( + new.clone().map(|x| x.into_internal()), + ) + }); + } + pub fn remove_factor_from_all_roles( &self, factor_source_id: FactorSourceID, @@ -756,6 +772,18 @@ mod tests { sut.remove_factor_from_confirmation(f.clone()); assert_eq!(xs, sut.get_confirmation_factors()); + assert_eq!( + sut.validate().unwrap(), + SecurityShieldBuilderInvalidReason::MissingAuthSigningFactor + ); + sut.set_authentication_signing_factor(Some( + FactorSourceID::sample_device_other(), + )); + assert_eq!( + sut.get_authentication_signing_factor(), + Some(FactorSourceID::sample_device_other()) + ); + let v0 = sut.validate(); let v1 = sut.validate(); // can call validate many times! assert_eq!(v0, v1); @@ -764,6 +792,10 @@ mod tests { let shield = sut.build().unwrap(); // can call build many times! assert_eq!(shield0, shield); + assert_eq!( + shield.authentication_signing_factor, + FactorSourceID::sample_device_other() + ); assert_eq!(shield.metadata.display_name.value, "S.H.I.E.L.D."); assert_eq!( shield.matrix_of_factors.primary_role.override_factors, diff --git a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder_invalid_reason.rs b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder_invalid_reason.rs index 16a0a488b..56eb64369 100644 --- a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder_invalid_reason.rs +++ b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_shield_builder_invalid_reason.rs @@ -8,6 +8,9 @@ use thiserror::Error as ThisError; Clone, Debug, ThisError, PartialEq, InternalConversion, uniffi::Error, )] pub enum SecurityShieldBuilderInvalidReason { + #[error("Auth Signing Factor Missing")] + MissingAuthSigningFactor, + #[error("Shield name is invalid")] ShieldNameInvalid, diff --git a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_source_ids.rs b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_source_ids.rs index 8ece8ba6f..be0a81376 100644 --- a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_source_ids.rs +++ b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_source_ids.rs @@ -15,6 +15,9 @@ pub struct SecurityStructureOfFactorSourceIDs { /// The structure of factors to use for certain roles, Primary, Recovery /// and Confirmation role. pub matrix_of_factors: MatrixOfFactorSourceIDs, + + /// The factor to use for authentication signing aka true Rola Key. + pub authentication_signing_factor: FactorSourceID, } delegate_debug_into!( diff --git a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_sources.rs b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_sources.rs index 7789cc128..0ebd22f7d 100644 --- a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_sources.rs +++ b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_sources.rs @@ -12,6 +12,9 @@ pub struct SecurityStructureOfFactorSources { /// The structure of factors to use for certain roles, Primary, Recovery /// and Confirmation role. pub matrix_of_factors: MatrixOfFactorSources, + + /// The factor to use for authentication signing aka true Rola Key. + pub authentication_signing_factor: FactorSource, } #[uniffi::export] diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml index e3d95d302..10708a6ac 100644 --- a/crates/sargon/Cargo.toml +++ b/crates/sargon/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon" # Don't forget to update version in crates/sargon-uniffi/Cargo.toml -version = "1.1.92" +version = "1.1.93" edition = "2021" build = "build.rs" diff --git a/crates/sargon/src/core/error/common_error.rs b/crates/sargon/src/core/error/common_error.rs index 48a760836..62a4c2df1 100644 --- a/crates/sargon/src/core/error/common_error.rs +++ b/crates/sargon/src/core/error/common_error.rs @@ -859,6 +859,15 @@ pub enum CommonError { #[error("Cannot securify entity that has provisional security config")] CannotSecurifyEntityHasProvisionalSecurityConfig = 10241, + + #[error("Too few FactorInstances derived")] + TooFewFactorInstancesDerived = 10242, + + #[error("Missing Authentication Signing FactorInstance mapping SecurityStructureOfFactorSources into SecurityStructureOfFactorInstances.")] + MissingRolaKeyForSecurityStructureOfFactorInstances = 10243, + + #[error("SecurityStateAccessController address mismatch")] + SecurityStateAccessControllerAddressMismatch = 10244, } impl CommonError { diff --git a/crates/sargon/src/core/types/keys/key_agreement/private_key.rs b/crates/sargon/src/core/types/keys/key_agreement/private_key.rs index 24e97de2a..c7afd73a7 100644 --- a/crates/sargon/src/core/types/keys/key_agreement/private_key.rs +++ b/crates/sargon/src/core/types/keys/key_agreement/private_key.rs @@ -156,7 +156,6 @@ mod tests { fn public_key() { let private_key = SUT::sample(); let public_key = private_key.public_key(); - println!("{:?}", public_key); assert_eq!( public_key, KeyAgreementPublicKey::from_hex("8679bc1fe3210b2ce84793668b05218fdc4c220bc05387b7d2ac0d4c7b7c5d10".to_owned()) diff --git a/crates/sargon/src/factor_instances_provider/agnostic_paths/derivation_preset.rs b/crates/sargon/src/factor_instances_provider/agnostic_paths/derivation_preset.rs index 799e1a1f5..6e662864d 100644 --- a/crates/sargon/src/factor_instances_provider/agnostic_paths/derivation_preset.rs +++ b/crates/sargon/src/factor_instances_provider/agnostic_paths/derivation_preset.rs @@ -24,6 +24,12 @@ pub enum DerivationPreset { #[debug("A-MFA")] AccountMfa, + /// Used to form DerivationPaths used to derive FactorInstances + /// for Authentication Signing (Securified) for accounts + /// `(EntityKind::Account, KeySpace::Securified, KeyKind::AuthenticationSigning)` + #[debug("A-Rola")] + AccountRola, + /// Used to form DerivationPaths used to derive FactorInstances /// for "veci": Virtual Entity Creating (Factor)Instance for personas. /// `(EntityKind::Identity, KeySpace::Unsecurified, KeyKind::TransactionSigning)` @@ -35,6 +41,12 @@ pub enum DerivationPreset { /// `(EntityKind::Identity, KeySpace::Securified, KeyKind::TransactionSigning)` #[debug("I-MFA")] IdentityMfa, + + /// Used to form DerivationPaths used to derive FactorInstances + /// for Authentication Signing (Securified) for peresonas + /// `(EntityKind::Identity, KeySpace::Securified, KeyKind::AuthenticationSigning)` + #[debug("I-Rola")] + IdentityRola, } // ============= @@ -63,6 +75,15 @@ impl DerivationPreset { CAP26EntityKind::Identity => Self::IdentityMfa, } } + + /// Selects a `DerivationPreset` for MFA based on `CAP26EntityKind`, + /// i.e. either `DerivationPreset::AccountRola` or `DerivationPreset::IdentityRola`. + pub fn rola_entity_kind(entity_kind: CAP26EntityKind) -> Self { + match entity_kind { + CAP26EntityKind::Account => Self::AccountRola, + CAP26EntityKind::Identity => Self::IdentityRola, + } + } } // ============= @@ -72,8 +93,12 @@ impl DerivationPreset { /// Returns the `CAP26EntityKind` of the `DerivationPreset`. pub fn entity_kind(&self) -> CAP26EntityKind { match self { - Self::AccountVeci | Self::AccountMfa => CAP26EntityKind::Account, - Self::IdentityVeci | Self::IdentityMfa => CAP26EntityKind::Identity, + Self::AccountVeci | Self::AccountMfa | Self::AccountRola => { + CAP26EntityKind::Account + } + Self::IdentityVeci | Self::IdentityMfa | Self::IdentityRola => { + CAP26EntityKind::Identity + } } } @@ -84,6 +109,9 @@ impl DerivationPreset { | Self::IdentityVeci | Self::AccountMfa | Self::IdentityMfa => CAP26KeyKind::TransactionSigning, + Self::AccountRola | Self::IdentityRola => { + CAP26KeyKind::AuthenticationSigning + } } } @@ -96,7 +124,10 @@ impl DerivationPreset { is_hardened: true, } } - Self::AccountMfa | Self::IdentityMfa => KeySpace::Securified, + Self::AccountMfa + | Self::IdentityMfa + | Self::AccountRola + | Self::IdentityRola => KeySpace::Securified, } } @@ -136,4 +167,28 @@ mod tests { fn inequality() { assert_ne!(SUT::sample(), SUT::sample_other()); } + + #[test] + fn test_mfa_entity_kind() { + assert_eq!( + SUT::mfa_entity_kind(CAP26EntityKind::Account), + SUT::AccountMfa + ); + assert_eq!( + SUT::mfa_entity_kind(CAP26EntityKind::Identity), + SUT::IdentityMfa + ); + } + + #[test] + fn test_rola_entity_kind() { + assert_eq!( + SUT::rola_entity_kind(CAP26EntityKind::Account), + SUT::AccountRola + ); + assert_eq!( + SUT::rola_entity_kind(CAP26EntityKind::Identity), + SUT::IdentityRola + ); + } } diff --git a/crates/sargon/src/factor_instances_provider/agnostic_paths/index_agnostic_path.rs b/crates/sargon/src/factor_instances_provider/agnostic_paths/index_agnostic_path.rs index 77f9354bc..e735b7928 100644 --- a/crates/sargon/src/factor_instances_provider/agnostic_paths/index_agnostic_path.rs +++ b/crates/sargon/src/factor_instances_provider/agnostic_paths/index_agnostic_path.rs @@ -119,6 +119,16 @@ impl TryFrom for DerivationPreset { CAP26KeyKind::TransactionSigning, KeySpace::Securified, ) => Ok(DerivationPreset::IdentityMfa), + ( + CAP26EntityKind::Account, + CAP26KeyKind::AuthenticationSigning, + KeySpace::Securified, + ) => Ok(DerivationPreset::AccountRola), + ( + CAP26EntityKind::Identity, + CAP26KeyKind::AuthenticationSigning, + KeySpace::Securified, + ) => Ok(DerivationPreset::IdentityRola), _ => Err(CommonError::InvalidBIP32Path { bad_value: "Invalid combination of entity_kind, key_kind and key_space" @@ -334,4 +344,39 @@ mod tests { assert_json_value_fails::(json!("")); assert_json_value_fails::(json!(" ")); } + + #[test] + fn derivation_preset_rola_valid_account() { + let preset = DerivationPreset::try_from(SUT::new( + NetworkID::Mainnet, + CAP26EntityKind::Account, + CAP26KeyKind::AuthenticationSigning, + KeySpace::Securified, + )) + .unwrap(); + assert_eq!(preset, DerivationPreset::AccountRola); + } + + #[test] + fn derivation_preset_rola_invalid_not_securified() { + let res = DerivationPreset::try_from(SUT::new( + NetworkID::Mainnet, + CAP26EntityKind::Account, + CAP26KeyKind::AuthenticationSigning, + KeySpace::Unsecurified { is_hardened: true }, + )); + assert!(matches!(res, Err(CommonError::InvalidBIP32Path { .. }))); + } + + #[test] + fn derivation_preset_rola_valid_identity() { + let preset = DerivationPreset::try_from(SUT::new( + NetworkID::Mainnet, + CAP26EntityKind::Identity, + CAP26KeyKind::AuthenticationSigning, + KeySpace::Securified, + )) + .unwrap(); + assert_eq!(preset, DerivationPreset::IdentityRola); + } } diff --git a/crates/sargon/src/factor_instances_provider/agnostic_paths/quantified_derivation_preset.rs b/crates/sargon/src/factor_instances_provider/agnostic_paths/quantified_derivation_preset.rs index 250ef7c82..6afd21b29 100644 --- a/crates/sargon/src/factor_instances_provider/agnostic_paths/quantified_derivation_preset.rs +++ b/crates/sargon/src/factor_instances_provider/agnostic_paths/quantified_derivation_preset.rs @@ -7,6 +7,13 @@ pub struct QuantifiedDerivationPreset { pub quantity: usize, } +impl Identifiable for QuantifiedDerivationPreset { + type ID = DerivationPreset; + fn id(&self) -> DerivationPreset { + self.derivation_preset + } +} + impl QuantifiedDerivationPreset { pub fn new(derivation_preset: DerivationPreset, quantity: usize) -> Self { Self { @@ -14,4 +21,89 @@ impl QuantifiedDerivationPreset { quantity, } } + + /// Returns a the QuantifiedDerivationPresets needed to securify the `addresses_of_entities`, including + /// a new Authentication Signing factor instance for each entity. Will return + /// the `Account` variant of each DerivationPreset for each Account in `addresses_of_entities` + /// and the `Identity` variant of each DerivationPreset for each Persona in `addresses_of_entities`. + pub fn securifying_unsecurified_entities( + addresses_of_entities: &IndexSet, + ) -> IdentifiedVecOf { + Self::mfa_for_entities(addresses_of_entities, true) + } + + /// Returns a the QuantifiedDerivationPresets needed to securify the `addresses_of_entities`, Will return + /// the `Account` variant of each DerivationPreset for each Account in `addresses_of_entities` + /// and the `Identity` variant of each DerivationPreset for each Persona in `addresses_of_entities`. + /// + /// if `include_rola_key_for_each_entity` is `true` a ROLA key for each entity will be included. + /// Typically we only set `include_rola_key_for_each_entity` to `true` for securifying + /// unsecurified entities. For already securified entities we might not + /// need to change the ROLA key. + fn mfa_for_entities( + addresses_of_entities: &IndexSet, + include_rola_key_for_each_entity: bool, + ) -> IdentifiedVecOf { + let account_addresses = addresses_of_entities + .iter() + .filter(|a| a.is_account()) + .collect_vec(); + let identity_addresses = addresses_of_entities + .iter() + .filter(|a| a.is_identity()) + .collect_vec(); + + match (account_addresses.is_empty(), identity_addresses.is_empty()) { + (true, true) => IdentifiedVecOf::new(), // weird! + (true, false) => { + let mut presets = IdentifiedVecOf::just(Self::new( + DerivationPreset::IdentityMfa, + identity_addresses.len(), + )); + if include_rola_key_for_each_entity { + presets.append(Self::new( + DerivationPreset::IdentityRola, + identity_addresses.len(), + )); + } + presets + } + (false, false) => { + let mut presets = IdentifiedVecOf::from_iter([ + Self::new( + DerivationPreset::AccountMfa, + account_addresses.len(), + ), + Self::new( + DerivationPreset::IdentityMfa, + identity_addresses.len(), + ), + ]); + if include_rola_key_for_each_entity { + presets.append(Self::new( + DerivationPreset::AccountRola, + account_addresses.len(), + )); + presets.append(Self::new( + DerivationPreset::IdentityRola, + identity_addresses.len(), + )); + } + presets + } + (false, true) => { + let mut presets = IdentifiedVecOf::just(Self::new( + DerivationPreset::AccountMfa, + account_addresses.len(), + )); + if include_rola_key_for_each_entity { + presets.append(Self::new( + DerivationPreset::AccountRola, + account_addresses.len(), + )); + } + presets + } + } + } } diff --git a/crates/sargon/src/factor_instances_provider/agnostic_paths/quantities.rs b/crates/sargon/src/factor_instances_provider/agnostic_paths/quantities.rs index e6349fbff..253b26f41 100644 --- a/crates/sargon/src/factor_instances_provider/agnostic_paths/quantities.rs +++ b/crates/sargon/src/factor_instances_provider/agnostic_paths/quantities.rs @@ -2,3 +2,35 @@ use crate::prelude::*; /// The quantity of DerivationPreset's to fill cache with. pub const CACHE_FILLING_QUANTITY: usize = 30; + +/// The quantity of DerivationPreset's to fill cache with for `DerivationPreset::AccountVeci`. +pub const CACHE_FILLING_QUANTITY_ACCOUNT_VECI: usize = CACHE_FILLING_QUANTITY; + +/// The quantity of DerivationPreset's to fill cache with for `DerivationPreset::AccountMfa`. +pub const CACHE_FILLING_QUANTITY_ACCOUNT_MFA: usize = CACHE_FILLING_QUANTITY; + +/// The quantity of DerivationPreset's to fill cache with for `DerivationPreset::AccountRola`. +pub const CACHE_FILLING_QUANTITY_ACCOUNT_ROLA: usize = CACHE_FILLING_QUANTITY; + +/// The quantity of DerivationPreset's to fill cache with for `DerivationPreset::IdentityVeci`. +pub const CACHE_FILLING_QUANTITY_IDENTITY_VECI: usize = CACHE_FILLING_QUANTITY; + +/// The quantity of DerivationPreset's to fill cache with for `DerivationPreset::IdentityMfa`. +pub const CACHE_FILLING_QUANTITY_IDENTITY_MFA: usize = CACHE_FILLING_QUANTITY; + +/// The quantity of DerivationPreset's to fill cache with for `DerivationPreset::IdentityRola`. +pub const CACHE_FILLING_QUANTITY_IDENTITY_ROLA: usize = CACHE_FILLING_QUANTITY; + +impl DerivationPreset { + /// The quantity of DerivationPreset's to fill cache with. + pub fn cache_filling_quantity(&self) -> usize { + match self { + Self::AccountVeci => CACHE_FILLING_QUANTITY_ACCOUNT_VECI, + Self::AccountMfa => CACHE_FILLING_QUANTITY_ACCOUNT_MFA, + Self::AccountRola => CACHE_FILLING_QUANTITY_ACCOUNT_ROLA, + Self::IdentityVeci => CACHE_FILLING_QUANTITY_IDENTITY_VECI, + Self::IdentityMfa => CACHE_FILLING_QUANTITY_IDENTITY_MFA, + Self::IdentityRola => CACHE_FILLING_QUANTITY_IDENTITY_ROLA, + } + } +} diff --git a/crates/sargon/src/factor_instances_provider/factor_instances_cache/factor_instances_cache.rs b/crates/sargon/src/factor_instances_provider/factor_instances_cache/factor_instances_cache.rs index 85705106a..b935ce6af 100644 --- a/crates/sargon/src/factor_instances_provider/factor_instances_cache/factor_instances_cache.rs +++ b/crates/sargon/src/factor_instances_provider/factor_instances_cache/factor_instances_cache.rs @@ -195,13 +195,18 @@ impl FactorInstancesCache { Ok(skipped_an_index_resulting_in_non_contiguity) } - /// Inserts all instance in `per_factor`. - pub fn insert_all( + pub fn insert( &self, - per_factor: &IndexMap, + per_derivation_preset_per_factor: impl Borrow< + InstancesPerDerivationPresetPerFactorSource, + >, ) -> Result<()> { - for (factor_source_id, instances) in per_factor { - _ = self.insert_for_factor(factor_source_id, instances)?; + let per_derivation_preset_per_factor = + per_derivation_preset_per_factor.borrow(); + for (_, per_factor) in per_derivation_preset_per_factor { + for (factor_source_id, instances) in per_factor { + _ = self.insert_for_factor(factor_source_id, instances)?; + } } Ok(()) } @@ -220,108 +225,265 @@ impl FactorInstancesCache { .max() } - /// Returns enough instances to satisfy the requested quantity for each factor source, - /// **OR LESS**, never more, and if less, it means we MUST derive more, and if we - /// must derive more, this function returns the quantities to derive for each factor source, - /// for each derivation preset, not only the originally requested one. - pub fn get_poly_factor_with_quantities( + /// Loads cached factor instances for the given network and factor source and + /// per derivation preset. The outcome is either a load from cache failure or + /// a `CachedInstancesWithQuantitiesOutcome` which is either a + /// `Satisfied` or `NotSatisfied` outcome. + /// + /// Satisfied means *fully satisfied*, i.e. all requested instances were + /// found in the cache. + /// + /// NotSatisfied means that the cache did not contain all the requested + /// instances, but it might have contained some of the quantity specified + /// per quantified derivation preset, and the rest must be derived, so + /// NotSatisfied contains the instances that were found in the cache and + /// the quantities to derive. + pub fn get( &self, factor_source_ids: &IndexSet, - originally_requested_quantified_derivation_preset: &QuantifiedDerivationPreset, + quantified_derivation_presets: &IdentifiedVecOf< + QuantifiedDerivationPreset, + >, network_id: NetworkID, ) -> Result { - let target_quantity = - originally_requested_quantified_derivation_preset.quantity; - let mut pf_instances = - IndexMap::::new(); - let mut pf_pdp_qty_to_derive = IndexMap::< - FactorSourceIDFromHash, - IndexMap, + let mut per_derivation_preset = IndexMap::< + DerivationPreset, + IndexMap< + FactorSourceIDFromHash, + CacheInstancesAndRemainingQuantityToDerive, + >, >::new(); - let mut is_quantity_satisfied_for_all_factor_sources = true; - for factor_source_id in factor_source_ids { - for preset in DerivationPreset::all() { - let index_agnostic_path = - preset.index_agnostic_path_on_network(network_id); + for preset in DerivationPreset::all() { + let mut per_factor_source = IndexMap::< + FactorSourceIDFromHash, + CacheInstancesAndRemainingQuantityToDerive, + >::new(); + + let cache_filling_quantity = preset.cache_filling_quantity(); + + let index_agnostic_path = + preset.index_agnostic_path_on_network(network_id); + + for factor_source_id in factor_source_ids { let for_preset = self .get_mono_factor(factor_source_id, index_agnostic_path) .unwrap_or_default(); + let count_in_cache = for_preset.len(); - if preset - == originally_requested_quantified_derivation_preset - .derivation_preset - { - let satisfies_requested_quantity = - count_in_cache >= target_quantity; - if satisfies_requested_quantity { - // The instances in the cache can satisfy the requested quantity - // for this factor source for this derivation preset - pf_instances.append_or_insert_to( - factor_source_id, - // Only take the first `target_quantity` instances - // to be used, the rest are not needed and should - // remain in the cache (later we will call delete on - // all those instances.) - for_preset.split_at(target_quantity).0, - ); + + let maybe_instances_with_remaining_qty_to_derive = + if let Some(quantified_derivation_preset) = + quantified_derivation_presets.get_id(preset) + { + // The `preset` was part of the originally requested preset + // with a target quantity. + let target_quantity = + quantified_derivation_preset.quantity; + + let is_quantity_satisfied = + count_in_cache >= target_quantity; + + if is_quantity_satisfied { + // The instances in the cache can satisfy the requested quantity + // for this factor source for this derivation preset + let instances_to_use_from_cache = + for_preset.split_at(target_quantity).0; + assert!(!instances_to_use_from_cache.is_empty()); + Some(CacheInstancesAndRemainingQuantityToDerive { + // Only take the first `target_quantity` instances + // to be used, the rest are not needed and should + // remain in the cache (later we will call delete on + // all those instances.) + instances_to_use_from_cache, + quantity_to_derive: 0, + }) + } else { + // Since we are deriving more we might as well ensure that the + // cache is filled with `CACHE_FILLING_QUANTITY` **AFTER** the + // requested quantity is satisfied, meaning we will not only + // derive `CACHE_FILLING_QUANTITY - count_in_cache`, instead we + // derive the `target_quantity` as well. + let quantity_to_derive = cache_filling_quantity + - count_in_cache + + target_quantity; + Some(CacheInstancesAndRemainingQuantityToDerive { + instances_to_use_from_cache: for_preset.clone(), + quantity_to_derive, + }) + } + } else if count_in_cache < cache_filling_quantity { + // Not requested derivation preset, calculate number + // of instances to derive IF we are going to derive anyway, + // we wanna FILL the cache for those derivation presets as well. + let quantity_to_derive = + cache_filling_quantity - count_in_cache; + + Some(CacheInstancesAndRemainingQuantityToDerive { + instances_to_use_from_cache: + FactorInstances::default(), + quantity_to_derive, + }) } else { - // The instances in the cache cannot satisfy the requested quantity - // we must derive more! - is_quantity_satisfied_for_all_factor_sources = false; - // Since we are deriving more we might as well ensure that the - // cache is filled with `CACHE_FILLING_QUANTITY` **AFTER** the - // requested quantity is satisfied, meaning we will not only - // derive `CACHE_FILLING_QUANTITY - count_in_cache`, instead we - // derive the `target_quantity` as well. - let quantity_to_derive = CACHE_FILLING_QUANTITY - - count_in_cache - + target_quantity; - pf_pdp_qty_to_derive.append_or_insert_element_to( - factor_source_id, - (preset, quantity_to_derive), - ); - // insert all instances to be used directly - pf_instances.append_or_insert_to( - factor_source_id, - for_preset.clone(), - ); - } - } else { - // Not originally requested derivation preset, calculate number - // of instances to derive IF we are going to derive anyway, - // we wanna FILL the cache for those derivation presets as well. - if count_in_cache < CACHE_FILLING_QUANTITY { - let qty_to_derive = - CACHE_FILLING_QUANTITY - count_in_cache; - pf_pdp_qty_to_derive.append_or_insert_element_to( - factor_source_id, - (preset, qty_to_derive), - ); - } + None + }; + if let Some(instances_with_remaining_qty_to_derive) = + maybe_instances_with_remaining_qty_to_derive + { + per_factor_source.insert( + *factor_source_id, + instances_with_remaining_qty_to_derive, + ); } } + if !per_factor_source.is_empty() { + per_derivation_preset.insert(preset, per_factor_source); + } } - let outcome = if is_quantity_satisfied_for_all_factor_sources { - CachedInstancesWithQuantitiesOutcome::Satisfied(pf_instances) + + let originally_request_presets = quantified_derivation_presets + .iter() + .map(|quantified_preset| quantified_preset.derivation_preset) + .collect::>(); + + let is_quantity_unsatisfied_for_any_requested = + per_derivation_preset + .iter() + .any(|(preset, pf)| { + /* Only lack of instances for originally requested presets is something which should cause the outcome of this reading from cache to be considered as `NotSatisfied` */ + let was_preset_originally_requested = originally_request_presets.contains(preset); + let need_to_derive_more = pf.iter().any(|(_, instances_and_remaining_qty_to_derive)| instances_and_remaining_qty_to_derive.quantity_to_derive > 0); + // We need to derive more instances for this derivation preset + was_preset_originally_requested && need_to_derive_more + }); + + let outcome = if is_quantity_unsatisfied_for_any_requested { + // The instances in the cache cannot satisfy the requested quantity + // we must derive more! + CachedInstancesWithQuantitiesOutcome::NotSatisfied( + CacheNotSatisfied { + cached_and_quantities_to_derive: per_derivation_preset, + }, + ) } else { - CachedInstancesWithQuantitiesOutcome::NotSatisfied { - partial_instances: pf_instances, - quantities_to_derive: pf_pdp_qty_to_derive, - } + CachedInstancesWithQuantitiesOutcome::Satisfied(CacheSatisfied { + cached: per_derivation_preset + .into_iter() + // Satisfied, but `per_derivation_preset` contains ALL Presets (in case of `NotSatisfied` - we are cache filling), + // so we filter out only the originally requested ones. + .filter(|(preset, _)| { + originally_request_presets.contains(preset) + }) + .map(|(preset, per_factor)| { + ( + preset, + per_factor.into_iter() + .map(|(fsid, instances_and_remaining_qty_to_derive)| { + (fsid, instances_and_remaining_qty_to_derive.instances_to_use_from_cache) + }) + .collect::>(), + ) + }) + .collect::(), + }) }; + Ok(outcome) } } -#[derive(Debug, PartialEq, Eq, enum_as_inner::EnumAsInner)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CacheInstancesAndRemainingQuantityToDerive { + pub instances_to_use_from_cache: FactorInstances, // if empty then this was not a requested derivation preset, but we are cache filling and found `quantity_to_derive` needed to fill cache. + pub quantity_to_derive: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CacheNotSatisfied { + /// PER DerivationPreset => PER FactorSourceID => CacheInstancesAndRemainingQuantityToDerive + pub cached_and_quantities_to_derive: IndexMap< + DerivationPreset, + IndexMap< + FactorSourceIDFromHash, + CacheInstancesAndRemainingQuantityToDerive, + >, + >, +} +impl CacheNotSatisfied { + fn map( + &self, + extract: impl Fn( + ( + FactorSourceIDFromHash, + CacheInstancesAndRemainingQuantityToDerive, + ), + ) -> Option<(FactorSourceIDFromHash, R)>, + ) -> IndexMap> { + self.cached_and_quantities_to_derive + .clone() + .into_iter() + .filter_map(|(preset, v)| { + let per_factor = v + .into_iter() + .filter_map(|(x, y)| extract((x, y))) + .collect::>(); + + if per_factor.is_empty() { + None + } else { + Some((preset, per_factor)) + } + }) + .collect() + } + + pub fn cached_instances_to_use( + &self, + ) -> InstancesPerDerivationPresetPerFactorSource { + self.map(|(x, y)| { + let instances = y.instances_to_use_from_cache; + if instances.is_empty() { + None + } else { + Some((x, instances)) + } + }) + } + + pub fn remaining_quantities_to_derive(&self) -> QuantitiesToDerive { + self.map(|(x, y)| { + if y.quantity_to_derive > 0 { + Some((x, y.quantity_to_derive)) + } else { + None + } + }) + } +} +pub type QuantitiesToDerive = + IndexMap>; + +pub type InstancesPerDerivationPresetPerFactorSource = IndexMap< + DerivationPreset, + IndexMap, +>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CacheSatisfied { + /// PER DerivationPreset => PER FactorSourceID => FactorInstances + pub cached: IndexMap< + DerivationPreset, + IndexMap, + >, +} + +#[derive(Debug, Clone, PartialEq, Eq, enum_as_inner::EnumAsInner)] pub enum CachedInstancesWithQuantitiesOutcome { - Satisfied(IndexMap), - NotSatisfied { - partial_instances: IndexMap, - quantities_to_derive: - IndexMap>, - }, + Satisfied(CacheSatisfied), + NotSatisfied(CacheNotSatisfied), } impl FactorInstancesCache { @@ -338,43 +500,50 @@ impl FactorInstancesCache { pub fn delete( &self, - pf_instances: &IndexMap, + pdp_pf_instances: &InstancesPerDerivationPresetPerFactorSource, ) { - for (factor_source_id, instances_to_delete) in pf_instances { - if instances_to_delete.is_empty() { - continue; - } - let mut binding = self.map.write().unwrap(); - let existing_for_factor = binding - .get_mut(factor_source_id) - .expect("expected to delete factors"); - - let instances_to_delete_by_path = - InstancesByAgnosticPath::from(instances_to_delete.clone()); - for (index_agnostic_path, instances_to_delete) in - instances_to_delete_by_path - { - let instances_to_delete = IndexSet::< - HierarchicalDeterministicFactorInstance, - >::from_iter( - instances_to_delete.into_iter() - ); - - let existing_for_path = existing_for_factor - .get(&index_agnostic_path) - .expect("expected to delete") - .factor_instances(); - - if !existing_for_path.is_superset(&instances_to_delete) { - panic!("Programmer error! Some of the factors to delete were not in cache!"); + for (preset, pf_instances) in pdp_pf_instances { + for (factor_source_id, instances_to_delete) in pf_instances { + if instances_to_delete.is_empty() { + continue; } - let to_keep = existing_for_path - .symmetric_difference(&instances_to_delete) - .cloned() - .collect::(); + let mut binding = self.map.write().unwrap(); + let existing_for_factor = binding + .get_mut(factor_source_id) + .expect("expected to delete factors"); - // replace - existing_for_factor.insert(index_agnostic_path, to_keep); + let instances_to_delete_by_path = + InstancesByAgnosticPath::from(instances_to_delete.clone()); + for (index_agnostic_path, instances_to_delete) in + instances_to_delete_by_path + { + assert_eq!( + DerivationPreset::try_from(index_agnostic_path) + .unwrap(), + *preset + ); + let instances_to_delete = IndexSet::< + HierarchicalDeterministicFactorInstance, + >::from_iter( + instances_to_delete.into_iter() + ); + + let existing_for_path = existing_for_factor + .get(&index_agnostic_path) + .expect("expected to delete") + .factor_instances(); + + if !existing_for_path.is_superset(&instances_to_delete) { + panic!("Programmer error! Some of the factors to delete were not in cache!"); + } + let to_keep = existing_for_path + .symmetric_difference(&instances_to_delete) + .cloned() + .collect::(); + + // replace + existing_for_factor.insert(index_agnostic_path, to_keep); + } } } @@ -437,6 +606,19 @@ impl FactorInstancesCache { #[cfg(test)] impl FactorInstancesCache { + pub fn get_poly_factor_with_quantities( + &self, + factor_source_ids: &IndexSet, + quantified_derivation_preset: &QuantifiedDerivationPreset, + network_id: NetworkID, + ) -> Result { + self.get( + factor_source_ids, + &IdentifiedVecOf::just(*quantified_derivation_preset), + network_id, + ) + } + /// Queries the cache to see if the cache is full for factor_source_id for /// each DerivationPreset pub fn is_full( @@ -444,24 +626,25 @@ impl FactorInstancesCache { network_id: NetworkID, factor_source_id: FactorSourceIDFromHash, ) -> bool { - DerivationPreset::all() + let cache_filling_quantities = DerivationPreset::all() .into_iter() .map(|preset| { - self.get_poly_factor_with_quantities( - &IndexSet::just(factor_source_id), - &QuantifiedDerivationPreset::new( - preset, - CACHE_FILLING_QUANTITY, - ), - network_id, - ) - }) - .all(|outcome| { - matches!( - outcome, - Ok(CachedInstancesWithQuantitiesOutcome::Satisfied(_)) + QuantifiedDerivationPreset::new( + preset, + preset.cache_filling_quantity(), ) }) + .collect::>(); + + let Ok(outcome) = self.get( + &IndexSet::just(factor_source_id), + &cache_filling_quantities, + network_id, + ) else { + return false; + }; + + outcome.is_satisfied() } pub fn assert_is_full( @@ -537,9 +720,12 @@ mod tests { fn insert_all_factor_source_id_discrepancy_is_err() { let sut = SUT::default(); assert!(sut - .insert_all(&IndexMap::kv( - FactorSourceIDFromHash::sample_password_other(), - FactorInstances::sample() + .insert(IndexMap::kv( + DerivationPreset::AccountMfa, + IndexMap::kv( + FactorSourceIDFromHash::sample_password_other(), + FactorInstances::sample() + ) )) .is_err()) } @@ -776,7 +962,10 @@ mod tests { ) .unwrap(); - sut.delete(&IndexMap::kv(fsid, instances)); + sut.delete(&IndexMap::kv( + DerivationPreset::AccountMfa, + IndexMap::kv(fsid, instances), + )); } #[test] @@ -825,7 +1014,7 @@ mod tests { .unwrap(); } - sut.delete(&to_delete); + sut.delete(&IndexMap::kv(DerivationPreset::AccountVeci, to_delete)); let path = &IndexAgnosticPath::new( NetworkID::Mainnet, diff --git a/crates/sargon/src/factor_instances_provider/factor_instances_cache/keyed_instances.rs b/crates/sargon/src/factor_instances_provider/factor_instances_cache/keyed_instances.rs index b3b0f1de5..64f805f28 100644 --- a/crates/sargon/src/factor_instances_provider/factor_instances_cache/keyed_instances.rs +++ b/crates/sargon/src/factor_instances_provider/factor_instances_cache/keyed_instances.rs @@ -2,11 +2,12 @@ use std::borrow::Borrow; use crate::prelude::*; -pub struct KeyedInstances( +#[derive(Debug)] +pub struct KeyedInstances( pub IndexMap, ); -impl KeyedInstances { +impl KeyedInstances { pub fn validate_from_source( &self, factor_source_id: impl Borrow, @@ -34,9 +35,19 @@ impl KeyedInstances { pub fn new(map: IndexMap) -> Self { Self(map) } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } -impl IntoIterator for KeyedInstances { +impl IntoIterator + for KeyedInstances +{ type Item = as IntoIterator>::Item; type IntoIter = as IntoIterator>::IntoIter; diff --git a/crates/sargon/src/factor_instances_provider/next_index_assigner/next_derivation_entity_index_profile_analyzing_assigner.rs b/crates/sargon/src/factor_instances_provider/next_index_assigner/next_derivation_entity_index_profile_analyzing_assigner.rs index d2994c9e4..f620daf1a 100644 --- a/crates/sargon/src/factor_instances_provider/next_index_assigner/next_derivation_entity_index_profile_analyzing_assigner.rs +++ b/crates/sargon/src/factor_instances_provider/next_index_assigner/next_derivation_entity_index_profile_analyzing_assigner.rs @@ -163,6 +163,60 @@ impl NextDerivationEntityIndexProfileAnalyzingAssigner { .max() } + /// Returns the max index of true ROLA keys of securified accounts with + /// factor source matching factor_source_id - if any. + fn max_account_rola( + &self, + factor_source_id: FactorSourceIDFromHash, + ) -> Option { + self.securified_accounts_on_network + .clone() + .into_iter() + .map(|e: SecurifiedAccount| { + e.securified_entity_control + .authentication_signing_factor_instance() + }) + .filter(|f| f.factor_source_id() == factor_source_id) + .map(|fi| { + AssertMatches { + network_id: self.network_id, + key_kind: CAP26KeyKind::AuthenticationSigning, + entity_kind: CAP26EntityKind::Account, + key_space: KeySpace::Securified, + } + .matches(&fi.derivation_path()) + .index() + }) + .max() + } + + /// Returns the max index of true ROLA keys of securified personas with + /// factor source matching factor_source_id - if any. + fn max_identity_rola( + &self, + factor_source_id: FactorSourceIDFromHash, + ) -> Option { + self.securified_personas_on_network + .clone() + .into_iter() + .map(|e: SecurifiedPersona| { + e.securified_entity_control + .authentication_signing_factor_instance() + }) + .filter(|f| f.factor_source_id() == factor_source_id) + .map(|fi| { + AssertMatches { + network_id: self.network_id, + key_kind: CAP26KeyKind::AuthenticationSigning, + entity_kind: CAP26EntityKind::Identity, + key_space: KeySpace::Securified, + } + .matches(&fi.derivation_path()) + .index() + }) + .max() + } + /// Returns the Max Derivation Entity Index of Securified Persona controlled /// by `factor_source_id`, or `None` if no securified persona controlled by that /// factor source id found. @@ -220,6 +274,12 @@ impl NextDerivationEntityIndexProfileAnalyzingAssigner { DerivationPreset::IdentityMfa => { self.max_identity_mfa(factor_source_id) } + DerivationPreset::AccountRola => { + self.max_account_rola(factor_source_id) + } + DerivationPreset::IdentityRola => { + self.max_identity_rola(factor_source_id) + } }; let Some(max) = max else { return Ok(None) }; @@ -513,4 +573,61 @@ mod tests { .ok() ) } + + #[test] + fn test_next_account_rola_at_7_is_8() { + let preset = DerivationPreset::AccountRola; + let network_id = NetworkID::Mainnet; + let sut = SUT::new( + network_id, + Arc::new(Profile::sample_from( + FactorSource::sample_all(), + [ + &Account::sample_at(8), /* unsecurified, should not interfere */ + &Account::sample_at(7), + ], + [], + )), + ); + type F = FactorSourceIDFromHash; + { + let fid = F::sample_device(); + let next = sut + .next(fid, preset.index_agnostic_path_on_network(network_id)) + .unwrap(); + + assert_eq!( + next, + HDPathComponent::from_local_key_space(8, KeySpace::Securified) + .ok() + ); + } + } + + #[test] + fn test_next_identity_rola_at_7_is_8() { + let preset = DerivationPreset::IdentityRola; + let network_id = NetworkID::Mainnet; + let sut = SUT::new( + network_id, + Arc::new(Profile::sample_from( + FactorSource::sample_all(), + [], + [&Persona::sample_at(7)], + )), + ); + type F = FactorSourceIDFromHash; + { + let fid = F::sample_device(); + let next = sut + .next(fid, preset.index_agnostic_path_on_network(network_id)) + .unwrap(); + + assert_eq!( + next, + HDPathComponent::from_local_key_space(8, KeySpace::Securified) + .ok() + ) + } + } } diff --git a/crates/sargon/src/factor_instances_provider/next_index_assigner/next_derivation_entity_index_with_ephemeral_offsets.rs b/crates/sargon/src/factor_instances_provider/next_index_assigner/next_derivation_entity_index_with_ephemeral_offsets.rs index 0cddfde87..c99862764 100644 --- a/crates/sargon/src/factor_instances_provider/next_index_assigner/next_derivation_entity_index_with_ephemeral_offsets.rs +++ b/crates/sargon/src/factor_instances_provider/next_index_assigner/next_derivation_entity_index_with_ephemeral_offsets.rs @@ -120,8 +120,10 @@ mod tests { vec![ HDPathComponent::unsecurified_hardened(0u32).unwrap(), // Account Veci HDPathComponent::Securified(SecurifiedU30::ZERO), // Account MFA + HDPathComponent::Securified(SecurifiedU30::ZERO), // Account Rola HDPathComponent::unsecurified_hardened(0u32).unwrap(), // Identify Veci HDPathComponent::Securified(SecurifiedU30::ZERO), // Identity MFA + HDPathComponent::Securified(SecurifiedU30::ZERO), // Identity Rola ] ); } diff --git a/crates/sargon/src/factor_instances_provider/provider/factor_instances_provider.rs b/crates/sargon/src/factor_instances_provider/provider/factor_instances_provider.rs index 0592a85a3..511041830 100644 --- a/crates/sargon/src/factor_instances_provider/provider/factor_instances_provider.rs +++ b/crates/sargon/src/factor_instances_provider/provider/factor_instances_provider.rs @@ -53,11 +53,31 @@ impl FactorInstancesProvider { ) -> Result<( InstancesInCacheConsumer, InternalFactorInstancesProviderOutcome, + )> { + self.provide_for_presets( + IdentifiedVecOf::just(quantified_derivation_preset), + derivation_purpose, + ) + .await + } + + pub async fn provide_for_presets( + self, + quantified_derivation_presets: IdentifiedVecOf< + QuantifiedDerivationPreset, + >, + derivation_purpose: DerivationPurpose, + ) -> Result<( + InstancesInCacheConsumer, + InternalFactorInstancesProviderOutcome, )> { let mut _self = self; _self - ._provide(quantified_derivation_preset, derivation_purpose) + ._provide_for_presets( + quantified_derivation_presets, + derivation_purpose, + ) .await } } @@ -68,12 +88,9 @@ impl FactorInstancesProvider { impl FactorInstancesProvider { fn make_instances_in_cache_consumer( &self, - instances_per_factor_sources_to_delete: IndexMap< - FactorSourceIDFromHash, - FactorInstances, - >, + instances_to_delete: InstancesPerDerivationPresetPerFactorSource, ) -> InstancesInCacheConsumer { - let instances_clone = instances_per_factor_sources_to_delete.clone(); + let instances_clone = instances_to_delete.clone(); let cache_client_clone = self.cache_client.clone(); InstancesInCacheConsumer::new(move || { let cache_client_clone_clone = cache_client_clone.clone(); @@ -84,9 +101,11 @@ impl FactorInstancesProvider { }) } - async fn _provide( + async fn _provide_for_presets( &mut self, - quantified_derivation_preset: QuantifiedDerivationPreset, + quantified_derivation_presets: IdentifiedVecOf< + QuantifiedDerivationPreset, + >, derivation_purpose: DerivationPurpose, ) -> Result<( InstancesInCacheConsumer, @@ -94,11 +113,12 @@ impl FactorInstancesProvider { )> { let factor_sources = self.factor_sources.clone(); let network_id = self.network_id; + let cached = self .cache_client - .get_poly_factor_with_quantities( + .get( &factor_sources.iter().map(|f| f.id_from_hash()).collect(), - &quantified_derivation_preset, + quantified_derivation_presets.clone(), network_id, ) .await?; @@ -111,7 +131,9 @@ impl FactorInstancesProvider { // will be deleted from the cache, they are still present in the cache now // and will continue to be present until the `consume()` is called. let instances_in_cache_consumer = self - .make_instances_in_cache_consumer(enough_instances.clone()); + .make_instances_in_cache_consumer( + enough_instances.clone().cached, + ); Ok(( instances_in_cache_consumer, InternalFactorInstancesProviderOutcome::satisfied_by_cache( @@ -119,15 +141,10 @@ impl FactorInstancesProvider { ), )) } - CachedInstancesWithQuantitiesOutcome::NotSatisfied { - quantities_to_derive, - partial_instances, - } => { - warn!("NotSatisfied, quantities_to_derive: {:?}, partial_instances: {:?}", quantities_to_derive, partial_instances); + CachedInstancesWithQuantitiesOutcome::NotSatisfied(unsatisfied) => { self.derive_more_and_cache( - quantified_derivation_preset, - partial_instances, - quantities_to_derive, + &quantified_derivation_presets, + unsatisfied, derivation_purpose, ) .await @@ -137,47 +154,52 @@ impl FactorInstancesProvider { async fn derive_more_and_cache( &mut self, - quantified_derivation_preset: QuantifiedDerivationPreset, - pf_found_in_cache_leq_requested: IndexMap< - FactorSourceIDFromHash, - FactorInstances, - >, - pf_pdp_qty_to_derive: IndexMap< - FactorSourceIDFromHash, - IndexMap, + requested_quantified_derivation_presets: &IdentifiedVecOf< + QuantifiedDerivationPreset, >, + not_satisfied: CacheNotSatisfied, derivation_purpose: DerivationPurpose, ) -> Result<( InstancesInCacheConsumer, InternalFactorInstancesProviderOutcome, )> { - let pf_newly_derived = self - .derive_more(pf_pdp_qty_to_derive, derivation_purpose) + let remaining_quantities_to_derive = + not_satisfied.remaining_quantities_to_derive(); + + assert!(!remaining_quantities_to_derive.is_empty()); + + let pdp_pf_newly_derived = self + .derive_more(remaining_quantities_to_derive, derivation_purpose) .await?; + assert!(!pdp_pf_newly_derived.is_empty(),); + + let pdp_pf_found_in_cache_leq_requested = + not_satisfied.cached_instances_to_use(); + let Split { - pf_to_use_directly, - pf_to_cache, + pdp_pf_to_use_directly, + pdp_pf_to_cache, } = self.split( - &quantified_derivation_preset, - &pf_found_in_cache_leq_requested, - &pf_newly_derived, + requested_quantified_derivation_presets, + &pdp_pf_found_in_cache_leq_requested, + &pdp_pf_newly_derived, ); let instances_in_cache_consumer = self .make_instances_in_cache_consumer( - pf_found_in_cache_leq_requested.clone(), + pdp_pf_found_in_cache_leq_requested.clone(), ); - self.cache_client.insert_all(&pf_to_cache).await?; + self.cache_client.insert(&pdp_pf_to_cache).await?; let outcome = InternalFactorInstancesProviderOutcome::transpose( - pf_to_cache, - pf_to_use_directly, - pf_found_in_cache_leq_requested, - pf_newly_derived, + pdp_pf_to_cache, + pdp_pf_to_use_directly, + pdp_pf_found_in_cache_leq_requested, + pdp_pf_newly_derived, ); - let outcome = outcome; + Ok((instances_in_cache_consumer, outcome)) } @@ -185,88 +207,167 @@ impl FactorInstancesProvider { /// based on the originally requested quantity. fn split( &self, - originally_requested_quantified_derivation_preset: &QuantifiedDerivationPreset, - pf_found_in_cache_leq_requested: &IndexMap< - FactorSourceIDFromHash, - FactorInstances, + requested_quantified_derivation_presets: &IdentifiedVecOf< + QuantifiedDerivationPreset, >, - pf_newly_derived: &IndexMap, + pdp_pf_found_in_cache_leq_requested: &InstancesPerDerivationPresetPerFactorSource, + pdp_pf_newly_derived: &InstancesPerDerivationPresetPerFactorSource, ) -> Split { + let derivation_presets_of_instances_to_merge = + pdp_pf_found_in_cache_leq_requested + .keys() + .chain(pdp_pf_newly_derived.keys()) + .cloned() + .collect::>(); + // Start by merging the instances found in cache and the newly derived instances, // into a single collection of instances per factor source, with the // instances from cache first in the list (per factor), and then the newly derived. // this is important so that we consume the instances from cache first. - let pf_derived_appended_to_from_cache = self - .factor_sources - .clone() - .into_iter() - .map(|f| f.id_from_hash()) - .map(|factor_source_id| { - let mut merged = IndexSet::new(); - let from_cache = pf_found_in_cache_leq_requested - .get(&factor_source_id) + let pdp_pf_derived_appended_to_from_cache = derivation_presets_of_instances_to_merge + .iter() + .filter_map(|preset| { + let pf_found_in_cache_leq_requested = + pdp_pf_found_in_cache_leq_requested + .get(preset) + .cloned() + // can be nil -> empty, if no instance was found in cache for this preset! + .unwrap_or_default(); + let pf_newly_derived = pdp_pf_newly_derived + .get(preset) .cloned() + // can be nil -> empty, if we did not derive any new instance for this preset! .unwrap_or_default(); - let newly_derived = pf_newly_derived - .get(&factor_source_id) - .cloned() - .unwrap_or_default(); - // IMPORTANT: Must put instances from cache **first**... - merged.extend(from_cache); - // ... and THEN the newly derived, so we consume the ones with - // lower index from cache first. - merged.extend(newly_derived); - (factor_source_id, FactorInstances::from(merged)) + if pf_found_in_cache_leq_requested.is_empty() + && pf_newly_derived.is_empty() + { + return None; + } + + let pf_instances = self + .factor_sources + .clone() + .into_iter() + .map(|f| f.id_from_hash()) + .filter_map(|factor_source_id| { + let from_cache = pf_found_in_cache_leq_requested + .get(&factor_source_id) + .cloned() + .unwrap_or_default(); + let newly_derived = pf_newly_derived + .get(&factor_source_id) + .cloned() + .unwrap_or_default(); + + if from_cache.is_empty() && newly_derived.is_empty() { + return None; + } + + let mut merged = IndexSet::new(); + // IMPORTANT: Must put instances from cache **first**... + merged.extend(from_cache); + // ... and THEN the newly derived, so we consume the ones with + // lower index from cache first. + merged.extend(newly_derived); + assert!( + merged + .clone() + .into_iter() + .all(|f| + DerivationPreset::try_from(f.derivation_path().agnostic()).unwrap() == *preset + ) + ); + assert!(!merged.is_empty()); + Some((factor_source_id, FactorInstances::from(merged))) + + }) + .collect::>(); + + assert!(!pf_instances.is_empty()); + Some((*preset, pf_instances)) + }) - .collect::>(); + .collect::(); + + let mut pdp_pf_to_use_directly = + InstancesPerDerivationPresetPerFactorSource::new(); + let mut pdp_pf_to_cache = + InstancesPerDerivationPresetPerFactorSource::new(); - let mut pf_to_use_directly = IndexMap::new(); - let mut pf_to_cache = - IndexMap::::new(); - let quantity_originally_requested = - originally_requested_quantified_derivation_preset.quantity; - let preset_originally_requested = - originally_requested_quantified_derivation_preset.derivation_preset; + let originally_requested_presets = + requested_quantified_derivation_presets + .iter() + .map(|qdp| qdp.derivation_preset) + .collect::>(); // Using the merged map, split the instances into those to use directly and those to cache. - for (factor_source_id, instances) in - pf_derived_appended_to_from_cache.clone().into_iter() + for (preset, pf_derived_appended_to_from_cache) in + pdp_pf_derived_appended_to_from_cache { - let mut instances_by_derivation_preset = - InstancesByDerivationPreset::from(instances); + let mut pf_to_cache = + IndexMap::::new(); + + let mut pf_to_use_directly = IndexMap::new(); - if let Some(instances_relevant_to_use_directly_with_abundance) = - instances_by_derivation_preset - .remove(preset_originally_requested) + for (factor, instances) in + pf_derived_appended_to_from_cache.clone().into_iter() { - let (to_use_directly, to_cache) = - instances_relevant_to_use_directly_with_abundance - .split_at(quantity_originally_requested); - pf_to_use_directly.insert(factor_source_id, to_use_directly); - pf_to_cache.insert(factor_source_id, to_cache); + assert!(instances.factor_instances().into_iter().all(|f| { + DerivationPreset::try_from(f.derivation_path().agnostic()) + .unwrap() + == preset + })); + + assert!(instances + .factor_instances() + .into_iter() + .all(|f| f.factor_source_id() == factor)); + + if originally_requested_presets.contains(&preset) { + // might have to split + + let requested_quantified_derivation_preset = + requested_quantified_derivation_presets + .get_id(preset) + .unwrap(); + + let instances_relevant_to_use_directly_with_abundance = + instances; + + let originally_requested_quantity = + requested_quantified_derivation_preset.quantity; + + let (to_use_directly, to_cache) = + instances_relevant_to_use_directly_with_abundance + .split_at(originally_requested_quantity); + + pf_to_use_directly.insert(factor, to_use_directly); + + pf_to_cache.insert(factor, to_cache); + } else { + // easy case, we don't want to use this directly at all + // meaning all + pf_to_cache.insert(factor, instances); + // we do not add any FactorInstances to `pf_to_use_directly` for this factor + } } - pf_to_cache.append_or_insert_to( - factor_source_id, - instances_by_derivation_preset.all_instances(), - ); + pdp_pf_to_use_directly.insert(preset, pf_to_use_directly); + pdp_pf_to_cache.insert(preset, pf_to_cache); } Split { - pf_to_use_directly, - pf_to_cache, + pdp_pf_to_use_directly, + pdp_pf_to_cache, } } pub(super) async fn derive_more( &self, - pf_pdp_quantity_to_derive: IndexMap< - FactorSourceIDFromHash, - IndexMap, - >, + quantities_to_derive: QuantitiesToDerive, derivation_purpose: DerivationPurpose, - ) -> Result> { + ) -> Result { let factor_sources = self.factor_sources.clone(); let network_id = self.network_id; @@ -277,72 +378,130 @@ impl FactorInstancesProvider { cache_snapshot, ); - let pf_paths = pf_pdp_quantity_to_derive + let per_preset_per_factor_paths = quantities_to_derive .into_iter() - .map(|(factor_source_id, pdp_quantity_to_derive)| { - let paths = pdp_quantity_to_derive + .filter(|(_, per_factor_source)| !per_factor_source.is_empty()) + .map(|(derivation_preset, per_factor_source)| { + let per_factor_paths = per_factor_source .into_iter() - .map(|(derivation_preset, qty)| { + .map(|(factor_source_id, qty)| { + assert!(qty > 0); // `qty` many paths - (0..qty) + let paths = (0..qty) .map(|_| { let index_agnostic_path = derivation_preset .index_agnostic_path_on_network(network_id); - next_index_assigner + let path = next_index_assigner .next(factor_source_id, index_agnostic_path) .map(|index| { DerivationPath::from(( index_agnostic_path, index, )) - }) + })?; + Ok(path) }) - .collect::>>() - }) - .collect::>>>()?; + .collect::>>()?; - // flatten (I was unable to use `flat_map` above combined with `Result`...) - let paths = - paths.into_iter().flatten().collect::>(); + assert!(!paths.is_empty()); + + Ok((factor_source_id, paths)) + }) + .collect::, + >, + >>()?; - Ok((factor_source_id, paths)) + Ok((derivation_preset, per_factor_paths)) }) .collect::>, + IndexMap< + DerivationPreset, + IndexMap>, + >, >>()?; + let mut per_factor_paths = + IndexMap::>::new(); + + for (_, pf) in per_preset_per_factor_paths.clone() { + for (factor_source_id, paths) in pf { + per_factor_paths.append_or_insert_to(factor_source_id, paths); + } + } + let interactor = self.interactor.clone(); + let collector = KeysCollector::new( factor_sources, - pf_paths.clone(), + per_factor_paths.clone(), interactor, derivation_purpose, )?; let pf_derived = collector.collect_keys().await.factors_by_source; - let mut pf_instances = - IndexMap::::new(); + for (k, v) in pf_derived.iter() { + let requested = per_factor_paths.get(k).unwrap(); + if v.len() < requested.len() { + return Err(CommonError::TooFewFactorInstancesDerived); + } + } - for (factor_source_id, paths) in pf_paths { - let derived_for_factor = pf_derived - .get(&factor_source_id) - .cloned() - .unwrap_or_default(); // if None -> Empty -> fail below. - if derived_for_factor.len() < paths.len() { - return Err(CommonError::FactorInstancesProviderDidNotDeriveEnoughFactors); + assert!(!pf_derived.is_empty()); + + let pf_pdp_derived = pf_derived + .into_iter() + .filter_map(|(k, v)| { + if v.is_empty() { + return None; + } + let instances = FactorInstances::from(v); + let instances = InstancesByDerivationPreset::from(instances).0; + if instances.is_empty() { + None + } else { + Some((k, instances)) + } + }) + .collect::, + >>(); + + assert!(!pf_pdp_derived.is_empty()); + + let mut pdp_pf_instances = IndexMap::< + DerivationPreset, + IndexMap, + >::new(); + + for (factor_source_id, pdp) in pf_pdp_derived { + assert!(!pdp.is_empty()); + for (preset, instances) in pdp { + assert!(!instances.is_empty()); + pdp_pf_instances.append_or_insert_to( + preset, + IndexMap::::kv( + factor_source_id, + instances, + ), + ); } - pf_instances.insert( - factor_source_id, - derived_for_factor.into_iter().collect::(), - ); } - Ok(pf_instances) + Ok(pdp_pf_instances) } } +/// A split of FactorInstances per DerivationPreset per FactorSource +/// is splitting and newly derived FactorInstances and then pre-pending +/// any instances found in cache to `pdp_pf_to_use_directly`. struct Split { - pf_to_use_directly: IndexMap, - pf_to_cache: IndexMap, + /// Per DerivationPreset per FactorSource instances to use directly + pdp_pf_to_use_directly: InstancesPerDerivationPresetPerFactorSource, + /// Per DerivationPreset per FactorSource instances to cache + pdp_pf_to_cache: InstancesPerDerivationPresetPerFactorSource, } diff --git a/crates/sargon/src/factor_instances_provider/provider/factor_instances_provider_unit_tests.rs b/crates/sargon/src/factor_instances_provider/provider/factor_instances_provider_unit_tests.rs index 70ca149f6..411bf52db 100644 --- a/crates/sargon/src/factor_instances_provider/provider/factor_instances_provider_unit_tests.rs +++ b/crates/sargon/src/factor_instances_provider/provider/factor_instances_provider_unit_tests.rs @@ -70,20 +70,37 @@ impl SargonOS { account_address: AccountAddress, security_structure_of_factor_instances: SecurityStructureOfFactorInstances, ) -> Result { - let mut account = self.account_by_address(account_address).unwrap(); + let entity = self.__OFFLINE_ONLY_securify_entity_without_saving( + AddressOfAccountOrPersona::Account(account_address), + security_structure_of_factor_instances, + )?; + + entity + .clone() + .as_account_entity() + .ok_or(CommonError::ExpectedAccountButGotPersona { + address: entity.address().to_string(), + }) + .cloned() + } + + pub(crate) fn __OFFLINE_ONLY_securify_entity_without_saving( + &self, + entity_address: AddressOfAccountOrPersona, + security_structure_of_factor_instances: SecurityStructureOfFactorInstances, + ) -> Result { + let mut entity = self.entity_by_address(entity_address)?; let veci: HierarchicalDeterministicFactorInstance; let access_controller_address: AccessControllerAddress; - match account.security_state() { + match entity.security_state() { EntitySecurityState::Unsecured { value } => { veci = value.transaction_signing.clone(); // THIS IS COMPLETELY WRONG! // The real solution should get the AccessControllerAddress on chain access_controller_address = - AccessControllerAddress::with_node_id_of( - &account.address(), - ); + AccessControllerAddress::with_node_id_of(&entity.address()); } EntitySecurityState::Securified { value } => { veci = value.veci.clone().unwrap(); @@ -97,11 +114,11 @@ impl SargonOS { security_structure_of_factor_instances, )?; - account.security_state = EntitySecurityState::Securified { + entity.set_security_state(EntitySecurityState::Securified { value: securified_control, - }; + })?; - Ok(account) + Ok(entity) } /// Uses FactorInstancesProvider to get factor instances for the `shield`. @@ -112,12 +129,34 @@ impl SargonOS { account_addresses: IndexSet, shield: &SecurityStructureOfFactorSources, ) -> Result<(Accounts, FactorInstancesProviderOutcome)> { - account_addresses + let (entities, outcome) = self + .__OFFLINE_ONLY_securify_entities( + account_addresses.into_iter().map(Into::into).collect(), + shield, + ) + .await?; + + let accounts = entities + .into_iter() + .map(|e| e.into_account_entity().unwrap()) + .collect(); + Ok((accounts, outcome)) + } + + async fn __OFFLINE_ONLY_securify_entities( + &self, + entity_addresses: IndexSet, + shield: &SecurityStructureOfFactorSources, + ) -> Result<( + IdentifiedVecOf, + FactorInstancesProviderOutcome, + )> { + entity_addresses .iter() - .for_each(|a| assert!(self.account_by_address(*a).is_ok())); + .for_each(|a| assert!(self.entity_by_address(*a).is_ok())); let outcome = self.make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - account_addresses.clone(), + entity_addresses.clone().into_iter().map(Into::into).collect(), shield.clone()).await?; let ( @@ -132,35 +171,36 @@ impl SargonOS { // consume! instances_in_cache_consumer.consume().await?; - let securified_accounts = account_addresses + let securified_entities = entity_addresses .into_iter() - .map(|account_address| { + .map(|entity_address| { let security_structure_of_factor_instances = security_structures_of_factor_instances - .shift_remove(&account_address) + .shift_remove(&entity_address) .unwrap(); - // Production ready code should batch update accounts, submit batch transaction to + // Production ready code should batch update entities, submit batch transaction to // network, and then batch update all accounts in Profile. - self.__OFFLINE_ONLY_securify_account_without_saving( - account_address, + self.__OFFLINE_ONLY_securify_entity_without_saving( + entity_address, security_structure_of_factor_instances, ) }) - .collect::>()?; + .collect::>>()?; assert!(security_structures_of_factor_instances.is_empty()); // Assert that none of the NEW FactorInstances collide with the existing ones self.profile() .unwrap() - .assert_new_factor_instances_not_already_used( - securified_accounts.clone(), + .assert_new_factor_instances_not_already_used_erased( + securified_entities.clone(), )?; - self.update_entities(securified_accounts.clone()).await?; + self.update_entities_erased(securified_entities.clone()) + .await?; Ok(( - securified_accounts.into_iter().collect(), + securified_entities.into_iter().collect(), derivation_outcome, )) } @@ -481,8 +521,11 @@ async fn cache_is_unchanged_in_case_of_failure() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); let all_accounts = os .profile() @@ -510,6 +553,7 @@ async fn cache_is_unchanged_in_case_of_failure() { .clone() .into_iter() .map(|a| a.address()) + .map(AddressOfAccountOrPersona::from) .collect(), shield_0.clone()).await.unwrap(); @@ -581,6 +625,7 @@ async fn cache_is_unchanged_in_case_of_failure() { .clone() .into_iter() .map(|a| a.address()) + .map(AddressOfAccountOrPersona::from) .collect(), shield_0.clone() ) @@ -633,9 +678,12 @@ async fn test_assert_factor_instances_invalid() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); - let (security_structure_of_fis, _, _) = os.make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome(IndexSet::from_iter([alice.address()]), shield_0.clone()).await.unwrap(); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); + let (security_structure_of_fis, _, _) = os.make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome(IndexSet::from_iter([AddressOfAccountOrPersona::from(alice.address())]), shield_0.clone()).await.unwrap(); let security_structure_of_fi = security_structure_of_fis.values().next().unwrap().clone(); @@ -694,7 +742,7 @@ async fn test_assert_factor_instances_invalid() { } #[actix_rt::test] -async fn add_account_and_personas_mixed() { +async fn add_account_and_personas_mixed_veci() { let os = SargonOS::fast_boot().await; let profile = os.profile().unwrap(); assert!(profile @@ -971,12 +1019,15 @@ async fn test_securified_accounts() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - IndexSet::from_iter([alice.address(), bob.address()]), + IndexSet::from_iter([AddressOfAccountOrPersona::from(alice.address()), AddressOfAccountOrPersona::from(bob.address())]), shield_0, ) .await @@ -990,7 +1041,9 @@ async fn test_securified_accounts() { // Don't forget to consume! instances_in_cache_consumer.consume().await.unwrap(); - let alice_sec = security_structures_of_fis.get(&alice.address()).unwrap(); + let alice_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(alice.address())) + .unwrap(); let alice_matrix = alice_sec.matrix_of_factors.clone(); assert_eq!(alice_matrix.primary().get_threshold(), 2); @@ -1029,7 +1082,9 @@ async fn test_securified_accounts() { // assert bob - let bob_sec = security_structures_of_fis.get(&bob.address()).unwrap(); + let bob_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(bob.address())) + .unwrap(); let bob_matrix = bob_sec.matrix_of_factors.clone(); assert_eq!(bob_matrix.primary().get_threshold(), 2); @@ -1105,12 +1160,15 @@ async fn test_securified_accounts() { number_of_days_until_auto_confirm: 1, }; - let shield_1 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_1); + let shield_1 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_1, + bdfs.clone(), + ); let (security_structures_of_fis, instances_in_cache_consumer, _) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - IndexSet::from_iter([carol.address()]), + IndexSet::from_iter([AddressOfAccountOrPersona::from(carol.address())]), shield_1.clone(), ) .await @@ -1119,7 +1177,9 @@ async fn test_securified_accounts() { // Don't forget to consume! instances_in_cache_consumer.consume().await.unwrap(); - let carol_sec = security_structures_of_fis.get(&carol.address()).unwrap(); + let carol_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(carol.address())) + .unwrap(); let carol_matrix = carol_sec.matrix_of_factors.clone(); assert_eq!(carol_matrix.primary_role.get_threshold_factors().len(), 1); @@ -1151,7 +1211,7 @@ async fn test_securified_accounts() { // Update Alice's shield 1 - only Passphrase as override factor let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - IndexSet::from_iter([alice.address()]), + IndexSet::from_iter([AddressOfAccountOrPersona::from(alice.address())]), shield_1, ) .await @@ -1165,7 +1225,9 @@ async fn test_securified_accounts() { "should have used cache" ); - let alice_sec = security_structures_of_fis.get(&alice.address()).unwrap(); + let alice_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(alice.address())) + .unwrap(); let alice_matrix = alice_sec.matrix_of_factors.clone(); @@ -1240,8 +1302,11 @@ async fn securify_accounts_when_cache_is_half_full_single_factor_source() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); let profile = os.profile().unwrap(); let all_accounts = profile .accounts_on_all_networks_including_hidden() @@ -1265,7 +1330,7 @@ async fn securify_accounts_when_cache_is_half_full_single_factor_source() { let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - first_half_of_accounts.clone().into_iter().map(|a| a.address()).collect(), + first_half_of_accounts.clone().into_iter().map(|a| a.address()).map(AddressOfAccountOrPersona::from).collect(), shield_0.clone(), ) .await @@ -1301,7 +1366,9 @@ async fn securify_accounts_when_cache_is_half_full_single_factor_source() { let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - second_half_of_accounts.clone().into_iter().map(|a| a.address()).collect(), + second_half_of_accounts.clone().into_iter().map(|a| a.address()) + .map(AddressOfAccountOrPersona::from) + .collect(), shield_0.clone(), ) .await @@ -1387,8 +1454,11 @@ async fn securify_accounts_when_cache_is_half_full_multiple_factor_sources() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); let all_accounts = os .profile() .unwrap() @@ -1413,7 +1483,7 @@ async fn securify_accounts_when_cache_is_half_full_multiple_factor_sources() { let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - first_half_of_accounts.clone().into_iter().map(|a| a.address()).collect(), + first_half_of_accounts.clone().into_iter().map(|a| a.address()).map(AddressOfAccountOrPersona::from).collect(), shield_0.clone(), ) .await @@ -1491,7 +1561,9 @@ async fn securify_accounts_when_cache_is_half_full_multiple_factor_sources() { let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - second_half_of_accounts.clone().into_iter().map(|a| a.address()).collect(), + second_half_of_accounts.clone().into_iter().map(|a| a.address()) + .map(AddressOfAccountOrPersona::from) + .collect(), shield_0.clone(), ) .await @@ -1624,8 +1696,11 @@ async fn securify_personas_when_cache_is_half_full_single_factor_source() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); let all_personas = os .profile() .unwrap() @@ -1650,7 +1725,9 @@ async fn securify_personas_when_cache_is_half_full_single_factor_source() { let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - first_half_of_personas.clone().into_iter().map(|a| a.address()).collect(), + first_half_of_personas.clone().into_iter().map(|a| a.address()) + .map(AddressOfAccountOrPersona::from) + .collect(), shield_0.clone(), ) .await @@ -1685,7 +1762,7 @@ async fn securify_personas_when_cache_is_half_full_single_factor_source() { let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - second_half_of_personas.clone().into_iter().map(|a| a.address()).collect(), + second_half_of_personas.clone().into_iter().map(|a| a.address()).map(AddressOfAccountOrPersona::from).collect(), shield_0.clone(), ) .await @@ -1757,12 +1834,14 @@ async fn create_single_account() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); - + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - IndexSet::just(alice.address()), + IndexSet::just(AddressOfAccountOrPersona::from(alice.address())), shield_0.clone(), ) .await @@ -1776,7 +1855,9 @@ async fn create_single_account() { "should have used cache" ); - let alice_sec = security_structures_of_fis.get(&alice.address()).unwrap(); + let alice_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(alice.address())) + .unwrap(); let alice_matrix = alice_sec.matrix_of_factors.clone(); @@ -1846,12 +1927,15 @@ async fn securified_personas() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - IndexSet::from_iter([batman.address(), satoshi.address()]), + IndexSet::from_iter([AddressOfAccountOrPersona::from(batman.address()), AddressOfAccountOrPersona::from(satoshi.address())]), shield_0, ) .await @@ -1865,7 +1949,9 @@ async fn securified_personas() { // Don't forget to consume! instances_in_cache_consumer.consume().await.unwrap(); - let batman_sec = security_structures_of_fis.get(&batman.address()).unwrap(); + let batman_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(batman.address())) + .unwrap(); let batman_matrix = batman_sec.matrix_of_factors.clone(); assert_eq!(batman_matrix.primary().get_threshold(), 2); @@ -1904,8 +1990,9 @@ async fn securified_personas() { // assert satoshi - let satoshi_sec = - security_structures_of_fis.get(&satoshi.address()).unwrap(); + let satoshi_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(satoshi.address())) + .unwrap(); let satoshi_matrix = satoshi_sec.matrix_of_factors.clone(); assert_eq!(satoshi_matrix.primary().get_threshold(), 2); @@ -1984,12 +2071,15 @@ async fn securified_personas() { number_of_days_until_auto_confirm: 1, }; - let shield_1 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_1); + let shield_1 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_1, + bdfs.clone(), + ); let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - IndexSet::from_iter([hyde.address()]), + IndexSet::from_iter([AddressOfAccountOrPersona::from(hyde.address())]), shield_1.clone(), ) .await @@ -2003,7 +2093,9 @@ async fn securified_personas() { "should have used cache" ); - let hyde_sec = security_structures_of_fis.get(&hyde.address()).unwrap(); + let hyde_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(hyde.address())) + .unwrap(); let hyde_matrix = hyde_sec.matrix_of_factors.clone(); assert_eq!(hyde_matrix.primary_role.get_threshold_factors().len(), 1); @@ -2035,7 +2127,7 @@ async fn securified_personas() { // Update Batman's shield 1 - only Passphrase as override factor let (security_structures_of_fis, instances_in_cache_consumer, derivation_outcome) = os .make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( - IndexSet::from_iter([batman.address()]), + IndexSet::from_iter([AddressOfAccountOrPersona::from(batman.address())]), shield_1, ) .await @@ -2048,7 +2140,9 @@ async fn securified_personas() { "should have used cache" ); - let batman_sec = security_structures_of_fis.get(&batman.address()).unwrap(); + let batman_sec = security_structures_of_fis + .get(&AddressOfAccountOrPersona::from(batman.address())) + .unwrap(); let batman_matrix = batman_sec.matrix_of_factors.clone(); @@ -2147,8 +2241,11 @@ async fn securified_all_accounts_next_veci_does_not_start_at_zero() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); let (_, derivation_outcome) = os .__OFFLINE_ONLY_securify_accounts( unnamed_accounts @@ -2283,7 +2380,7 @@ async fn securified_all_accounts_next_veci_does_not_start_at_zero() { } #[actix_rt::test] -async fn securified_accounts_asymmetric_indices() { +async fn securified_accounts_and_personas_mixed_asymmetric_indices() { let (os, bdfs) = SargonOS::with_bdfs().await; let cache = os.cache_snapshot().await; assert_eq!( @@ -2326,6 +2423,16 @@ async fn securified_accounts_asymmetric_indices() { .into_iter() .collect_vec(); + let (_, derivation_outcome) = os.batch_create_many_personas_with_factor_source_with_derivation_outcome_then_save_once(bdfs.clone(), CACHE_FILLING_QUANTITY as u16, network, "Persona".to_owned()).await.unwrap(); + assert!(derivation_outcome.debug_was_derived.is_empty()); + + let unnamed_personas = os + .profile() + .unwrap() + .personas_on_all_networks_including_hidden() + .into_iter() + .collect_vec(); + // This is NOT a valid Matrix! But for the purpose of this test, it's fine. // We are not testing valid matrices here... we are testing the factor // instances provider... @@ -2348,15 +2455,31 @@ async fn securified_accounts_asymmetric_indices() { number_of_days_until_auto_confirm: 1, }; - let shield_0 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_0); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); + + let mut unnamed_accounts_and_personas_mixed_addresses = IndexSet::new(); + unnamed_accounts_and_personas_mixed_addresses.extend( + unnamed_accounts + .clone() + .into_iter() + .map(|e| e.address()) + .map(AddressOfAccountOrPersona::from), + ); + unnamed_accounts_and_personas_mixed_addresses.extend( + unnamed_personas + .clone() + .into_iter() + .map(|e| e.address()) + .map(AddressOfAccountOrPersona::from), + ); + let (_, derivation_outcome) = os - .__OFFLINE_ONLY_securify_accounts( - unnamed_accounts - .clone() - .iter() - .map(|a| a.address()) - .collect(), + .__OFFLINE_ONLY_securify_entities( + unnamed_accounts_and_personas_mixed_addresses, &shield_0, ) .await @@ -2461,8 +2584,11 @@ async fn securified_accounts_asymmetric_indices() { number_of_days_until_auto_confirm: 1, }; - let shield_1 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_1); + let shield_1 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_1, + bdfs.clone(), + ); let (securified_alice, derivation_outcome) = os .__OFFLINE_ONLY_securify_account(alice.address(), &shield_1) @@ -2526,8 +2652,11 @@ async fn securified_accounts_asymmetric_indices() { number_of_days_until_auto_confirm: 1, }; - let shield_2 = - SecurityStructureOfFactorSources::new(DisplayName::sample(), matrix_2); + let shield_2 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_2, + bdfs.clone(), + ); let (securified_bob, derivation_outcome) = os .__OFFLINE_ONLY_securify_account(bob.address(), &shield_2) @@ -2636,6 +2765,7 @@ async fn securified_accounts_asymmetric_indices() { let shield_3fa = SecurityStructureOfFactorSources::new( DisplayName::sample(), matrix_3fa, + bdfs.clone(), ); let (securified_diana, derivation_outcome) = os @@ -2697,14 +2827,29 @@ async fn securified_accounts_asymmetric_indices() { os.clear_cache().await; // CLEAR CACHE - let (more_unnamed_accounts, _) = os.batch_create_many_accounts_with_bdfs_with_derivation_outcome_then_save_once(2 * CACHE_FILLING_QUANTITY as u16, network, "more".to_owned()).await.unwrap(); + let (more_unnamed_accounts, _) = os.batch_create_many_accounts_with_bdfs_with_derivation_outcome_then_save_once(2 * CACHE_FILLING_QUANTITY as u16, network, "more accounts".to_owned()).await.unwrap(); - let (many_securified_accounts, derivation_outcome) = os - .__OFFLINE_ONLY_securify_accounts( - more_unnamed_accounts - .into_iter() - .map(|a| a.address()) - .collect(), + let (more_unnamed_personas, _) = os.batch_create_many_personas_with_bdfs_with_derivation_outcome_then_save_once(2 * CACHE_FILLING_QUANTITY as u16, network, "more personas".to_owned()).await.unwrap(); + + let mut unnamed_accounts_and_personas_mixed_addresses = IndexSet::new(); + unnamed_accounts_and_personas_mixed_addresses.extend( + more_unnamed_accounts + .clone() + .into_iter() + .map(|e| e.address()) + .map(AddressOfAccountOrPersona::from), + ); + unnamed_accounts_and_personas_mixed_addresses.extend( + more_unnamed_personas + .clone() + .into_iter() + .map(|e| e.address()) + .map(AddressOfAccountOrPersona::from), + ); + + let (many_securified_entities, derivation_outcome) = os + .__OFFLINE_ONLY_securify_entities( + unnamed_accounts_and_personas_mixed_addresses.clone(), &shield_3fa, ) .await @@ -2716,17 +2861,29 @@ async fn securified_accounts_asymmetric_indices() { ); os.clear_cache().await; // CLEAR CACHE - for index in 0..many_securified_accounts.len() { - let securified_account = many_securified_accounts + for index in 0..many_securified_entities.len() { + let securified_entity = many_securified_entities .clone() .into_iter() .nth(index) .unwrap(); - let offset = (index + 1) as u32; - - assert_eq!( - securified_account + let (index_device, index_arculus, index_ledger) = + if securified_entity.is_account_entity() { + let offset = (index + 1) as u32; + ( + diana_mfa_device + offset, + diana_mfa_arculus + offset, + diana_mfa_ledger + offset, + ) + } else { + let base = + (index as i32 - more_unnamed_accounts.len() as i32) as u32; + (base + unnamed_personas.len() as u32, base, base) + }; + + pretty_assertions::assert_eq!( + securified_entity .try_get_secured_control() .unwrap() .security_structure @@ -2740,24 +2897,21 @@ async fn securified_accounts_asymmetric_indices() { ( bdfs.id_from_hash(), HDPathComponent::Securified( - SecurifiedU30::try_from(diana_mfa_device + offset) - .unwrap() + SecurifiedU30::try_from(index_device).unwrap() ) ), ( arculus.id_from_hash(), HDPathComponent::Securified( - SecurifiedU30::try_from(diana_mfa_arculus + offset) - .unwrap() + SecurifiedU30::try_from(index_arculus).unwrap() ) ), ( ledger.id_from_hash(), HDPathComponent::Securified( - SecurifiedU30::try_from(diana_mfa_ledger + offset) - .unwrap() + SecurifiedU30::try_from(index_ledger).unwrap() ) - ), + ) ] .into_iter() .collect::>() diff --git a/crates/sargon/src/factor_instances_provider/provider/outcome/factor_instances_provider_outcome.rs b/crates/sargon/src/factor_instances_provider/provider/outcome/factor_instances_provider_outcome.rs index 3a23f32d3..5bcac12a2 100644 --- a/crates/sargon/src/factor_instances_provider/provider/outcome/factor_instances_provider_outcome.rs +++ b/crates/sargon/src/factor_instances_provider/provider/outcome/factor_instances_provider_outcome.rs @@ -1,9 +1,35 @@ use crate::prelude::*; -/// Identical to `InternalFactorInstancesProviderOutcome` but `FactorInstancesProviderOutcomeForFactor` instead of `InternalFactorInstancesProviderOutcomeForFactor`, having -/// renamed field values to make it clear that `to_cache` instances already have been cached. +/// A collection of `FactorInstancesProviderOutcomePerFactor` keyed under +/// DerivationPreset. #[derive(Clone, Debug)] pub struct FactorInstancesProviderOutcome { + pub per_derivation_preset: + IndexMap, +} + +impl FactorInstancesProviderOutcome { + pub fn get_derivation_preset( + &self, + preset: DerivationPreset, + ) -> Option<&FactorInstancesProviderOutcomePerFactor> { + self.per_derivation_preset.get(&preset) + } + + pub fn get_derivation_preset_for_factor( + &self, + preset: DerivationPreset, + factor_source_id: &FactorSourceIDFromHash, + ) -> Option<&FactorInstancesProviderOutcomeForFactor> { + self.get_derivation_preset(preset) + .and_then(|x| x.per_factor.get(factor_source_id)) + } +} + +/// A collection of `FactorInstancesProviderOutcomeForFactor` keyed by their +/// FactorSourceID +#[derive(Clone, Debug)] +pub struct FactorInstancesProviderOutcomePerFactor { pub per_factor: IndexMap< FactorSourceIDFromHash, FactorInstancesProviderOutcomeForFactor, @@ -15,6 +41,19 @@ impl From { fn from(value: InternalFactorInstancesProviderOutcome) -> Self { Self { + per_derivation_preset: value + .per_derivation_preset + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + } + } +} +impl From + for FactorInstancesProviderOutcomePerFactor +{ + fn from(value: InternalFactorInstancesProviderOutcomePerFactor) -> Self { + FactorInstancesProviderOutcomePerFactor { per_factor: value .per_factor .into_iter() @@ -29,9 +68,13 @@ impl FactorInstancesProviderOutcome { pub fn newly_derived_instances_from_all_factor_sources( &self, ) -> FactorInstances { - self.per_factor + self.per_derivation_preset .values() - .flat_map(|x| x.debug_was_derived.factor_instances()) + .flat_map(|x| { + x.per_factor + .values() + .flat_map(|f| f.debug_was_derived.factor_instances()) + }) .collect() } @@ -46,9 +89,13 @@ impl FactorInstancesProviderOutcome { pub fn instances_found_in_cache_from_all_factor_sources( &self, ) -> FactorInstances { - self.per_factor + self.per_derivation_preset .values() - .flat_map(|x| x.debug_found_in_cache.factor_instances()) + .flat_map(|x| { + x.per_factor + .values() + .flat_map(|f| f.debug_found_in_cache.factor_instances()) + }) .collect() } diff --git a/crates/sargon/src/factor_instances_provider/provider/outcome/internal_factor_instances_provider_outcome.rs b/crates/sargon/src/factor_instances_provider/provider/outcome/internal_factor_instances_provider_outcome.rs index d9a414d30..18616217a 100644 --- a/crates/sargon/src/factor_instances_provider/provider/outcome/internal_factor_instances_provider_outcome.rs +++ b/crates/sargon/src/factor_instances_provider/provider/outcome/internal_factor_instances_provider_outcome.rs @@ -2,6 +2,36 @@ use crate::prelude::*; #[derive(Clone, Debug)] pub struct InternalFactorInstancesProviderOutcome { + pub per_derivation_preset: IndexMap< + DerivationPreset, + InternalFactorInstancesProviderOutcomePerFactor, + >, +} + +impl InternalFactorInstancesProviderOutcome { + /// Outcome of FactorInstances just from cache, none have been derived. + pub fn satisfied_by_cache(satisfied: CacheSatisfied) -> Self { + Self::new( + satisfied.cached.into_iter().map(|(preset, x)| { + let per_factor = InternalFactorInstancesProviderOutcomePerFactor::satisfied_by_cache(x); + (preset, per_factor) + }).collect::>() + ) + } + + pub fn get_for_derivation_preset( + &self, + preset: DerivationPreset, + ) -> Option<&InternalFactorInstancesProviderOutcomePerFactor> { + self.per_derivation_preset.get(&preset) + } +} + +#[derive(Clone, Debug)] +pub struct InternalFactorInstancesProviderOutcomePerFactor { pub per_factor: IndexMap< FactorSourceIDFromHash, InternalFactorInstancesProviderOutcomeForFactor, @@ -9,6 +39,62 @@ pub struct InternalFactorInstancesProviderOutcome { } impl InternalFactorInstancesProviderOutcome { + pub fn new( + per_derivation_preset: IndexMap< + DerivationPreset, + InternalFactorInstancesProviderOutcomePerFactor, + >, + ) -> Self { + Self { + per_derivation_preset, + } + } + + /// For each value of each collection, "transposes" it. For more info see + /// `InternalFactorInstancesProviderOutcomePerFactor::transpose` + pub fn transpose( + pdp_pf_to_cache: InstancesPerDerivationPresetPerFactorSource, + pdp_pf_to_use_directly: InstancesPerDerivationPresetPerFactorSource, + pdp_pf_found_in_cache: InstancesPerDerivationPresetPerFactorSource, + pdp_pf_newly_derived: InstancesPerDerivationPresetPerFactorSource, + ) -> Self { + let mut per_derivation_preset = IndexMap::< + DerivationPreset, + InternalFactorInstancesProviderOutcomePerFactor, + >::new(); + + for preset in DerivationPreset::all().iter() { + let pf_to_cache = + pdp_pf_to_cache.get(preset).cloned().unwrap_or_default(); + let pf_to_use_directly = pdp_pf_to_use_directly + .get(preset) + .cloned() + .unwrap_or_default(); + let pf_found_in_cache = pdp_pf_found_in_cache + .get(preset) + .cloned() + .unwrap_or_default(); + let pf_newly_derived = pdp_pf_newly_derived + .get(preset) + .cloned() + .unwrap_or_default(); + + let per_factor = + InternalFactorInstancesProviderOutcomePerFactor::transpose( + pf_to_cache, + pf_to_use_directly, + pf_found_in_cache, + pf_newly_derived, + ); + + per_derivation_preset.insert(*preset, per_factor); + } + + Self::new(per_derivation_preset) + } +} + +impl InternalFactorInstancesProviderOutcomePerFactor { pub fn new( per_factor: IndexMap< FactorSourceIDFromHash, @@ -23,19 +109,19 @@ impl InternalFactorInstancesProviderOutcome { pf_found_in_cache: IndexMap, ) -> Self { Self::new( - pf_found_in_cache - .into_iter() - .map(|(k, v)| { - ( - k, - InternalFactorInstancesProviderOutcomeForFactor::satisfied_by_cache(k, v), - ) - }) - .collect(), +pf_found_in_cache + .into_iter() + .map(|(k, v)| { + ( + k, + InternalFactorInstancesProviderOutcomeForFactor::satisfied_by_cache(k, v), + ) + }) + .collect(), ) } - /// "Transposes" a **collection** of `IndexMap` into `IndexMap` (`InternalFactorInstancesProviderOutcomeForFactor` is essentially a collection of FactorInstance) + /// "Transposes" pub fn transpose( pf_to_cache: IndexMap, pf_to_use_directly: IndexMap, @@ -144,7 +230,7 @@ mod tests { use super::*; #[allow(clippy::upper_case_acronyms)] - type SUT = InternalFactorInstancesProviderOutcome; + type SUT = InternalFactorInstancesProviderOutcomePerFactor; #[test] fn only_to_cache() { diff --git a/crates/sargon/src/factor_instances_provider/provider/outcome/internal_factor_instances_provider_outcome_for_factor.rs b/crates/sargon/src/factor_instances_provider/provider/outcome/internal_factor_instances_provider_outcome_for_factor.rs index 1ab52809a..0c98e3fe7 100644 --- a/crates/sargon/src/factor_instances_provider/provider/outcome/internal_factor_instances_provider_outcome_for_factor.rs +++ b/crates/sargon/src/factor_instances_provider/provider/outcome/internal_factor_instances_provider_outcome_for_factor.rs @@ -118,16 +118,30 @@ impl HasSampleValues for InternalFactorInstancesProviderOutcomeForFactor { } fn sample_other() -> Self { - Self::new(FactorSourceIDFromHash::sample_at(1), FactorInstances::new(IndexSet::from_iter([ - HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(2), - ])), FactorInstances::new(IndexSet::from_iter([ - HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(0), - ])), FactorInstances::new(IndexSet::from_iter([ - HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(0), - ])), FactorInstances::new(IndexSet::from_iter([ - HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(1), - HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(2), - ]))) + Self::new( + FactorSourceIDFromHash::sample_at(1), + FactorInstances::new( + IndexSet::from_iter([ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(2), + ]) + ), + FactorInstances::new( + IndexSet::from_iter([ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(0), + ]) + ), + FactorInstances::new( + IndexSet::from_iter([ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(0), + ]) + ), + FactorInstances::new( + IndexSet::from_iter([ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(1), + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(2), + ]) + ) + ) } } diff --git a/crates/sargon/src/factor_instances_provider/provider/provider_adopters/cache_filler.rs b/crates/sargon/src/factor_instances_provider/provider/provider_adopters/cache_filler.rs index d55d49c17..1a6f8b208 100644 --- a/crates/sargon/src/factor_instances_provider/provider/provider_adopters/cache_filler.rs +++ b/crates/sargon/src/factor_instances_provider/provider/provider_adopters/cache_filler.rs @@ -3,6 +3,18 @@ use crate::prelude::*; /// Uses a `FactorInstancesProvider` to fill the cache with instances for a new FactorSource. pub struct CacheFiller; +pub struct CacheFillingQuantities; +impl CacheFillingQuantities { + pub fn for_factor_source(id: FactorSourceIDFromHash) -> QuantitiesToDerive { + DerivationPreset::all() + .into_iter() + .map(|preset| { + (preset, IndexMap::kv(id, preset.cache_filling_quantity())) + }) + .collect::() + } +} + impl CacheFiller { /// Uses a `FactorInstancesProvider` to fill the `cache` with FactorInstances for a new FactorSource. /// Saves FactorInstances into the mutable `cache` parameter and returns a @@ -13,7 +25,7 @@ impl CacheFiller { factor_source: FactorSource, network_id: NetworkID, // typically mainnet interactor: Arc, - ) -> Result { + ) -> Result { let provider = FactorInstancesProvider::new( network_id, IndexSet::just(factor_source.clone()), @@ -21,28 +33,52 @@ impl CacheFiller { cache_client.clone(), interactor, ); - let quantities = IndexMap::kv( + + let quantities_to_derive = CacheFillingQuantities::for_factor_source( factor_source.id_from_hash(), - DerivationPreset::all() - .into_iter() - .map(|dp| (dp, CACHE_FILLING_QUANTITY)) - .collect::>(), ); - let derived = provider - .derive_more(quantities, DerivationPurpose::pre_deriving_keys()) + + let pdp_pf_derived = provider + .derive_more( + quantities_to_derive, + DerivationPurpose::pre_deriving_keys(), + ) .await?; - cache_client.insert_all(&derived).await?; + cache_client.insert(&pdp_pf_derived).await?; - let derived = - derived.get(&factor_source.id_from_hash()).unwrap().clone(); - let outcome = InternalFactorInstancesProviderOutcomeForFactor::new( - factor_source.id_from_hash(), - derived.clone(), - FactorInstances::default(), - FactorInstances::default(), - derived, - ); - Ok(outcome.into()) + let per_derivation_preset = pdp_pf_derived + .into_iter() + .map(|(preset, v)| { + let per_factor = v + .into_iter() + .map(|(factor_source_id, derived)| { + assert_eq!(factor_source_id, factor_source.id_from_hash()); + + let internal_for_factor = InternalFactorInstancesProviderOutcomeForFactor::new( + factor_source.id_from_hash(), + derived.clone(), + FactorInstances::default(), + FactorInstances::default(), + derived, + ); + let for_factor = FactorInstancesProviderOutcomeForFactor::from(internal_for_factor); + + (factor_source_id, for_factor) + }) + .collect::>(); + + + (preset, FactorInstancesProviderOutcomePerFactor { + per_factor, + }) + }).collect::>(); + + Ok(FactorInstancesProviderOutcome { + per_derivation_preset, + }) } } diff --git a/crates/sargon/src/factor_instances_provider/provider/provider_adopters/securify_entity_factor_instances_provider.rs b/crates/sargon/src/factor_instances_provider/provider/provider_adopters/securify_entity_factor_instances_provider.rs index 661a45b88..513b7b750 100644 --- a/crates/sargon/src/factor_instances_provider/provider/provider_adopters/securify_entity_factor_instances_provider.rs +++ b/crates/sargon/src/factor_instances_provider/provider/provider_adopters/securify_entity_factor_instances_provider.rs @@ -18,16 +18,16 @@ impl SecurifyEntityFactorInstancesProvider { pub async fn for_account_mfa( cache_client: Arc, profile: Arc, - matrix_of_factor_sources: MatrixOfFactorSources, + security_structure_of_factor_sources: SecurityStructureOfFactorSources, account_addresses: IndexSet, interactor: Arc, ) -> Result<(InstancesInCacheConsumer, FactorInstancesProviderOutcome)> { - Self::for_entity_mfa::( + Self::securifying_unsecurified( cache_client, profile, - matrix_of_factor_sources, - account_addresses, + security_structure_of_factor_sources, + account_addresses.into_iter().map(Into::into).collect(), interactor, ) .await @@ -47,16 +47,16 @@ impl SecurifyEntityFactorInstancesProvider { pub async fn for_persona_mfa( cache_client: Arc, profile: Arc, - matrix_of_factor_sources: MatrixOfFactorSources, + security_structure_of_factor_sources: SecurityStructureOfFactorSources, persona_addresses: IndexSet, interactor: Arc, ) -> Result<(InstancesInCacheConsumer, FactorInstancesProviderOutcome)> { - Self::for_entity_mfa::( + Self::securifying_unsecurified( cache_client, profile, - matrix_of_factor_sources, - persona_addresses, + security_structure_of_factor_sources, + persona_addresses.into_iter().map(Into::into).collect(), interactor, ) .await @@ -73,15 +73,19 @@ impl SecurifyEntityFactorInstancesProvider { /// /// We are always reading from the beginning of each FactorInstance collection in the cache, /// and we are always appending to the end. - pub async fn for_entity_mfa( + pub async fn securifying_unsecurified( + // if you need to UPDATE already securified, upgrade this to conditionally consume ROLA + // factors, by not using `QuantifiedDerivationPreset::securifying_unsecurified_entities` + // below. I.e. create the set of `QuantifiedDerivationPreset` which does not unconditionally + // specify ROLA factors. cache_client: Arc, profile: Arc, - matrix_of_factor_sources: MatrixOfFactorSources, - addresses_of_entities: IndexSet, + security_structure_of_factor_sources: SecurityStructureOfFactorSources, + addresses_of_entities: IndexSet, interactor: Arc, ) -> Result<(InstancesInCacheConsumer, FactorInstancesProviderOutcome)> { - let factor_sources_to_use = matrix_of_factor_sources + let factor_sources_to_use = security_structure_of_factor_sources .all_factors() .into_iter() .map(|x| x.to_owned()) @@ -107,7 +111,7 @@ impl SecurifyEntityFactorInstancesProvider { assert!( addresses_of_entities .iter() - .all(|a| profile.contains_entity_by_address::(a)), + .all(|a| profile.contains_entity_by_address(a)), "unknown entity" ); @@ -119,8 +123,6 @@ impl SecurifyEntityFactorInstancesProvider { "wrong network" ); - let entity_kind = A::entity_kind(); - let provider = FactorInstancesProvider::new( network_id, factor_sources_to_use, @@ -129,14 +131,18 @@ impl SecurifyEntityFactorInstancesProvider { interactor, ); + let purpose = DerivationPurpose::for_securifying_or_updating( + &addresses_of_entities, + ); + + let quantified_derivation_presets = + QuantifiedDerivationPreset::securifying_unsecurified_entities( + &addresses_of_entities, + ); + + assert!(quantified_derivation_presets.len() >= 2); // at least one entity kind, and ROLA + TX: at least 2 let (instances_in_cache_consumer, outcome) = provider - .provide( - QuantifiedDerivationPreset::new( - DerivationPreset::mfa_entity_kind(entity_kind), - addresses_of_entities.len(), - ), - DerivationPurpose::for_securifying_or_updating(entity_kind), - ) + .provide_for_presets(quantified_derivation_presets, purpose) .await?; Ok((instances_in_cache_consumer, outcome.into())) @@ -161,7 +167,7 @@ mod tests { let _ = SUT::for_account_mfa( Arc::new(cache_client), Arc::new(Profile::sample_from([fs.clone()], [&a], [])), - MatrixOfFactorSources::sample(), + SecurityStructureOfFactorSources::sample(), IndexSet::::new(), // <---- EMPTY => should_panic Arc::new(TestDerivationInteractor::default()), ) @@ -178,7 +184,7 @@ mod tests { let _ = SUT::for_account_mfa( Arc::new(cache_client), Arc::new(Profile::sample_from([fs.clone()], [&a], [])), - MatrixOfFactorSources::sample(), + SecurityStructureOfFactorSources::sample(), IndexSet::just(Account::sample_other().address()), // <---- unknown => should_panic Arc::new(TestDerivationInteractor::default()), ) @@ -211,7 +217,7 @@ mod tests { let _ = SUT::for_account_mfa( Arc::new(cache_client), Arc::new(profile), - MatrixOfFactorSources::sample(), + SecurityStructureOfFactorSources::sample(), IndexSet::from_iter([mainnet_account.address()]), Arc::new(TestDerivationInteractor::default()), ) @@ -257,7 +263,7 @@ mod tests { let _ = SUT::for_account_mfa( Arc::new(cache_client), Arc::new(profile), - MatrixOfFactorSources::sample(), + SecurityStructureOfFactorSources::sample(), IndexSet::from_iter([ mainnet_account.address(), stokenet_account.address(), @@ -306,39 +312,49 @@ mod tests { let matrix_0 = MatrixOfFactorSources::new(matrix_ids, factor_sources).unwrap(); + let shield_0 = SecurityStructureOfFactorSources::new( + DisplayName::sample(), + matrix_0, + bdfs.clone(), + ); + let cache_client = Arc::new(os.clients.factor_instances_cache.clone()); let profile = Arc::new(os.profile().unwrap()); let derivation_interactors = os.keys_derivation_interactor(); - let (instances_in_cache_consumer, outcome) = SUT::for_account_mfa( - cache_client.clone(), - profile, - matrix_0.clone(), - IndexSet::just(alice.address()), - derivation_interactors.clone(), - ) - .await - .unwrap(); + let (instances_in_cache_consumer, outcome) = + SUT::securifying_unsecurified( + cache_client.clone(), + profile, + shield_0.clone(), + IndexSet::from_iter([ + AddressOfAccountOrPersona::from(alice.address()), + AddressOfAccountOrPersona::from(batman.address()), + ]), + derivation_interactors.clone(), + ) + .await + .unwrap(); + + assert_eq!(outcome.per_derivation_preset.len(), 4); // don't forget to consume instances_in_cache_consumer.consume().await.unwrap(); - let outcome = outcome.per_factor.get(&bdfs.id_from_hash()).unwrap(); - assert_eq!(outcome.to_use_directly.len(), 1); - let profile = Arc::new(os.profile().unwrap()); - let (instances_in_cache_consumer, outcome) = SUT::for_persona_mfa( - cache_client.clone(), - profile, - matrix_0.clone(), - IndexSet::just(batman.address()), - derivation_interactors.clone(), - ) - .await - .unwrap(); + let account_outcome = outcome + .get_derivation_preset_for_factor( + DerivationPreset::AccountMfa, + &bdfs.id_from_hash(), + ) + .unwrap(); + assert_eq!(account_outcome.to_use_directly.len(), 1); - // don't forget to consume - instances_in_cache_consumer.consume().await.unwrap(); - let outcome = outcome.per_factor.get(&bdfs.id_from_hash()).unwrap(); - assert_eq!(outcome.to_use_directly.len(), 1); + let persona_outcome = outcome + .get_derivation_preset_for_factor( + DerivationPreset::AccountMfa, + &bdfs.id_from_hash(), + ) + .unwrap(); + assert_eq!(persona_outcome.to_use_directly.len(), 1); } } diff --git a/crates/sargon/src/factor_instances_provider/provider/provider_adopters/virtual_entity_creating_instance_provider.rs b/crates/sargon/src/factor_instances_provider/provider/provider_adopters/virtual_entity_creating_instance_provider.rs index 2d9895f1d..1f8465586 100644 --- a/crates/sargon/src/factor_instances_provider/provider/provider_adopters/virtual_entity_creating_instance_provider.rs +++ b/crates/sargon/src/factor_instances_provider/provider/provider_adopters/virtual_entity_creating_instance_provider.rs @@ -21,10 +21,8 @@ impl VirtualEntityCreatingInstanceProvider { factor_source: FactorSource, network_id: NetworkID, interactor: Arc, - ) -> Result<( - InstancesInCacheConsumer, - FactorInstancesProviderOutcomeForFactor, - )> { + ) -> Result<(InstancesInCacheConsumer, FactorInstancesProviderOutcome)> + { Self::for_entity_veci( CAP26EntityKind::Account, cache_client, @@ -52,10 +50,8 @@ impl VirtualEntityCreatingInstanceProvider { factor_source: FactorSource, network_id: NetworkID, interactor: Arc, - ) -> Result<( - InstancesInCacheConsumer, - FactorInstancesProviderOutcomeForFactor, - )> { + ) -> Result<(InstancesInCacheConsumer, FactorInstancesProviderOutcome)> + { Self::for_entity_veci( CAP26EntityKind::Identity, cache_client, @@ -84,10 +80,8 @@ impl VirtualEntityCreatingInstanceProvider { factor_source: FactorSource, network_id: NetworkID, interactor: Arc, - ) -> Result<( - InstancesInCacheConsumer, - FactorInstancesProviderOutcomeForFactor, - )> { + ) -> Result<(InstancesInCacheConsumer, FactorInstancesProviderOutcome)> + { Self::for_many_entity_vecis( 1, entity_kind, @@ -120,10 +114,8 @@ impl VirtualEntityCreatingInstanceProvider { factor_source: FactorSource, network_id: NetworkID, interactor: Arc, - ) -> Result<( - InstancesInCacheConsumer, - FactorInstancesProviderOutcomeForFactor, - )> { + ) -> Result<(InstancesInCacheConsumer, FactorInstancesProviderOutcome)> + { let provider = FactorInstancesProvider::new( network_id, IndexSet::just(factor_source.clone()), @@ -131,22 +123,16 @@ impl VirtualEntityCreatingInstanceProvider { cache_client, interactor, ); + + let derivation_preset = DerivationPreset::veci_entity_kind(entity_kind); + let (instances_in_cache_consumer, outcome) = provider .provide( - QuantifiedDerivationPreset::new( - DerivationPreset::veci_entity_kind(entity_kind), - count, - ), + QuantifiedDerivationPreset::new(derivation_preset, count), DerivationPurpose::creation_of_new_virtual_entity(entity_kind), ) .await?; - let outcome = outcome - .per_factor - .get(&factor_source.id_from_hash()) - .cloned() - .expect("Expected to have instances for the factor source"); - Ok((instances_in_cache_consumer, outcome.into())) } } @@ -176,23 +162,55 @@ mod tests { .unwrap(); consumer.consume().await.unwrap(); - assert_eq!(outcome.factor_source_id, bdfs.id_from_hash()); - - assert_eq!(outcome.debug_found_in_cache.len(), 0); - assert_eq!( - outcome.debug_was_cached.len(), - DerivationPreset::all().len() * CACHE_FILLING_QUANTITY + outcome + .per_derivation_preset + .values() + .flat_map(|x| x.per_factor.keys().cloned().collect_vec()) + .collect::>(), + HashSet::::from_iter([bdfs.id_from_hash()]) ); - assert_eq!( - outcome.debug_was_derived.len(), - DerivationPreset::all().len() * CACHE_FILLING_QUANTITY + 1 - ); + assert!(outcome.per_derivation_preset.values().all(|x| x + .per_factor + .values() + .all(|y| y.debug_found_in_cache.is_empty()))); + + assert!(outcome.per_derivation_preset.clone().into_iter().all( + |(preset, x)| x + .per_factor + .values() + .all(|y| y.debug_was_cached.len() + == preset.cache_filling_quantity()) + )); + + for (preset, per_factor) in outcome.per_derivation_preset.iter() { + for (_, for_factor) in per_factor.per_factor.iter() { + let derivation_based_offset = + if *preset == DerivationPreset::IdentityVeci { + 1 /* One persona created */ + } else { + 0 + }; + assert_eq!( + for_factor.debug_was_derived.len(), + preset.cache_filling_quantity() + derivation_based_offset + ) + } + } + + let instances_used_directly = outcome + .per_derivation_preset + .get(&DerivationPreset::IdentityVeci) + .unwrap() + .per_factor + .get(&bdfs.id_from_hash()) + .unwrap() + .to_use_directly + .factor_instances(); - let instances_used_directly = - outcome.to_use_directly.factor_instances(); assert_eq!(instances_used_directly.len(), 1); + let instances_used_directly = instances_used_directly.first().unwrap(); assert_eq!( @@ -264,6 +282,7 @@ mod tests { let network = NetworkID::Mainnet; let bdfs = FactorSource::sample(); let cache_client = Arc::new(FactorInstancesCacheClient::in_memory()); + let (consumer, outcome) = SUT::for_account_veci( cache_client.clone(), None, @@ -273,24 +292,56 @@ mod tests { ) .await .unwrap(); - consumer.consume().await.unwrap(); - assert_eq!(outcome.factor_source_id, bdfs.id_from_hash()); - - assert_eq!(outcome.debug_found_in_cache.len(), 0); + consumer.consume().await.unwrap(); assert_eq!( - outcome.debug_was_cached.len(), - DerivationPreset::all().len() * CACHE_FILLING_QUANTITY + outcome + .per_derivation_preset + .values() + .flat_map(|x| x.per_factor.keys().cloned().collect_vec()) + .collect::>(), + HashSet::::from_iter([bdfs.id_from_hash()]) ); - assert_eq!( - outcome.debug_was_derived.len(), - DerivationPreset::all().len() * CACHE_FILLING_QUANTITY + 1 - ); + assert!(outcome.per_derivation_preset.values().all(|x| x + .per_factor + .values() + .all(|y| y.debug_found_in_cache.is_empty()))); + + assert!(outcome.per_derivation_preset.clone().into_iter().all( + |(preset, x)| x + .per_factor + .values() + .all(|y| y.debug_was_cached.len() + == preset.cache_filling_quantity()) + )); + + for (k, v) in outcome.per_derivation_preset.iter() { + for (_, y) in v.per_factor.iter() { + let derivation_based_offset = + if *k == DerivationPreset::AccountVeci { + 1 + } else { + 0 + }; + assert_eq!( + y.debug_was_derived.len(), + k.cache_filling_quantity() + derivation_based_offset + ) + } + } + + let instances_used_directly = outcome + .per_derivation_preset + .get(&DerivationPreset::AccountVeci) + .unwrap() + .per_factor + .get(&bdfs.id_from_hash()) + .unwrap() + .to_use_directly + .factor_instances(); - let instances_used_directly = - outcome.to_use_directly.factor_instances(); assert_eq!(instances_used_directly.len(), 1); let instances_used_directly = instances_used_directly.first().unwrap(); @@ -480,13 +531,44 @@ mod tests { .unwrap(); consumer.consume().await.unwrap(); - assert_eq!(outcome.factor_source_id, bdfs.id_from_hash()); - assert_eq!(outcome.debug_found_in_cache.len(), 1); // This time we found in cache - assert_eq!(outcome.debug_was_cached.len(), 0); - assert_eq!(outcome.debug_was_derived.len(), 0); + assert_eq!( + outcome + .per_derivation_preset + .values() + .flat_map(|x| x.per_factor.keys().cloned().collect_vec()) + .collect::>(), + HashSet::::from_iter([bdfs.id_from_hash()]) + ); + + assert!(outcome.per_derivation_preset.values().all(|x| x + .per_factor + .values() + .all(|y| y.debug_found_in_cache.len() == 1))); // This time we found in cache + + assert!(outcome.per_derivation_preset.clone().into_iter().all( + |(_, x)| x + .per_factor + .values() + .all(|y| y.debug_was_cached.is_empty()) + )); + + assert!(outcome.per_derivation_preset.clone().into_iter().all( + |(_, x)| x + .per_factor + .values() + .all(|y| y.debug_was_derived.is_empty()) + )); + + let instances_used_directly = outcome + .per_derivation_preset + .get(&DerivationPreset::AccountVeci) + .unwrap() + .per_factor + .get(&bdfs.id_from_hash()) + .unwrap() + .to_use_directly + .factor_instances(); - let instances_used_directly = - outcome.to_use_directly.factor_instances(); assert_eq!(instances_used_directly.len(), 1); let instances_used_directly = instances_used_directly.first().unwrap(); @@ -568,11 +650,33 @@ mod tests { .unwrap(); consumer.consume().await.unwrap(); - assert_eq!(outcome.factor_source_id, bdfs.id_from_hash()); + assert_eq!( + outcome + .per_derivation_preset + .values() + .flat_map(|x| x.per_factor.keys().cloned().collect_vec()) + .collect::>(), + HashSet::::from_iter([bdfs.id_from_hash()]) + ); - assert_eq!(outcome.debug_found_in_cache.len(), 29); - assert_eq!(outcome.debug_was_cached.len(), 0); - assert_eq!(outcome.debug_was_derived.len(), 0); + assert!(outcome.per_derivation_preset.values().all(|x| x + .per_factor + .values() + .all(|y| y.debug_found_in_cache.len() == 29))); + + assert!(outcome.per_derivation_preset.clone().into_iter().all( + |(_, x)| x + .per_factor + .values() + .all(|y| y.debug_was_cached.is_empty()) + )); + + assert!(outcome.per_derivation_preset.clone().into_iter().all( + |(_, x)| x + .per_factor + .values() + .all(|y| y.debug_was_derived.is_empty()) + )); let cached = cache_client .peek_all_instances_of_factor_source(bdfs.id_from_hash()) @@ -605,14 +709,47 @@ mod tests { .unwrap(); consumer.consume().await.unwrap(); - assert_eq!(outcome.factor_source_id, bdfs.id_from_hash()); + assert_eq!( + outcome + .per_derivation_preset + .values() + .flat_map(|x| x.per_factor.keys().cloned().collect_vec()) + .collect::>(), + HashSet::::from_iter([bdfs.id_from_hash()]) + ); - assert_eq!(outcome.debug_found_in_cache.len(), 0); - assert_eq!(outcome.debug_was_cached.len(), CACHE_FILLING_QUANTITY); // ONLY 30, not 120... - assert_eq!(outcome.debug_was_derived.len(), CACHE_FILLING_QUANTITY + 1); + assert!(outcome.per_derivation_preset.values().all(|x| x + .per_factor + .values() + .all(|y| y.debug_found_in_cache.is_empty()))); + + assert!(outcome.per_derivation_preset.clone().into_iter().all( + |(preset, x)| x + .per_factor + .values() + .all(|y| y.debug_was_cached.len() + == preset.cache_filling_quantity()) + )); + + assert!(outcome.per_derivation_preset.clone().into_iter().all( + |(preset, x)| { + x.per_factor.values().all(|y| { + y.debug_was_derived.len() + == preset.cache_filling_quantity() + 1 + } /* One account created */) + } + )); + + let instances_used_directly = outcome + .per_derivation_preset + .get(&DerivationPreset::AccountVeci) + .unwrap() + .per_factor + .get(&bdfs.id_from_hash()) + .unwrap() + .to_use_directly + .factor_instances(); - let instances_used_directly = - outcome.to_use_directly.factor_instances(); assert_eq!(instances_used_directly.len(), 1); let instances_used_directly = instances_used_directly.first().unwrap(); diff --git a/crates/sargon/src/factor_instances_provider/types/appendable_collection.rs b/crates/sargon/src/factor_instances_provider/types/appendable_collection.rs index c690578c6..117169660 100644 --- a/crates/sargon/src/factor_instances_provider/types/appendable_collection.rs +++ b/crates/sargon/src/factor_instances_provider/types/appendable_collection.rs @@ -34,7 +34,7 @@ pub trait AppendableMap { } } -/// A collection which we can append to, primlary used as a generic +/// A collection which we can append to, primarily used as a generic /// constraint for `AppendableMap`. pub trait AppendableCollection: FromIterator { type Element; diff --git a/crates/sargon/src/factor_instances_provider/types/factor_instances.rs b/crates/sargon/src/factor_instances_provider/types/factor_instances.rs index 0db4d4db8..17f90edc7 100644 --- a/crates/sargon/src/factor_instances_provider/types/factor_instances.rs +++ b/crates/sargon/src/factor_instances_provider/types/factor_instances.rs @@ -43,9 +43,28 @@ impl FactorInstances { self.shift_remove_index(idx) } - pub fn first(&self) -> Option { - self.factor_instances.first().cloned() + fn first_of_key_kind( + &self, + key_kind: CAP26KeyKind, + ) -> Option { + self.factor_instances + .iter() + .find(|i| i.get_key_kind() == key_kind) + .cloned() + } + + pub fn first_transaction_signing( + &self, + ) -> Option { + self.first_of_key_kind(CAP26KeyKind::TransactionSigning) } + + pub fn first_authentication_signing( + &self, + ) -> Option { + self.first_of_key_kind(CAP26KeyKind::AuthenticationSigning) + } + pub fn split_at(self, mid: usize) -> (Self, Self) { let instances = self.factor_instances.into_iter().collect_vec(); let (head, tail) = instances.split_at(mid); diff --git a/crates/sargon/src/hierarchical_deterministic/bip39/mnemonic.rs b/crates/sargon/src/hierarchical_deterministic/bip39/mnemonic.rs index fac358211..fea725831 100644 --- a/crates/sargon/src/hierarchical_deterministic/bip39/mnemonic.rs +++ b/crates/sargon/src/hierarchical_deterministic/bip39/mnemonic.rs @@ -424,9 +424,6 @@ mod tests { fn find_off_device_sample_other() { let s = "off device sign source sample other off sample other off sample other off sample other off sample other off device sample other off"; let mnemonics = calculate_last_mnemonic_word_from_phrase(s); - for mnemonic in mnemonics.iter() { - println!("{}", mnemonic.words.iter().last().unwrap().word); - } assert!(mnemonics.iter().contains(&SUT::sample_off_device_other())); } diff --git a/crates/sargon/src/keys_collector/collector/derivation_purpose.rs b/crates/sargon/src/keys_collector/collector/derivation_purpose.rs index 74c3576bd..7f3db0c91 100644 --- a/crates/sargon/src/keys_collector/collector/derivation_purpose.rs +++ b/crates/sargon/src/keys_collector/collector/derivation_purpose.rs @@ -12,11 +12,15 @@ pub enum DerivationPurpose { /// for identity VECIs CreatingNewPersona, - /// When applying a security shield to an account, initiates keys collection + /// When applying a security shield to accounts and personas mixed, initiates keys collection + /// for account MFA + SecurifyingAccountsAndPersonas, + + /// When applying a security shield to only accounts, initiates keys collection /// for account MFA SecurifyingAccount, - /// When applying a security shield to a persona, initiates keys collection + /// When applying a security shield to only personas, initiates keys collection /// for identity MFA SecurifyingPersona, @@ -35,10 +39,23 @@ impl DerivationPurpose { } } - pub fn for_securifying_or_updating(entity_kind: CAP26EntityKind) -> Self { - match entity_kind { - CAP26EntityKind::Account => Self::SecurifyingAccount, - CAP26EntityKind::Identity => Self::SecurifyingPersona, + pub fn for_securifying_or_updating( + addresses_of_entities: &IndexSet, + ) -> Self { + let account_addresses = addresses_of_entities + .iter() + .filter(|a| a.is_account()) + .collect_vec(); + let identity_addresses = addresses_of_entities + .iter() + .filter(|a| a.is_identity()) + .collect_vec(); + + match (account_addresses.is_empty(), identity_addresses.is_empty()) { + (true, true) => unreachable!("Incorrect implementation"), // weird! + (true, false) => Self::SecurifyingPersona, + (false, false) => Self::SecurifyingAccountsAndPersonas, + (false, true) => Self::SecurifyingAccount, } } @@ -71,21 +88,36 @@ mod tests { } #[test] - fn test_for_securifying_account() { + fn test_for_securifying_account_only() { assert_eq!( - SUT::for_securifying_or_updating(CAP26EntityKind::Account), + SUT::for_securifying_or_updating(&IndexSet::from_iter([ + AccountAddress::sample().into() + ])), SUT::SecurifyingAccount ) } #[test] - fn test_for_securifying_persona() { + fn test_for_securifying_persona_only() { assert_eq!( - SUT::for_securifying_or_updating(CAP26EntityKind::Identity), + SUT::for_securifying_or_updating(&IndexSet::from_iter([ + IdentityAddress::sample().into() + ])), SUT::SecurifyingPersona ) } + #[test] + fn test_for_securifying_account_and_persona() { + assert_eq!( + SUT::for_securifying_or_updating(&IndexSet::from_iter([ + AccountAddress::sample().into(), + IdentityAddress::sample().into() + ])), + SUT::SecurifyingAccountsAndPersonas + ) + } + #[test] fn test_for_pre_deriving_keys() { assert_eq!(SUT::pre_deriving_keys(), SUT::PreDerivingKeys) diff --git a/crates/sargon/src/keys_collector/derivation_testing/test_keys_collector/test_derivation_interactor.rs b/crates/sargon/src/keys_collector/derivation_testing/test_keys_collector/test_derivation_interactor.rs index 379e89057..1dbcf7d30 100644 --- a/crates/sargon/src/keys_collector/derivation_testing/test_keys_collector/test_derivation_interactor.rs +++ b/crates/sargon/src/keys_collector/derivation_testing/test_keys_collector/test_derivation_interactor.rs @@ -54,7 +54,7 @@ impl TestDerivationInteractor { cloned_client, async move |id| { id.maybe_sample_associated_mnemonic() - .ok_or(CommonError::Unknown) + .ok_or(CommonError::FactorSourceDiscrepancy) }, ) .await diff --git a/crates/sargon/src/profile/logic/account/query_accounts.rs b/crates/sargon/src/profile/logic/account/query_accounts.rs index 0bdbace8d..37e244ff7 100644 --- a/crates/sargon/src/profile/logic/account/query_accounts.rs +++ b/crates/sargon/src/profile/logic/account/query_accounts.rs @@ -47,6 +47,20 @@ impl Profile { Err(CommonError::UnknownAccount) } + pub fn entity_by_address( + &self, + entity_address: AddressOfAccountOrPersona, + ) -> Result { + self.networks + .get_id(entity_address.network_id()) + .and_then(|n| n.entity_by_address(&entity_address)) + .ok_or(if entity_address.is_account() { + CommonError::UnknownAccount + } else { + CommonError::UnknownPersona + }) + } + pub fn get_entities_of_kind_on_network_in_key_space( &self, entity_kind: CAP26EntityKind, diff --git a/crates/sargon/src/profile/logic/create_entity.rs b/crates/sargon/src/profile/logic/create_entity.rs index 1fe917f04..25838fc9f 100644 --- a/crates/sargon/src/profile/logic/create_entity.rs +++ b/crates/sargon/src/profile/logic/create_entity.rs @@ -31,19 +31,29 @@ impl Profile { let count = count as usize; let fsid = factor_source.factor_source_id(); + let entity_kind = E::entity_kind(); let (instances_in_cache_consumer, outcome) = VirtualEntityCreatingInstanceProvider::for_many_entity_vecis( count, - E::entity_kind(), + entity_kind, factor_instances_cache_client, Arc::new(self.clone()), - factor_source, + factor_source.clone(), network_id, key_derivation_interactor, ) .await?; + let outcome = outcome + .per_derivation_preset + .get(&DerivationPreset::veci_entity_kind(entity_kind)) + .unwrap() + .per_factor + .get(&factor_source.id_from_hash()) + .cloned() + .unwrap(); + let instances_to_use_directly = outcome.clone().to_use_directly; assert_eq!(instances_to_use_directly.len(), count); diff --git a/crates/sargon/src/profile/logic/profile_network/profile_network_get_entities.rs b/crates/sargon/src/profile/logic/profile_network/profile_network_get_entities.rs index bbfc55c58..844d3889b 100644 --- a/crates/sargon/src/profile/logic/profile_network/profile_network_get_entities.rs +++ b/crates/sargon/src/profile/logic/profile_network/profile_network_get_entities.rs @@ -48,18 +48,24 @@ impl ProfileNetwork { .collect() } - pub fn contains_entity_by_address( + pub fn entity_by_address( &self, - entity_address: &A, - ) -> bool { - self.get_entities_erased(A::entity_kind()) + entity_address: &AddressOfAccountOrPersona, + ) -> Option { + let entities = self + .get_entities_erased(entity_address.get_entity_kind()) .into_iter() - .any(|e| { - e.address() - == Into::::into( - entity_address.clone(), - ) - }) + .filter(|e| e.address() == *entity_address) + .collect_vec(); + assert!(entities.len() <= 1); + entities.first().cloned() + } + + pub fn contains_entity_by_address( + &self, + entity_address: &AddressOfAccountOrPersona, + ) -> bool { + self.entity_by_address(entity_address).is_some() } } diff --git a/crates/sargon/src/profile/logic/profile_networks.rs b/crates/sargon/src/profile/logic/profile_networks.rs index 0d98b8c77..85aedbabf 100644 --- a/crates/sargon/src/profile/logic/profile_networks.rs +++ b/crates/sargon/src/profile/logic/profile_networks.rs @@ -11,12 +11,12 @@ impl Profile { pub fn has_any_account_on_any_network(&self) -> bool { self.networks.iter().any(|n| !n.accounts.is_empty()) } - pub fn contains_entity_by_address( + pub fn contains_entity_by_address( &self, - entity_address: &A, + entity_address: &AddressOfAccountOrPersona, ) -> bool { self.networks.iter().any(|n: ProfileNetwork| { - n.contains_entity_by_address::(entity_address) + n.contains_entity_by_address(entity_address) }) } } diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/automatic_shield_builder.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/automatic_shield_builder.rs index 76eb2a063..b3b727b50 100644 --- a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/automatic_shield_builder.rs +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/automatic_shield_builder.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use super::{ - proto_matrix::ProtoMatrix, quantity::Quantity, + proto_shield::ProtoShield, quantity::Quantity, CallsToAssignUnsupportedFactor, }; @@ -32,7 +32,7 @@ pub(crate) struct AutomaticShieldBuilder { /// The factors assigned to each role, including the factors originally /// set for the primary role. - proto_matrix: ProtoMatrix, + proto_shield: ProtoShield, } impl SecurityShieldBuilder { @@ -111,14 +111,14 @@ impl SecurityShieldBuilder { primary_factors, ); - let proto_matrix = auto_builder.assign()?; + let proto_shield = auto_builder.assign()?; assert_eq!( - proto_matrix.primary.clone().into_iter().collect_vec(), + proto_shield.primary.clone().into_iter().collect_vec(), self.get_primary_threshold_factors(), "Auto assignment should not have changed the primary factors" ); - self.set_state(proto_matrix); + self.set_state(proto_shield); if let Some(invalid_reason) = self.validate() { Err(CommonError::AutomaticShieldBuildingFailure { @@ -129,17 +129,22 @@ impl SecurityShieldBuilder { } } - /// Updates the Primary, Recovery and Confirmation roles with the factors of the given `ProtoMatrix`. - fn set_state(&self, proto_matrix: ProtoMatrix) { + /// Updates the Authentication Signing Factor, + /// Primary (should remain unchanged since these factor should have been set at start of auto assign), + /// Recovery and Confirmation roles with the factors of the given `ProtoShield`. + fn set_state(&self, proto_shield: ProtoShield) { self.reset_factors_in_roles(); - self.set_threshold(proto_matrix.primary.len() as u8); - proto_matrix.primary.into_iter().for_each(|f| { + self.set_authentication_signing_factor(Some( + proto_shield.authentication_signing_factor, + )); + self.set_threshold(proto_shield.primary.len() as u8); + proto_shield.primary.into_iter().for_each(|f| { self.add_factor_source_to_primary_threshold(f); }); - proto_matrix.recovery.into_iter().for_each(|f| { + proto_shield.recovery.into_iter().for_each(|f| { self.add_factor_source_to_recovery_override(f); }); - proto_matrix.confirmation.into_iter().for_each(|f| { + proto_shield.confirmation.into_iter().for_each(|f| { self.add_factor_source_to_confirmation_override(f); }); } @@ -153,10 +158,24 @@ impl AutomaticShieldBuilder { Self { stats_for_testing: AutoBuildOutcomeForTesting::default(), remaining_available_factors: available_factors, - proto_matrix: ProtoMatrix::new(primary), + proto_shield: ProtoShield::new(primary), } } + fn remaining_factors_matching_selector( + &self, + selector: FactorSelector, + ) -> IndexSet { + self.remaining_available_factors + .iter() + .filter(|&f| match selector { + FactorSelector::Category(category) => f.category() == category, + FactorSelector::Kind(kind) => f.factor_source_kind() == kind, + }) + .map(|f| f.id()) + .collect::>() + } + /// Returns `Some(n)` if any factor matching the selector was found where `n` /// is `<= quantity_to_add` and `None` if no factors matching the selector was. /// found. Guaranteed to never return `Some(0)`. @@ -168,15 +187,8 @@ impl AutomaticShieldBuilder { ) -> Option { let target_role = to; - let mut factors_to_add = self - .remaining_available_factors - .iter() - .filter(|&f| match selector { - FactorSelector::Category(category) => f.category() == category, - FactorSelector::Kind(kind) => f.factor_source_kind() == kind, - }) - .map(|f| f.id()) - .collect::>(); + let mut factors_to_add = + self.remaining_factors_matching_selector(selector); if let Some(quantity) = quantity_to_add.as_fixed() { factors_to_add = factors_to_add @@ -193,14 +205,14 @@ impl AutomaticShieldBuilder { self.remaining_available_factors .retain(|f| !factors_to_add.contains(&f.id())); - self.proto_matrix + self.proto_shield .add_factors_for_role(target_role, factors_to_add); Some(number_of_factors_added) } fn factors_for_role(&self, role: RoleKind) -> &IndexSet { - self.proto_matrix.factors_for_role(role) + self.proto_shield.factors_for_role(role) } /// Returns `true` if any factor was assigned, `false` otherwise. @@ -332,12 +344,22 @@ impl AutomaticShieldBuilder { /// Automatic assignment of factors to roles according to [this heuristics][doc]. /// /// [doc]: https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields#Automatic-Security-Shield-Construction - fn assign(&mut self) -> Result { + fn assign(&mut self) -> Result { + if let Some(authentication_signing_factor) = self + .remaining_factors_matching_selector(FactorSelector::Kind( + FactorSourceKind::Device, + )) + .first() + { + self.proto_shield.authentication_signing_factor = + *authentication_signing_factor + } + // 📒 "If the user only chose 1 factor for PRIMARY, remove that factor from the list (it cannot be used elsewhere - otherwise it can)." { if self.count_factors_for_role(Primary) == 1 && let Some(only_primary_factor) = - self.proto_matrix.primary.iter().next() + self.proto_shield.primary.iter().next() { self.remaining_available_factors .retain(|f| f.id() != *only_primary_factor); @@ -390,7 +412,7 @@ impl AutomaticShieldBuilder { } } - Ok(self.proto_matrix.clone()) + Ok(self.proto_shield.clone()) } } diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/mod.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/mod.rs index f50fd048e..e8cfebb4d 100644 --- a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/mod.rs +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/mod.rs @@ -2,7 +2,7 @@ mod auto_build_outcome_for_testing; #[allow(clippy::module_inception)] mod automatic_shield_builder; mod factor_selector; -mod proto_matrix; +mod proto_shield; mod quantity; pub use auto_build_outcome_for_testing::*; diff --git a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_matrix.rs b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_shield.rs similarity index 81% rename from crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_matrix.rs rename to crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_shield.rs index 56dde7835..9563cf2c5 100644 --- a/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_matrix.rs +++ b/crates/sargon/src/profile/mfa/security_structures/automatic_shield_builder/proto_shield.rs @@ -5,15 +5,19 @@ use RoleKind::*; /// A tiny holder of factors for each Role. /// Used by the AutomaticShieldBuilder to keep track of which factors are assigned to which role. #[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct ProtoMatrix { +pub(super) struct ProtoShield { + pub(super) authentication_signing_factor: FactorSourceID, pub(super) primary: IndexSet, pub(super) recovery: IndexSet, pub(super) confirmation: IndexSet, } -impl ProtoMatrix { +impl ProtoShield { pub(super) fn new(primary: IndexSet) -> Self { + assert!(!primary.is_empty()); + let authentication_signing_factor = primary.first().cloned().unwrap(); Self { + authentication_signing_factor, primary, recovery: IndexSet::new(), confirmation: IndexSet::new(), diff --git a/crates/sargon/src/profile/mfa/security_structures/matrices/matrix_of_factor_instances.rs b/crates/sargon/src/profile/mfa/security_structures/matrices/matrix_of_factor_instances.rs index 7c1fde0a2..dffcb1745 100644 --- a/crates/sargon/src/profile/mfa/security_structures/matrices/matrix_of_factor_instances.rs +++ b/crates/sargon/src/profile/mfa/security_structures/matrices/matrix_of_factor_instances.rs @@ -213,6 +213,43 @@ impl HasSampleValues for MatrixOfFactorInstances { } } +impl SecurityStructureOfFactorInstances { + pub fn fulfilling_structure_of_factor_sources_with_instances( + consuming_instances: &mut IndexMap< + FactorSourceIDFromHash, + FactorInstances, + >, + security_structure_of_factor_sources: &SecurityStructureOfFactorSources, + ) -> Result { + let matrix_of_factors = MatrixOfFactorInstances::fulfilling_matrix_of_factor_sources_with_instances( + consuming_instances, + security_structure_of_factor_sources.matrix_of_factors.clone(), + )?; + + let authentication_signing = if let Some(existing) = consuming_instances + .get_mut( + &security_structure_of_factor_sources + .authentication_signing_factor + .id_from_hash(), + ) { + let instance = existing.first_authentication_signing().ok_or( + CommonError::MissingRolaKeyForSecurityStructureOfFactorInstances, + )?; + + let _ = existing.shift_remove(&instance); // don't forget to consume it! + Ok(instance) + } else { + Err(CommonError::MissingRolaKeyForSecurityStructureOfFactorInstances) + }?; + + Self::new( + security_structure_of_factor_sources.id(), + matrix_of_factors, + authentication_signing, + ) + } +} + impl MatrixOfFactorInstances { /// Maps `MatrixOfFactorSources -> MatrixOfFactorInstances` by /// "assigning" FactorInstances to each MatrixOfFactorInstances from @@ -226,7 +263,7 @@ impl MatrixOfFactorInstances { /// However, the same FactorInstance is NEVER used in two different MatrixOfFactorInstances. /// /// - pub fn fulfilling_matrix_of_factor_sources_with_instances( + fn fulfilling_matrix_of_factor_sources_with_instances( consuming_instances: &mut IndexMap< FactorSourceIDFromHash, FactorInstances, diff --git a/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/role_with_factor_instances.rs b/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/role_with_factor_instances.rs index fb3f872c1..e2dd1a105 100644 --- a/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/role_with_factor_instances.rs +++ b/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/role_with_factor_instances.rs @@ -42,9 +42,10 @@ impl RoleWithFactorInstances { from.iter() .map(|f| { if let Some(existing) = instances.get(&f.id_from_hash()) { - let hd_instance = existing.first().ok_or( - CommonError::MissingFactorMappingInstancesIntoRole, - )?; + let hd_instance = + existing.first_transaction_signing().ok_or( + CommonError::MissingFactorMappingInstancesIntoRole, + )?; let instance = FactorInstance::from(hd_instance); Ok(instance) } else { diff --git a/crates/sargon/src/profile/mfa/security_structures/security_shield_builder.rs b/crates/sargon/src/profile/mfa/security_structures/security_shield_builder.rs index 261682243..5f4a63088 100644 --- a/crates/sargon/src/profile/mfa/security_structures/security_shield_builder.rs +++ b/crates/sargon/src/profile/mfa/security_structures/security_shield_builder.rs @@ -3,6 +3,7 @@ use crate::prelude::*; #[derive(Debug)] pub struct SecurityShieldBuilder { matrix_builder: RwLock, + authentication_signing_factor: RwLock>, name: RwLock, // We eagerly set this, and we use it inside the `build` method, ensuring // that for the same *state* of `MatrixBuilder` we always have the same shield! @@ -25,6 +26,7 @@ impl SecurityShieldBuilder { Self { matrix_builder: RwLock::new(matrix_builder), name, + authentication_signing_factor: RwLock::new(None), shield_id: SecurityStructureID::from(id()), created_on: now(), } @@ -88,6 +90,10 @@ impl SecurityShieldBuilder { self.name.read().unwrap().clone() } + pub fn get_authentication_signing_factor(&self) -> Option { + *self.authentication_signing_factor.read().unwrap() + } + pub fn get_primary_threshold_factors(&self) -> Vec { self.get_factors(|builder| builder.get_primary_threshold_factors()) } @@ -114,6 +120,14 @@ impl SecurityShieldBuilder { self } + pub fn set_authentication_signing_factor( + &self, + new: impl Into>, + ) -> &Self { + *self.authentication_signing_factor.write().unwrap() = new.into(); + self + } + /// Adds the factor source to the primary role threshold list. /// /// Also sets the threshold to 1 this is the first factor set and if @@ -421,10 +435,21 @@ impl SecurityShieldBuilder { if DisplayName::new(self.get_name()).is_err() { return Some(SecurityShieldBuilderInvalidReason::ShieldNameInvalid); } - self.get(|builder| { + + if let Some(matrix_invalid_reason) = self.get(|builder| { let r = builder.validate(); r.as_shield_validation() - }) + }) { + return Some(matrix_invalid_reason); + } + + if self.get_authentication_signing_factor().is_none() { + return Some( + SecurityShieldBuilderInvalidReason::MissingAuthSigningFactor, + ); + } + + None } /// Validates **just** the primary role **in isolation**. @@ -503,6 +528,10 @@ impl SecurityShieldBuilder { SecurityStructureOfFactorSourceIds, SecurityShieldBuilderInvalidReason, > { + let authentication_signing_factor = + self.get_authentication_signing_factor().ok_or( + SecurityShieldBuilderInvalidReason::MissingAuthSigningFactor, + )?; let matrix_result = self.get(|builder| builder.build()); if let Some(validation_error) = matrix_result.as_shield_validation() { @@ -530,6 +559,7 @@ impl SecurityShieldBuilder { let shield = SecurityStructureOfFactorSourceIds { matrix_of_factors, metadata, + authentication_signing_factor, }; Ok(shield) } @@ -559,6 +589,9 @@ mod tests { let _ = sut .set_name("S.H.I.E.L.D.") + .set_authentication_signing_factor(Some( + FactorSourceID::sample_device(), + )) // Primary .set_number_of_days_until_auto_confirm(42) .add_factor_source_to_primary_threshold( @@ -941,6 +974,7 @@ mod test_invalid { #[test] fn valid_is_none() { let sut = SUT::new(); + sut.set_authentication_signing_factor(FactorSourceID::sample_device()); sut.add_factor_source_to_primary_override( FactorSourceID::sample_device(), ); @@ -964,6 +998,9 @@ mod test_invalid { sut.add_factor_source_to_confirmation_override( FactorSourceID::sample_arculus(), ); + sut.set_authentication_signing_factor(Some( + FactorSourceID::sample_device(), + )); sut } diff --git a/crates/sargon/src/profile/mfa/security_structures/security_shield_builder_invalid_reason.rs b/crates/sargon/src/profile/mfa/security_structures/security_shield_builder_invalid_reason.rs index 728f3ae0a..7d3982517 100644 --- a/crates/sargon/src/profile/mfa/security_structures/security_shield_builder_invalid_reason.rs +++ b/crates/sargon/src/profile/mfa/security_structures/security_shield_builder_invalid_reason.rs @@ -179,6 +179,9 @@ impl AsShieldBuilderViolation for (RoleKind, NotYetValidReason) { #[repr(u32)] #[derive(Clone, Debug, thiserror::Error, PartialEq)] pub enum SecurityShieldBuilderInvalidReason { + #[error("Auth Signing Factor Missing")] + MissingAuthSigningFactor, + #[error("Shield name is invalid")] ShieldNameInvalid, diff --git a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/abstract_security_structure_of_factors.rs b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/abstract_security_structure_of_factors.rs index 9ba771767..5e4ff10fb 100644 --- a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/abstract_security_structure_of_factors.rs +++ b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/abstract_security_structure_of_factors.rs @@ -10,6 +10,9 @@ pub struct AbstractSecurityStructure { /// The structure of factors to use for certain roles, Primary, Recovery /// and Confirmation role. pub matrix_of_factors: AbstractMatrixBuilt, + + /// The factor to use for authentication signing aka true Rola Key. + pub authentication_signing_factor: FACTOR, } impl Identifiable for AbstractSecurityStructure { @@ -22,7 +25,9 @@ impl Identifiable for AbstractSecurityStructure { impl AbstractSecurityStructure { pub fn all_factors(&self) -> HashSet<&FACTOR> { - self.matrix_of_factors.all_factors() + let mut all = self.matrix_of_factors.all_factors(); + all.extend([&self.authentication_signing_factor]); + all } } @@ -30,18 +35,25 @@ impl AbstractSecurityStructure { pub fn with_metadata( metadata: SecurityStructureMetadata, matrix_of_factors: AbstractMatrixBuilt, + authentication_signing_factor: FACTOR, ) -> Self { Self { metadata, matrix_of_factors, + authentication_signing_factor, } } pub fn new( display_name: DisplayName, matrix_of_factors: AbstractMatrixBuilt, + authentication_signing_factor: FACTOR, ) -> Self { let metadata = SecurityStructureMetadata::new(display_name); - Self::with_metadata(metadata, matrix_of_factors) + Self::with_metadata( + metadata, + matrix_of_factors, + authentication_signing_factor, + ) } } diff --git a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_source_ids.rs index ec9cd65eb..7eb8d9429 100644 --- a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -9,12 +9,20 @@ pub type SecurityStructureOfFactorSourceIDs = impl HasSampleValues for SecurityStructureOfFactorSourceIds { fn sample() -> Self { let metadata = SecurityStructureMetadata::sample(); - Self::with_metadata(metadata, MatrixOfFactorSourceIds::sample()) + Self::with_metadata( + metadata, + MatrixOfFactorSourceIds::sample(), + FactorSourceID::sample_device(), + ) } fn sample_other() -> Self { let metadata = SecurityStructureMetadata::sample_other(); - Self::with_metadata(metadata, MatrixOfFactorSourceIds::sample_other()) + Self::with_metadata( + metadata, + MatrixOfFactorSourceIds::sample_other(), + FactorSourceID::sample_ledger(), + ) } } @@ -41,13 +49,20 @@ mod tests { assert_eq_after_json_roundtrip( &sut, r#" - { + { "metadata": { "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "displayName": "Spending Account", "createdOn": "2023-09-11T16:05:56.000Z", "lastUpdatedOn": "2023-09-11T16:05:56.000Z" }, + "authenticationSigningFactor": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, "matrixOfFactors": { "primaryRole": { "threshold": 2, @@ -122,6 +137,13 @@ mod tests { "createdOn": "2023-12-24T17:13:56.123Z", "lastUpdatedOn": "2023-12-24T17:13:56.123Z" }, + "authenticationSigningFactor": { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + }, "matrixOfFactors": { "primaryRole": { "threshold": 1, diff --git a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_sources.rs b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_sources.rs index c9b3f75e1..bece239af 100644 --- a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_sources.rs +++ b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_sources.rs @@ -6,12 +6,20 @@ pub type SecurityStructureOfFactorSources = impl HasSampleValues for SecurityStructureOfFactorSources { fn sample() -> Self { let metadata = SecurityStructureMetadata::sample(); - Self::with_metadata(metadata, MatrixOfFactorSources::sample()) + Self::with_metadata( + metadata, + MatrixOfFactorSources::sample(), + FactorSource::sample_device(), + ) } fn sample_other() -> Self { let metadata = SecurityStructureMetadata::sample_other(); - Self::with_metadata(metadata, MatrixOfFactorSources::sample_other()) + Self::with_metadata( + metadata, + MatrixOfFactorSources::sample_other(), + FactorSource::sample_ledger(), + ) } } @@ -36,6 +44,10 @@ impl TryFrom<(&SecurityStructureOfFactorSourceIDs, &FactorSources)> value: (&SecurityStructureOfFactorSourceIDs, &FactorSources), ) -> Result { let (id_level, factor_sources) = value; + let authentication_signing_factor = factor_sources + .get_id(id_level.authentication_signing_factor) + .cloned() + .ok_or(CommonError::FactorSourceDiscrepancy)?; let matrix_of_factors = MatrixOfFactorSources::try_from(( &id_level.matrix_of_factors, factor_sources, @@ -43,6 +55,7 @@ impl TryFrom<(&SecurityStructureOfFactorSourceIDs, &FactorSources)> Ok(Self { metadata: id_level.metadata.clone(), matrix_of_factors, + authentication_signing_factor, }) } } @@ -54,6 +67,9 @@ impl From Self { metadata: value.metadata, matrix_of_factors: value.matrix_of_factors.into(), + authentication_signing_factor: value + .authentication_signing_factor + .factor_source_id(), } } } diff --git a/crates/sargon/src/profile/supporting_types/account_or_persona.rs b/crates/sargon/src/profile/supporting_types/account_or_persona.rs index 591699b84..d2513cabd 100644 --- a/crates/sargon/src/profile/supporting_types/account_or_persona.rs +++ b/crates/sargon/src/profile/supporting_types/account_or_persona.rs @@ -27,6 +27,12 @@ impl HasSecurityState for AccountOrPersona { Self::PersonaEntity(p) => p.security_state(), } } + fn set_security_state_unchecked(&mut self, new_state: EntitySecurityState) { + match self { + Self::AccountEntity(a) => a.set_security_state_unchecked(new_state), + Self::PersonaEntity(p) => p.set_security_state_unchecked(new_state), + } + } } impl IsKeySpaceAware for AccountOrPersona { diff --git a/crates/sargon/src/profile/v100/address/non_fungible_global_id.rs b/crates/sargon/src/profile/v100/address/non_fungible_global_id.rs index f0898ef54..904a9e5a8 100644 --- a/crates/sargon/src/profile/v100/address/non_fungible_global_id.rs +++ b/crates/sargon/src/profile/v100/address/non_fungible_global_id.rs @@ -335,7 +335,6 @@ mod tests { // local_id: ruid local_id = NonFungibleLocalId::ruid(hex_decode("deadbeef12345678babecafe87654321fadedeaf01234567ecadabba76543210").unwrap()).unwrap(); item = SUT::new(resource_address, local_id); - println!("{}", item.formatted(AddressFormat::Raw)); assert_eq!( item.formatted(AddressFormat::Default), "reso...c9wlxa:{dead...3210}" diff --git a/crates/sargon/src/profile/v100/app_preferences/security.rs b/crates/sargon/src/profile/v100/app_preferences/security.rs index ea13ccf13..7b527c5b3 100644 --- a/crates/sargon/src/profile/v100/app_preferences/security.rs +++ b/crates/sargon/src/profile/v100/app_preferences/security.rs @@ -184,6 +184,13 @@ mod tests { "createdOn": "2023-09-11T16:05:56.000Z", "lastUpdatedOn": "2023-09-11T16:05:56.000Z" }, + "authenticationSigningFactor": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, "matrixOfFactors": { "primaryRole": { "threshold": 2, @@ -248,6 +255,13 @@ mod tests { "createdOn": "2023-12-24T17:13:56.123Z", "lastUpdatedOn": "2023-12-24T17:13:56.123Z" }, + "authenticationSigningFactor": { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + }, "matrixOfFactors": { "primaryRole": { "threshold": 1, diff --git a/crates/sargon/src/profile/v100/entity/account/account.rs b/crates/sargon/src/profile/v100/entity/account/account.rs index 09b268a67..68b4a1a9d 100644 --- a/crates/sargon/src/profile/v100/entity/account/account.rs +++ b/crates/sargon/src/profile/v100/entity/account/account.rs @@ -82,6 +82,9 @@ impl HasSecurityState for Account { fn security_state(&self) -> EntitySecurityState { self.security_state.clone() } + fn set_security_state_unchecked(&mut self, new_state: EntitySecurityState) { + self.security_state = new_state; + } } impl IsSecurityStateAware for Account { diff --git a/crates/sargon/src/profile/v100/entity/has_security_state.rs b/crates/sargon/src/profile/v100/entity/has_security_state.rs index 389977716..2f79b4df2 100644 --- a/crates/sargon/src/profile/v100/entity/has_security_state.rs +++ b/crates/sargon/src/profile/v100/entity/has_security_state.rs @@ -2,6 +2,42 @@ use crate::prelude::*; pub trait HasSecurityState: HasFactorInstances + IsSecurityStateAware { fn security_state(&self) -> EntitySecurityState; + fn set_security_state_unchecked(&mut self, new_state: EntitySecurityState); + + // TODO: Should we check `provisional_securified_config` of `self` and/or + // of `new_state`? + fn set_security_state( + &mut self, + new_state: EntitySecurityState, + ) -> Result<()> { + match (&self.security_state(), &new_state) { + ( + &EntitySecurityState::Securified { .. }, + &EntitySecurityState::Unsecured { .. }, + ) => { + Err(CommonError::SecurityStateSecurifiedButExpectedUnsecurified) + } + ( + EntitySecurityState::Securified { + value: sec_existing, + }, + EntitySecurityState::Securified { value: sec_new }, + ) => { + if sec_new.access_controller_address + != sec_existing.access_controller_address + { + Err(CommonError::SecurityStateAccessControllerAddressMismatch) + } else { + self.set_security_state_unchecked(new_state); + Ok(()) + } + } + _ => { + self.set_security_state_unchecked(new_state); + Ok(()) + } + } + } fn try_get_secured_control(&self) -> Result { self.security_state() @@ -23,3 +59,129 @@ impl HasFactorInstances for T { self.security_state().unique_tx_signing_factor_instances() } } + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = AccountOrPersona; + + fn test_set_security_state_fail_cannot_unsecurify(sut: impl Into) { + let mut sut = sut.into(); + assert!(sut.is_securified()); + + let unsecurified = EntitySecurityState::sample(); + assert!(unsecurified.is_unsecured()); + + let result = sut.set_security_state(unsecurified); + assert_eq!( + result, + Err(CommonError::SecurityStateSecurifiedButExpectedUnsecurified) + ); + + // assert unchanged + assert!(sut.is_securified()); + } + + #[test] + fn set_security_state_fail_cannot_unsecurify_account() { + test_set_security_state_fail_cannot_unsecurify(Account::sample_at(2)) + } + + #[test] + fn set_security_state_fail_cannot_unsecurify_persona() { + test_set_security_state_fail_cannot_unsecurify(Persona::sample_at(2)) + } + + fn test_set_security_state_fail_can_change_unsecurified( + sut: impl Into, + ) { + let mut sut = sut.into(); + assert!(!sut.is_securified()); + + let unsecurified = EntitySecurityState::sample(); + assert!(unsecurified.is_unsecured()); + + let result = sut.set_security_state(unsecurified.clone()); + assert!(result.is_ok()); + assert_eq!(sut.security_state(), unsecurified); + } + + #[test] + fn set_security_state_fail_can_change_unsecurified_account() { + test_set_security_state_fail_can_change_unsecurified(Account::sample()); + } + + #[test] + fn set_security_state_fail_can_change_unsecurified_persona() { + test_set_security_state_fail_can_change_unsecurified(Persona::sample()); + } + + fn test_set_security_state_fail_access_controller_mismatch( + sut: impl Into, + ) { + let mut sut = sut.into(); + let entity_state = sut.security_state(); + assert!(sut.is_securified()); + + let other_securified = EntitySecurityState::Securified { + value: SecuredEntityControl::sample(), + }; + + let result = sut.set_security_state(other_securified); + assert_eq!( + result, + Err(CommonError::SecurityStateAccessControllerAddressMismatch) + ); + + // assert unchanged + assert_eq!(sut.security_state(), entity_state); + } + + #[test] + fn set_security_state_fail_access_controller_mismatch_account() { + test_set_security_state_fail_access_controller_mismatch( + Account::sample_at(2), + ); + } + + #[test] + fn set_security_state_fail_access_controller_mismatch_persona() { + test_set_security_state_fail_access_controller_mismatch( + Persona::sample_at(2), + ) + } + + fn test_set_security_state_can_change_securified(sut: impl Into) { + let mut sut = sut.into(); + let entity_state = sut.security_state(); + assert!(sut.is_securified()); + let access_controller_address = entity_state + .clone() + .as_securified() + .unwrap() + .access_controller_address; + + let mut value = SecuredEntityControl::sample(); + value.access_controller_address = access_controller_address; + let other_securified = EntitySecurityState::Securified { value }; + + let result = sut.set_security_state(other_securified); + + assert!(result.is_ok()); + assert!(sut.is_securified()); + assert_ne!(sut.security_state(), entity_state); + } + + #[test] + fn set_security_state_can_change_securified_account() { + test_set_security_state_can_change_securified(Account::sample_at(2)); + } + + #[test] + fn set_security_state_can_change_securified_persona() { + test_set_security_state_can_change_securified(Persona::sample_at(2)); + } +} diff --git a/crates/sargon/src/profile/v100/entity/persona/persona.rs b/crates/sargon/src/profile/v100/entity/persona/persona.rs index d59181548..9671040f3 100644 --- a/crates/sargon/src/profile/v100/entity/persona/persona.rs +++ b/crates/sargon/src/profile/v100/entity/persona/persona.rs @@ -75,6 +75,9 @@ impl HasSecurityState for Persona { fn security_state(&self) -> EntitySecurityState { self.security_state.clone() } + fn set_security_state_unchecked(&mut self, new_state: EntitySecurityState) { + self.security_state = new_state; + } } impl IsBaseEntity for Persona { type Address = IdentityAddress; diff --git a/crates/sargon/src/profile/v100/networks/network/profile_network.rs b/crates/sargon/src/profile/v100/networks/network/profile_network.rs index 152f6c257..d74b78523 100644 --- a/crates/sargon/src/profile/v100/networks/network/profile_network.rs +++ b/crates/sargon/src/profile/v100/networks/network/profile_network.rs @@ -163,22 +163,28 @@ impl ProfileNetwork { &mut self, updated_entities: IdentifiedVecOf, ) -> Result<()> { - match E::entity_kind() { - CAP26EntityKind::Account => { - updated_entities.to_accounts().and_then(|xs| { - self.accounts - .update_items(xs) - .map_err(|_| CommonError::UnknownAccount) - }) - } - CAP26EntityKind::Identity => { - updated_entities.to_personas().and_then(|xs| { - self.personas - .update_items(xs) - .map_err(|_| CommonError::UnknownPersona) - }) - } + self.update_entities_erased( + updated_entities.into_iter().map(Into::into).collect(), + ) + } + + pub fn update_entities_erased( + &mut self, + updated_entities: IdentifiedVecOf, + ) -> Result<()> { + for entity in updated_entities { + match entity { + AccountOrPersona::AccountEntity(account) => self + .accounts + .try_update_with(&account.id(), |a| *a = account.clone()) + .map_err(|_| CommonError::UnknownAccount), + AccountOrPersona::PersonaEntity(persona) => self + .personas + .try_update_with(&persona.id(), |p| *p = persona.clone()) + .map_err(|_| CommonError::UnknownPersona), + }?; } + Ok(()) } /// Returns a clone of the updated account if found, else None. diff --git a/crates/sargon/src/profile/v100/networks/profile_networks.rs b/crates/sargon/src/profile/v100/networks/profile_networks.rs index b5b9e104d..299ed5a8f 100644 --- a/crates/sargon/src/profile/v100/networks/profile_networks.rs +++ b/crates/sargon/src/profile/v100/networks/profile_networks.rs @@ -23,11 +23,20 @@ impl ProfileNetworks { pub fn update_entities( &mut self, updated_entities: IdentifiedVecOf, + ) -> Result<()> { + self.update_entities_erased( + updated_entities.into_iter().map(Into::into).collect(), + ) + } + + pub fn update_entities_erased( + &mut self, + updated_entities: IdentifiedVecOf, ) -> Result<()> { let network = updated_entities.assert_elements_not_empty_and_on_same_network()?; self.try_try_update_with(&network, |n| { - n.update_entities(updated_entities.clone()) + n.update_entities_erased(updated_entities.clone()) }) } diff --git a/crates/sargon/src/profile/v100/profile.rs b/crates/sargon/src/profile/v100/profile.rs index 1644a7450..ff649081f 100644 --- a/crates/sargon/src/profile/v100/profile.rs +++ b/crates/sargon/src/profile/v100/profile.rs @@ -290,10 +290,22 @@ impl Profile { &self, entities: IdentifiedVecOf, ) -> Result<()> { - let instances_of_new_entities = entities + let entities = entities .items() .into_iter() .map(Into::::into) + .collect::>(); + + self.assert_new_factor_instances_not_already_used_erased(entities) + } + + pub fn assert_new_factor_instances_not_already_used_erased( + &self, + entities: IdentifiedVecOf, + ) -> Result<()> { + let instances_of_new_entities = entities + .items() + .into_iter() .map(|e| (e.clone(), e.unique_all_factor_instances())) .collect::>>(); @@ -392,7 +404,16 @@ impl Profile { &mut self, updated_entities: IdentifiedVecOf, ) -> Result<()> { - self.networks.update_entities(updated_entities) + self.update_entities_erased( + updated_entities.into_iter().map(Into::into).collect(), + ) + } + + pub fn update_entities_erased( + &mut self, + updated_entities: IdentifiedVecOf, + ) -> Result<()> { + self.networks.update_entities_erased(updated_entities) } /// Returns a clone of the updated account if found, else None. diff --git a/crates/sargon/src/signing/collector/signatures_collector.rs b/crates/sargon/src/signing/collector/signatures_collector.rs index b7ba69bc7..101f2ceae 100644 --- a/crates/sargon/src/signing/collector/signatures_collector.rs +++ b/crates/sargon/src/signing/collector/signatures_collector.rs @@ -1529,16 +1529,17 @@ mod tests { fn sample_securified_mainnet( name: impl AsRef, + rola_index: u32, veci: HierarchicalDeterministicFactorInstance, make_role: impl Fn() -> GeneralRoleWithHierarchicalDeterministicFactorInstances, ) -> AccountOrPersona { if TypeId::of::() == TypeId::of::() { AccountOrPersona::from(Account::sample_securified_mainnet( - name, veci, make_role, + name, rola_index, veci, make_role, )) } else { AccountOrPersona::from(Persona::sample_securified_mainnet( - name, veci, make_role, + name, rola_index, veci, make_role, )) } } @@ -1906,6 +1907,7 @@ mod tests { SignableWithEntities::::sample([ sample_securified_mainnet::( "Alice", + 0, if E::entity_kind() == CAP26EntityKind::Identity { HierarchicalDeterministicFactorInstance::sample_fii10() diff --git a/crates/sargon/src/signing/petition_types/petition_for_entity.rs b/crates/sargon/src/signing/petition_types/petition_for_entity.rs index ea15aa01a..db2ecac2a 100644 --- a/crates/sargon/src/signing/petition_types/petition_for_entity.rs +++ b/crates/sargon/src/signing/petition_types/petition_for_entity.rs @@ -452,6 +452,7 @@ impl HasSampleValues Self::from_entity_with_role_kind( Account::sample_securified_mainnet( "Grace", + 6, HierarchicalDeterministicFactorInstance::sample_fii10(), || { GeneralRoleWithHierarchicalDeterministicFactorInstances::r6(HierarchicalDeterministicFactorInstance::sample_id_to_instance( @@ -648,6 +649,7 @@ mod tests { let intent_hash = TransactionIntentHash::sample(); let entity = Account::sample_securified_mainnet( "Alice", + 0, HierarchicalDeterministicFactorInstance::sample_fii10(), || { let fi = HierarchicalDeterministicFactorInstance::sample_id_to_instance( diff --git a/crates/sargon/src/signing/petition_types/petition_for_transaction.rs b/crates/sargon/src/signing/petition_types/petition_for_transaction.rs index 7547a2c4d..c445aa31e 100644 --- a/crates/sargon/src/signing/petition_types/petition_for_transaction.rs +++ b/crates/sargon/src/signing/petition_types/petition_for_transaction.rs @@ -261,6 +261,7 @@ impl HasSampleValues fn sample() -> Self { let account = Account::sample_securified_mainnet( "Grace", + 6, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(0), || { GeneralRoleWithHierarchicalDeterministicFactorInstances::r6( diff --git a/crates/sargon/src/system/clients/client/factor_instances_cache_client.rs b/crates/sargon/src/system/clients/client/factor_instances_cache_client.rs index 9e21213b7..63f3bda03 100644 --- a/crates/sargon/src/system/clients/client/factor_instances_cache_client.rs +++ b/crates/sargon/src/system/clients/client/factor_instances_cache_client.rs @@ -107,13 +107,12 @@ impl FactorInstancesCacheClient { impl FactorInstancesCacheClient { pub async fn delete( &self, - instances_per_factor_sources_to_delete: IndexMap< - FactorSourceIDFromHash, - FactorInstances, + instances_to_delete: impl Borrow< + InstancesPerDerivationPresetPerFactorSource, >, ) -> Result<()> { self.update_and_persist_cache(|cache| { - cache.delete(instances_per_factor_sources_to_delete.borrow()); + cache.delete(instances_to_delete.borrow()); Ok(()) }) .await @@ -136,12 +135,14 @@ impl FactorInstancesCacheClient { } /// Inserts all instance in `per_factor`. - pub async fn insert_all( + pub async fn insert( &self, - per_factor: impl Borrow>, + per_derivation_preset_per_factor: impl Borrow< + InstancesPerDerivationPresetPerFactorSource, + >, ) -> Result<()> { self.update_and_persist_cache(|cache| { - cache.insert_all(per_factor.borrow()) + cache.insert(per_derivation_preset_per_factor.borrow()) }) .await } @@ -158,22 +159,18 @@ impl FactorInstancesCacheClient { .await } - /// Returns enough instances to satisfy the requested quantity for each factor source, - /// **OR LESS**, never more, and if less, it means we MUST derive more, and if we - /// must derive more, this function returns the quantities to derive for each factor source, - /// for each derivation preset, not only the originally requested one. - pub async fn get_poly_factor_with_quantities( + pub async fn get( &self, factor_source_ids: impl Borrow>, - originally_requested_quantified_derivation_preset: impl Borrow< - QuantifiedDerivationPreset, + quantified_derivation_presets: impl Borrow< + IdentifiedVecOf, >, network_id: NetworkID, ) -> Result { self.access_cache_init_if_needed(|cache| { - cache.get_poly_factor_with_quantities( + cache.get( factor_source_ids.borrow(), - originally_requested_quantified_derivation_preset.borrow(), + quantified_derivation_presets.borrow(), network_id, ) }) @@ -201,6 +198,28 @@ impl FactorInstancesCacheClient { #[cfg(test)] impl FactorInstancesCacheClient { + /// Returns enough instances to satisfy the requested quantity for each factor source, + /// **OR LESS**, never more, and if less, it means we MUST derive more, and if we + /// must derive more, this function returns the quantities to derive for each factor source, + /// for each derivation preset, not only the originally requested one. + async fn get_poly_factor_with_quantities( + &self, + factor_source_ids: impl Borrow>, + originally_requested_quantified_derivation_preset: impl Borrow< + QuantifiedDerivationPreset, + >, + network_id: NetworkID, + ) -> Result { + self.access_cache_init_if_needed(|cache| { + cache.get_poly_factor_with_quantities( + factor_source_ids.borrow(), + originally_requested_quantified_derivation_preset.borrow(), + network_id, + ) + }) + .await + } + pub async fn insert_single( &self, instance: impl Borrow, @@ -306,10 +325,13 @@ mod tests { let five = HDPathComponent::from(five); assert_eq!(max_higher_sut1, Some(five)); - sut2.delete(IndexMap::from_iter([( - fsid, - FactorInstances::from_iter([fi5.clone()]), - )])) + sut2.delete(IndexMap::kv( + DerivationPreset::AccountVeci, + IndexMap::from_iter([( + fsid, + FactorInstances::from_iter([fi5.clone()]), + )]), + )) .await .unwrap(); @@ -325,10 +347,17 @@ mod tests { ) .await .unwrap(); + let satisfied = IndexMap::kv(fsid, instances); - assert_eq!( - poly, - CachedInstancesWithQuantitiesOutcome::Satisfied(satisfied) + + pretty_assertions::assert_eq!( + poly.into_satisfied() + .unwrap() + .cached + .get(&DerivationPreset::AccountVeci) + .cloned() + .unwrap(), + satisfied ); let snap_1 = sut1.snapshot().await.unwrap(); @@ -345,9 +374,12 @@ mod tests { let sut = SUT::new(file_system); let fs = FactorSourceIDFromHash::sample_at(0); - sut.insert_all(IndexMap::kv(fs, FactorInstances::sample())) - .await - .unwrap(); + sut.insert(&IndexMap::kv( + DerivationPreset::AccountMfa, + IndexMap::kv(fs, FactorInstances::sample()), + )) + .await + .unwrap(); let max = sut .max_index_for( diff --git a/crates/sargon/src/system/sargon_os/profile_state_holder.rs b/crates/sargon/src/system/sargon_os/profile_state_holder.rs index 0d4e1f043..7102c7334 100644 --- a/crates/sargon/src/system/sargon_os/profile_state_holder.rs +++ b/crates/sargon/src/system/sargon_os/profile_state_holder.rs @@ -86,6 +86,13 @@ impl ProfileStateHolder { }) } + pub fn entity_by_address( + &self, + entity_address: AddressOfAccountOrPersona, + ) -> Result { + self.try_access_profile_with(|p| p.entity_by_address(entity_address)) + } + /// Looks up the account by account address, returns Err if the account is /// unknown, will return a hidden account if queried for. pub fn account_by_address( diff --git a/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs b/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs index 8ba4e4927..e6b85d344 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs @@ -31,6 +31,13 @@ impl SargonOS { self.profile_state_holder.account_by_address(address) } + pub fn entity_by_address( + &self, + entity_address: AddressOfAccountOrPersona, + ) -> Result { + self.profile_state_holder.entity_by_address(entity_address) + } + /// Creates a new unsaved mainnet account named "Unnamed {N}", where `N` is the /// index of the next account for the BDFS. /// @@ -603,19 +610,57 @@ impl SargonOS { pub async fn update_entities( &self, updated: IdentifiedVecOf, + ) -> Result<()> { + self.update_entities_erased( + updated.into_iter().map(Into::into).collect(), + ) + .await + } + + pub async fn update_entities_erased( + &self, + updated: IdentifiedVecOf, ) -> Result<()> { let addresses = updated .clone() .into_iter() .map(|e| e.address()) .collect::>(); - self.update_profile_with(|p| p.update_entities(updated.clone())) + + let account_addresses = addresses + .iter() + .filter_map(|e| e.as_account()) + .cloned() + .collect::>(); + let identity_addresses = addresses + .iter() + .filter_map(|e| e.as_identity()) + .cloned() + .collect::>(); + + let modified_any_account = !account_addresses.is_empty(); + let modified_any_persona = !identity_addresses.is_empty(); + + self.update_profile_with(|p| p.update_entities_erased(updated.clone())) .await?; - if let Some(event) = E::profile_modified_event(true, addresses) { - self.event_bus - .emit(EventNotification::profile_modified(event)) - .await; + if modified_any_account { + if let Some(event) = + Account::profile_modified_event(true, account_addresses) + { + self.event_bus + .emit(EventNotification::profile_modified(event)) + .await; + } + } + if modified_any_persona { + if let Some(event) = + Persona::profile_modified_event(true, identity_addresses) + { + self.event_bus + .emit(EventNotification::profile_modified(event)) + .await; + } } Ok(()) } @@ -800,70 +845,124 @@ impl SargonOS { impl SargonOS { #[allow(dead_code)] #[cfg(test)] - pub(crate) async fn make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome< - A: IsEntityAddress, - >( + pub(crate) async fn make_security_structure_of_factor_instances_for_entities_without_consuming_cache_with_derivation_outcome( &self, - addresses_of_entities: IndexSet, + addresses_of_entities: IndexSet, security_structure_of_factor_sources: SecurityStructureOfFactorSources, // Aka "shield" ) -> Result<( - IndexMap, + IndexMap, InstancesInCacheConsumer, FactorInstancesProviderOutcome, )> { let profile_snapshot = self.profile()?; let key_derivation_interactors = self.keys_derivation_interactor(); - let matrix_of_factor_sources = - &security_structure_of_factor_sources.matrix_of_factors; + // if you need to UPDATE already securified, upgrade this to conditionally consume ROLA + // factors, by not using `QuantifiedDerivationPreset::securifying_unsecurified_entities` + // inside of SecurifyEntityFactorInstancesProvider::securifying_unsecurified. I.e. create the set of `QuantifiedDerivationPreset` which does not unconditionally + // specify ROLA factors. let (instances_in_cache_consumer, outcome) = - SecurifyEntityFactorInstancesProvider::for_entity_mfa::( + SecurifyEntityFactorInstancesProvider::securifying_unsecurified( Arc::new(self.clients.factor_instances_cache.clone()), Arc::new(profile_snapshot.clone()), - matrix_of_factor_sources.clone(), + security_structure_of_factor_sources.clone(), addresses_of_entities.clone(), key_derivation_interactors, ) .await?; - let mut instances_per_factor_source = outcome + let mut instances_per_preset_per_factor_source = outcome .clone() - .per_factor + .per_derivation_preset .into_iter() - .map(|(k, outcome_per_factor)| { - (k, outcome_per_factor.to_use_directly) + .map(|(preset, pf)| { + ( + preset, + pf + .per_factor + .into_iter() + .map(|(k, v)| (k, v.to_use_directly)).collect::>() + ) }) - .collect::>(); + .collect::(); assert_eq!( - instances_per_factor_source - .keys() - .cloned() + instances_per_preset_per_factor_source + .clone() + .into_iter() + .flat_map(|(_, y)| { + y.into_iter() + .map(|(a, _)| a) + .collect::>() + }) .collect::>(), - matrix_of_factor_sources + security_structure_of_factor_sources .all_factors() .into_iter() .map(|f| f.id_from_hash()) .collect::>() ); - let security_structure_id = security_structure_of_factor_sources.id(); - - let security_structures_of_factor_instances = addresses_of_entities.clone().into_iter().map(|entity_address| - { - let security_structure_of_factor_instances: SecurityStructureOfFactorInstances = { - let matrix_of_factor_instances = MatrixOfFactorInstances::fulfilling_matrix_of_factor_sources_with_instances( - &mut instances_per_factor_source, - matrix_of_factor_sources.clone(), - )?; - SecurityStructureOfFactorInstances::new( - security_structure_id, - matrix_of_factor_instances, - HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(entity_address.network_id(), CAP26KeyKind::AuthenticationSigning, A::entity_kind(), Hardened::Securified(SecurifiedU30::ZERO)), - )? + let mut security_structures_of_factor_instances = IndexMap::< + AddressOfAccountOrPersona, + SecurityStructureOfFactorInstances, + >::new(); + + let mut distribute_instances_for_entity_of_kind_if_needed = + |entity_kind: CAP26EntityKind| -> Result<()> { + let addresses_of_kind = addresses_of_entities + .iter() + .filter(|a| a.get_entity_kind() == entity_kind) + .collect::>(); + + if addresses_of_kind.is_empty() { + return Ok(()); + }; + + let mut instances_per_factor_source = { + let tx_preset = + DerivationPreset::mfa_entity_kind(entity_kind); + let rola_preset = + DerivationPreset::rola_entity_kind(entity_kind); + + let instances_per_factor_source_mfa = instances_per_preset_per_factor_source + .swap_remove(&tx_preset) + .unwrap_or_else(|| panic!("Expected to find instances for derivation preset: {:?}", tx_preset)); + + let instances_per_factor_source_rola = instances_per_preset_per_factor_source + .swap_remove(&rola_preset) + .unwrap_or_else(|| panic!("Expected to find instances for derivation preset: {:?}", rola_preset)); + + // Merge `instances_per_factor_source_mfa` and `instances_per_factor_source_rola` together + let mut instances_per_factor_source = + instances_per_factor_source_mfa; + for (k, v) in instances_per_factor_source_rola { + instances_per_factor_source.append_or_insert_to(k, v); + } + instances_per_factor_source + }; + + for entity_address in addresses_of_kind.clone().into_iter() { + let security_structure_of_factor_instances = SecurityStructureOfFactorInstances::fulfilling_structure_of_factor_sources_with_instances( + &mut instances_per_factor_source, + &security_structure_of_factor_sources + )?; + + security_structures_of_factor_instances.insert( + *entity_address, + security_structure_of_factor_instances, + ); + } + + Ok(()) }; - Ok((entity_address, security_structure_of_factor_instances)) - }).collect::>>()?; + + distribute_instances_for_entity_of_kind_if_needed( + CAP26EntityKind::Account, + )?; + distribute_instances_for_entity_of_kind_if_needed( + CAP26EntityKind::Identity, + )?; Ok(( security_structures_of_factor_instances, @@ -1310,26 +1409,64 @@ mod tests { } #[actix_rt::test] - async fn update_account_updates_in_memory_profile() { + async fn update_account_and_persona_updates_in_memory_profile() { // ARRANGE - let os = SUT::fast_boot().await; + let event_bus_driver = RustEventBusDriver::new(); + let drivers = Drivers::with_event_bus(event_bus_driver.clone()); + let clients = Clients::new(Bios::new(drivers)); + let interactors = Interactors::new_from_clients(&clients); + let os = timeout( + SARGON_OS_TEST_MAX_ASYNC_DURATION, + SUT::boot_with_clients_and_interactor(clients, interactors), + ) + .await + .unwrap(); + os.new_wallet(false).await.unwrap(); let mut account = Account::sample(); os.with_timeout(|x| x.add_account(account.clone())) .await .unwrap(); - // ACT - account.display_name = DisplayName::random(); - os.with_timeout(|x| x.update_account(account.clone())) + let mut persona = Persona::sample(); + os.with_timeout(|x| x.add_persona(persona.clone())) .await .unwrap(); + // ACT + account.display_name = DisplayName::random(); + persona.display_name = DisplayName::random(); + os.with_timeout(|x| { + x.update_entities_erased(IdentifiedVecOf::from_iter([ + AccountOrPersona::from(account.clone()), + AccountOrPersona::from(persona.clone()), + ])) + }) + .await + .unwrap(); + // ASSERT + assert_eq!(os.profile().unwrap().networks[0].accounts[0], account); + assert_eq!(os.profile().unwrap().networks[0].personas[0], persona); + use EventKind::*; assert_eq!( - os.profile().unwrap().networks[0].accounts[0], - account.clone() - ) + event_bus_driver + .recorded() + .into_iter() + .map(|e| e.event.kind()) + .collect_vec(), + vec![ + Booted, + ProfileSaved, + ProfileSaved, + AccountAdded, + ProfileSaved, + PersonaAdded, + ProfileSaved, + AccountUpdated, + PersonaUpdated + ] + ); } #[actix_rt::test] diff --git a/crates/sargon/src/system/sargon_os/sargon_os_factors.rs b/crates/sargon/src/system/sargon_os/sargon_os_factors.rs index 2a1e85c6d..6ac077796 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os_factors.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os_factors.rs @@ -238,7 +238,7 @@ impl SargonOS { pub async fn pre_derive_and_fill_cache_with_instances_for_factor_source( &self, factor_source: FactorSource, - ) -> Result { + ) -> Result { if !factor_source.factor_source_id().is_hash() { panic!("Unsupported FactorSource which is not HD.") } @@ -253,22 +253,12 @@ impl SargonOS { ) .await?; - assert_eq!(outcome.factor_source_id, factor_source.id_from_hash()); + assert!(outcome.per_derivation_preset.values().all(|pf| pf + .per_factor + .keys() + .collect_vec() + == vec![&factor_source.id_from_hash()])); - #[cfg(test)] - { - assert_eq!(outcome.debug_found_in_cache.len(), 0); - - assert_eq!( - outcome.debug_was_cached.len(), - DerivationPreset::all().len() * CACHE_FILLING_QUANTITY - ); - - assert_eq!( - outcome.debug_was_derived.len(), - DerivationPreset::all().len() * CACHE_FILLING_QUANTITY - ); - } Ok(outcome) } diff --git a/crates/sargon/src/types/samples/account_samples.rs b/crates/sargon/src/types/samples/account_samples.rs index 3d3c44d1f..31c69e05f 100644 --- a/crates/sargon/src/types/samples/account_samples.rs +++ b/crates/sargon/src/types/samples/account_samples.rs @@ -17,6 +17,7 @@ static ALL_ACCOUNT_SAMPLES: Lazy<[Account; 10]> = Lazy::new(|| { // Carla | 2 | Securified { Single Threshold only } Account::sample_securified_mainnet( "Carla", + 2, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(2), || { let idx = @@ -32,6 +33,7 @@ static ALL_ACCOUNT_SAMPLES: Lazy<[Account; 10]> = Lazy::new(|| { // David | 3 | Securified { Single Override only } Account::sample_securified_mainnet( "David", + 3, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(3), || { let idx = @@ -47,6 +49,7 @@ static ALL_ACCOUNT_SAMPLES: Lazy<[Account; 10]> = Lazy::new(|| { // Emily | 4 | Securified { Threshold factors only #3 } Account::sample_securified_mainnet( "Emily", + 4, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(4), || { let idx = @@ -62,6 +65,7 @@ static ALL_ACCOUNT_SAMPLES: Lazy<[Account; 10]> = Lazy::new(|| { // Frank | 5 | Securified { Override factors only #2 } Account::sample_securified_mainnet( "Frank", + 5, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(5), || { let idx = @@ -77,6 +81,7 @@ static ALL_ACCOUNT_SAMPLES: Lazy<[Account; 10]> = Lazy::new(|| { // Grace | 6 | Securified { Threshold #3 and Override factors #2 } Account::sample_securified_mainnet( "Grace", + 6, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(6), || { let idx = @@ -92,6 +97,7 @@ static ALL_ACCOUNT_SAMPLES: Lazy<[Account; 10]> = Lazy::new(|| { // Ida | 7 | Securified { Threshold only # 5/5 } Account::sample_securified_mainnet( "Ida", + 7, HierarchicalDeterministicFactorInstance::sample_fia11(), || { let idx = @@ -112,6 +118,7 @@ static ALL_ACCOUNT_SAMPLES: Lazy<[Account; 10]> = Lazy::new(|| { // Klara | 9 | Securified { Threshold 1/1 and Override factors #1 } Account::sample_securified_mainnet( "Klara", + 9, HierarchicalDeterministicFactorInstance::sample_fia12(), || { let idx = @@ -153,6 +160,7 @@ impl Account { pub fn sample_securified_mainnet( name: impl AsRef, + rola_index: u32, veci: HierarchicalDeterministicFactorInstance, make_role: impl Fn() -> GeneralRoleWithHierarchicalDeterministicFactorInstances, ) -> Self { @@ -171,10 +179,6 @@ impl Account { .collect_vec(), ); - let rola_index = u32::from( - veci.derivation_entity_index().index_in_local_key_space(), - ); - let network_id = NetworkID::Mainnet; let address = AccountAddress::new(veci.public_key(), NetworkID::Mainnet); diff --git a/crates/sargon/src/types/samples/persona_samples.rs b/crates/sargon/src/types/samples/persona_samples.rs index c4f50722c..9ddcbb2be 100644 --- a/crates/sargon/src/types/samples/persona_samples.rs +++ b/crates/sargon/src/types/samples/persona_samples.rs @@ -15,6 +15,7 @@ static ALL_PERSONA_SAMPLES: Lazy<[Persona; 8]> = Lazy::new(|| { // Ziggy | 2 | Securified { Single Threshold only } Persona::sample_securified_mainnet( "Ziggy", + 2, HierarchicalDeterministicFactorInstance::sample_mainnet_identity_device_factor_fs_10_unsecurified_at_index(2), || { let idx = @@ -28,6 +29,7 @@ static ALL_PERSONA_SAMPLES: Lazy<[Persona; 8]> = Lazy::new(|| { // Superman | 3 | Securified { Single Override only } Persona::sample_securified_mainnet( "Superman", + 3, HierarchicalDeterministicFactorInstance::sample_mainnet_identity_device_factor_fs_10_unsecurified_at_index(3), || { let idx = @@ -41,6 +43,7 @@ static ALL_PERSONA_SAMPLES: Lazy<[Persona; 8]> = Lazy::new(|| { // Banksy | 4 | Securified { Threshold factors only #3 } Persona::sample_securified_mainnet( "Banksy", + 4, HierarchicalDeterministicFactorInstance::sample_mainnet_identity_device_factor_fs_10_unsecurified_at_index(4), || { let idx = @@ -54,6 +57,7 @@ static ALL_PERSONA_SAMPLES: Lazy<[Persona; 8]> = Lazy::new(|| { // Voltaire | 5 | Securified { Override factors only #2 } Persona::sample_securified_mainnet( "Voltaire", + 6, HierarchicalDeterministicFactorInstance::sample_mainnet_identity_device_factor_fs_10_unsecurified_at_index(5), || { let idx = @@ -67,6 +71,7 @@ static ALL_PERSONA_SAMPLES: Lazy<[Persona; 8]> = Lazy::new(|| { // Kasparov | 6 | Securified { Threshold #3 and Override factors #2 } Persona::sample_securified_mainnet( "Kasparov", + 6, HierarchicalDeterministicFactorInstance::sample_mainnet_identity_device_factor_fs_10_unsecurified_at_index(6), || { let idx = @@ -80,6 +85,7 @@ static ALL_PERSONA_SAMPLES: Lazy<[Persona; 8]> = Lazy::new(|| { // Pelé | 7 | Securified { Threshold only # 5/5 } Persona::sample_securified_mainnet( "Pelé", + 7, HierarchicalDeterministicFactorInstance::sample_mainnet_identity_device_factor_fs_10_unsecurified_at_index(7), || { let idx = @@ -151,6 +157,7 @@ impl Persona { pub fn sample_securified_mainnet( name: impl AsRef, + rola_index: u32, veci: HierarchicalDeterministicFactorInstance, make_role: impl Fn() -> GeneralRoleWithHierarchicalDeterministicFactorInstances, ) -> Self { @@ -204,10 +211,6 @@ impl Persona { let address = IdentityAddress::new(veci.public_key(), NetworkID::Mainnet); - let rola_index = u32::from( - veci.derivation_entity_index().index_in_local_key_space(), - ); - let security_structure_of_factor_instances = SecurityStructureOfFactorInstances::new( SecurityStructureID::sample(), diff --git a/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/v1/transaction_intent.rs b/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/v1/transaction_intent.rs index c49f57f4b..9957d8285 100644 --- a/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/v1/transaction_intent.rs +++ b/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/v1/transaction_intent.rs @@ -212,7 +212,6 @@ mod tests { let sut = SUT::test_with_sbor_depth(SUT::MAX_SBOR_DEPTH, NetworkID::Stokenet) .unwrap(); - println!("{}", &sut.manifest); assert_eq!(sut.transaction_intent_hash().to_string(), "txid_rdx1uwcfczupvvrrtxwxx6p5jugaxvu3j83tj5nz9pnrr44jyxccg2cqhuvzhy") } diff --git a/crates/sargon/tests/integration/main.rs b/crates/sargon/tests/integration/main.rs index 2a2af9f63..208c780c8 100644 --- a/crates/sargon/tests/integration/main.rs +++ b/crates/sargon/tests/integration/main.rs @@ -378,6 +378,7 @@ mod integration_tests { let alice = Account::sample_securified_mainnet( "Alice", + 0, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(0), || { let i = Hardened::from_local_key_space(0u32, IsSecurified(true)) @@ -397,6 +398,7 @@ mod integration_tests { let bob = Account::sample_securified_mainnet( "Bob", + 1, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(1), || { let i = Hardened::from_local_key_space(1u32, IsSecurified(true)) @@ -415,6 +417,7 @@ mod integration_tests { let carol = Account::sample_securified_mainnet( "Carol", + 3, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(2), || { let i = Hardened::from_local_key_space(2u32, IsSecurified(true))