diff --git a/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs new file mode 100644 index 00000000..69d66a82 --- /dev/null +++ b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs @@ -0,0 +1,12 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub(crate) struct AbstractMatrixBuilderOrBuilt { + #[serde(skip)] + #[doc(hidden)] + pub(crate) built: PhantomData, + + pub(crate) primary_role: AbstractRoleBuilderOrBuilt, + pub(crate) recovery_role: AbstractRoleBuilderOrBuilt, + pub(crate) confirmation_role: AbstractRoleBuilderOrBuilt, +} diff --git a/crates/rules/src/matrices/builder/error.rs b/crates/rules/src/matrices/builder/error.rs new file mode 100644 index 00000000..b572380c --- /dev/null +++ b/crates/rules/src/matrices/builder/error.rs @@ -0,0 +1,52 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixRolesInCombinationViolation { + #[error("Basic violation: {0}")] + Basic(#[from] MatrixRolesInCombinationBasicViolation), + + #[error("Forever invalid: {0}")] + ForeverInvalid(#[from] MatrixRolesInCombinationForeverInvalid), + + #[error("Not yet valid: {0}")] + NotYetValid(#[from] MatrixRolesInCombinationNotYetValid), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixRolesInCombinationBasicViolation { + #[error("The factor source was not found in any role")] + FactorSourceNotFoundInAnyRole, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixRolesInCombinationForeverInvalid { + #[error("Recovery and confirmation factors overlap. No factor may be used in both the recovery and confirmation roles")] + RecoveryAndConfirmationFactorsOverlap, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixRolesInCombinationNotYetValid { + #[error("The single factor used in the primary role must not be used in any other role")] + SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixBuilderValidation { + #[error("Role {role:?} in isolation violation: {violation}")] + RoleInIsolation { + role: RoleKind, + violation: RoleBuilderValidation, + }, + #[error("Roles in combination violation: {0}")] + CombinationViolation(#[from] MatrixRolesInCombinationViolation), +} + +pub(crate) trait IntoMatrixErr { + fn into_matrix_err(self, role: RoleKind) -> Result; +} + +impl IntoMatrixErr for Result { + fn into_matrix_err(self, role: RoleKind) -> Result { + self.map_err(|violation| MatrixBuilderValidation::RoleInIsolation { role, violation }) + } +} diff --git a/crates/rules/src/matrices/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs similarity index 95% rename from crates/rules/src/matrices/matrix_builder.rs rename to crates/rules/src/matrices/builder/matrix_builder.rs index c107aaa4..0d24a66e 100644 --- a/crates/rules/src/matrices/matrix_builder.rs +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -1,109 +1,17 @@ use crate::prelude::*; -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct AbstractMatrixBuilderOrBuilt { - #[serde(skip)] - #[doc(hidden)] - built: PhantomData, - - primary_role: AbstractRoleBuilderOrBuilt, - recovery_role: AbstractRoleBuilderOrBuilt, - confirmation_role: AbstractRoleBuilderOrBuilt, -} +pub type MatrixBuilderMutateResult = Result<(), MatrixBuilderValidation>; +pub type MatrixBuilderBuildResult = Result; -pub type MatrixWithFactorSourceIds = AbstractMatrixBuilderOrBuilt; pub type MatrixBuilder = AbstractMatrixBuilderOrBuilt< FactorSourceID, MatrixWithFactorSourceIds, RoleWithFactorSourceIds, >; -#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] -pub enum MatrixRolesInCombinationViolation { - #[error("Basic violation: {0}")] - Basic(#[from] MatrixRolesInCombinationBasicViolation), - - #[error("Forever invalid: {0}")] - ForeverInvalid(#[from] MatrixRolesInCombinationForeverInvalid), - - #[error("Not yet valid: {0}")] - NotYetValid(#[from] MatrixRolesInCombinationNotYetValid), -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] -pub enum MatrixRolesInCombinationBasicViolation { - #[error("The factor source was not found in any role")] - FactorSourceNotFoundInAnyRole, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] -pub enum MatrixRolesInCombinationForeverInvalid { - #[error("Recovery and confirmation factors overlap. No factor may be used in both the recovery and confirmation roles")] - RecoveryAndConfirmationFactorsOverlap, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] -pub enum MatrixRolesInCombinationNotYetValid { - #[error("The single factor used in the primary role must not be used in any other role")] - SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] -pub enum MatrixBuilderValidation { - #[error("Role {role:?} in isolation violation: {violation}")] - RoleInIsolation { - role: RoleKind, - violation: RoleBuilderValidation, - }, - #[error("Roles in combination violation: {0}")] - CombinationViolation(#[from] MatrixRolesInCombinationViolation), -} - -trait IntoMatrixErr { - fn into_matrix_err(self, role: RoleKind) -> Result; -} -impl IntoMatrixErr for Result { - fn into_matrix_err(self, role: RoleKind) -> Result { - self.map_err(|violation| MatrixBuilderValidation::RoleInIsolation { role, violation }) - } -} - -pub type MatrixBuilderMutateResult = Result<(), MatrixBuilderValidation>; -pub type MatrixBuilderBuildResult = Result; - -#[cfg(test)] -impl MatrixWithFactorSourceIds { - fn with_roles( - primary: RoleWithFactorSourceIds, - recovery: RoleWithFactorSourceIds, - confirmation: RoleWithFactorSourceIds, - ) -> Self { - assert_eq!(primary.role(), sargon::RoleKind::Primary); - assert_eq!(recovery.role(), sargon::RoleKind::Recovery); - assert_eq!(confirmation.role(), sargon::RoleKind::Confirmation); - Self { - built: PhantomData, - primary_role: primary, - recovery_role: recovery, - confirmation_role: confirmation, - } - } -} - -impl MatrixWithFactorSourceIds { - pub fn primary(&self) -> &RoleWithFactorSourceIds { - &self.primary_role - } - - pub fn recovery(&self) -> &RoleWithFactorSourceIds { - &self.recovery_role - } - - pub fn confirmation(&self) -> &RoleWithFactorSourceIds { - &self.confirmation_role - } -} - +// ================== +// ===== PUBLIC ===== +// ================== impl MatrixBuilder { pub fn new() -> Self { Self { @@ -114,66 +22,6 @@ impl MatrixBuilder { } } - fn validate_if_primary_has_single_it_must_not_be_used_by_any_other_role( - &self, - ) -> MatrixBuilderMutateResult { - let primary_has_single_factor = self.primary_role.factors().len() == 1; - if primary_has_single_factor { - let primary_factors = self.primary_role.factors(); - let primary_factor = primary_factors.first().unwrap(); - let recovery_set = HashSet::<_>::from_iter(self.recovery_role.override_factors()); - let confirmation_set = - HashSet::<_>::from_iter(self.confirmation_role.override_factors()); - if recovery_set.contains(primary_factor) || confirmation_set.contains(primary_factor) { - return Err(MatrixBuilderValidation::CombinationViolation( - MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole), - )); - } - } - Ok(()) - } - - fn validate_no_factor_may_be_used_in_both_recovery_and_confirmation( - &self, - ) -> MatrixBuilderMutateResult { - let recovery_set = HashSet::<_>::from_iter(self.recovery_role.override_factors()); - let confirmation_set = HashSet::<_>::from_iter(self.confirmation_role.override_factors()); - let intersection = recovery_set - .intersection(&confirmation_set) - .collect::>(); - if intersection.is_empty() { - Ok(()) - } else { - Err(MatrixBuilderValidation::CombinationViolation( - MatrixRolesInCombinationViolation::ForeverInvalid( - MatrixRolesInCombinationForeverInvalid::RecoveryAndConfirmationFactorsOverlap, - ), - )) - } - } - - /// Security Shield Rules - /// In addition to the factor/role rules above, the wallet must enforce certain rules for combinations of - /// factors across the three roles. The construction method described in the next section will automatically - /// always follow these rules. A user may however choose to manually add/remove factors from their Shield - /// configuration and so the wallet must evaluate these rules and inform the user when the combination they - /// have chosen cannot be used. The wallet should never allow a user to complete a Shield configuration that - /// violates these rules. - /// - /// 1. If only one factor is used for `Primary`, that factor may not be used for either `Recovery` or `Confirmation` - /// 2. No factor may be used (override) in both `Recovery` and `Confirmation` - /// 3. No factor may be used in both the `Primary` threshold and `Primary` override - /// - fn validate_combination(&self) -> MatrixBuilderMutateResult { - self.validate_if_primary_has_single_it_must_not_be_used_by_any_other_role()?; - self.validate_no_factor_may_be_used_in_both_recovery_and_confirmation()?; - - // N.B. the third 3: - // "3. No factor may be used in both the `Primary` threshold and `Primary` override" - // is already enforced by the RoleBuilder - Ok(()) - } - pub fn build(self) -> MatrixBuilderBuildResult { self.validate_combination()?; @@ -416,6 +264,71 @@ impl MatrixBuilder { } } +// ================== +// ==== PRIVATE ===== +// ================== +impl MatrixBuilder { + fn validate_if_primary_has_single_it_must_not_be_used_by_any_other_role( + &self, + ) -> MatrixBuilderMutateResult { + let primary_has_single_factor = self.primary_role.factors().len() == 1; + if primary_has_single_factor { + let primary_factors = self.primary_role.factors(); + let primary_factor = primary_factors.first().unwrap(); + let recovery_set = HashSet::<_>::from_iter(self.recovery_role.override_factors()); + let confirmation_set = + HashSet::<_>::from_iter(self.confirmation_role.override_factors()); + if recovery_set.contains(primary_factor) || confirmation_set.contains(primary_factor) { + return Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole), + )); + } + } + Ok(()) + } + + fn validate_no_factor_may_be_used_in_both_recovery_and_confirmation( + &self, + ) -> MatrixBuilderMutateResult { + let recovery_set = HashSet::<_>::from_iter(self.recovery_role.override_factors()); + let confirmation_set = HashSet::<_>::from_iter(self.confirmation_role.override_factors()); + let intersection = recovery_set + .intersection(&confirmation_set) + .collect::>(); + if intersection.is_empty() { + Ok(()) + } else { + Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::ForeverInvalid( + MatrixRolesInCombinationForeverInvalid::RecoveryAndConfirmationFactorsOverlap, + ), + )) + } + } + + /// Security Shield Rules + /// In addition to the factor/role rules above, the wallet must enforce certain rules for combinations of + /// factors across the three roles. The construction method described in the next section will automatically + /// always follow these rules. A user may however choose to manually add/remove factors from their Shield + /// configuration and so the wallet must evaluate these rules and inform the user when the combination they + /// have chosen cannot be used. The wallet should never allow a user to complete a Shield configuration that + /// violates these rules. + /// + /// 1. If only one factor is used for `Primary`, that factor may not be used for either `Recovery` or `Confirmation` + /// 2. No factor may be used (override) in both `Recovery` and `Confirmation` + /// 3. No factor may be used in both the `Primary` threshold and `Primary` override + /// + fn validate_combination(&self) -> MatrixBuilderMutateResult { + self.validate_if_primary_has_single_it_must_not_be_used_by_any_other_role()?; + self.validate_no_factor_may_be_used_in_both_recovery_and_confirmation()?; + + // N.B. the third 3: + // "3. No factor may be used in both the `Primary` threshold and `Primary` override" + // is already enforced by the RoleBuilder + Ok(()) + } +} + #[cfg(test)] mod tests { diff --git a/crates/rules/src/matrices/builder/mod.rs b/crates/rules/src/matrices/builder/mod.rs new file mode 100644 index 00000000..b7581259 --- /dev/null +++ b/crates/rules/src/matrices/builder/mod.rs @@ -0,0 +1,6 @@ +mod error; +mod matrix_builder; + +pub use error::*; +#[allow(unused_imports)] +pub use matrix_builder::*; diff --git a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs new file mode 100644 index 00000000..ab033c1b --- /dev/null +++ b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; + +pub type MatrixWithFactorSourceIds = AbstractMatrixBuilderOrBuilt; + +#[cfg(test)] +impl MatrixWithFactorSourceIds { + pub(crate) fn with_roles( + primary: RoleWithFactorSourceIds, + recovery: RoleWithFactorSourceIds, + confirmation: RoleWithFactorSourceIds, + ) -> Self { + assert_eq!(primary.role(), sargon::RoleKind::Primary); + assert_eq!(recovery.role(), sargon::RoleKind::Recovery); + assert_eq!(confirmation.role(), sargon::RoleKind::Confirmation); + Self { + built: PhantomData, + primary_role: primary, + recovery_role: recovery, + confirmation_role: confirmation, + } + } +} + +impl MatrixWithFactorSourceIds { + pub fn primary(&self) -> &RoleWithFactorSourceIds { + &self.primary_role + } + + pub fn recovery(&self) -> &RoleWithFactorSourceIds { + &self.recovery_role + } + + pub fn confirmation(&self) -> &RoleWithFactorSourceIds { + &self.confirmation_role + } +} diff --git a/crates/rules/src/matrices/mod.rs b/crates/rules/src/matrices/mod.rs index f08a27eb..f56411f7 100644 --- a/crates/rules/src/matrices/mod.rs +++ b/crates/rules/src/matrices/mod.rs @@ -1,4 +1,8 @@ -mod matrix_builder; +mod abstract_matrix_builder_or_built; +mod builder; +mod matrix_with_factor_source_ids; +pub(crate) use abstract_matrix_builder_or_built::*; #[allow(unused_imports)] -pub use matrix_builder::*; +pub use builder::*; +pub use matrix_with_factor_source_ids::*;