Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit

Permalink
split
Browse files Browse the repository at this point in the history
  • Loading branch information
CyonAlexRDX committed Nov 26, 2024
1 parent 654995a commit 3b5adb3
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 159 deletions.
12 changes: 12 additions & 0 deletions crates/rules/src/matrices/abstract_matrix_builder_or_built.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) struct AbstractMatrixBuilderOrBuilt<F, T, U> {
#[serde(skip)]
#[doc(hidden)]
pub(crate) built: PhantomData<T>,

pub(crate) primary_role: AbstractRoleBuilderOrBuilt<F, U>,
pub(crate) recovery_role: AbstractRoleBuilderOrBuilt<F, U>,
pub(crate) confirmation_role: AbstractRoleBuilderOrBuilt<F, U>,
}
52 changes: 52 additions & 0 deletions crates/rules/src/matrices/builder/error.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
fn into_matrix_err(self, role: RoleKind) -> Result<T, MatrixBuilderValidation>;
}

impl<T> IntoMatrixErr<T> for Result<T, RoleBuilderValidation> {
fn into_matrix_err(self, role: RoleKind) -> Result<T, MatrixBuilderValidation> {
self.map_err(|violation| MatrixBuilderValidation::RoleInIsolation { role, violation })
}
}
Original file line number Diff line number Diff line change
@@ -1,109 +1,17 @@
use crate::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AbstractMatrixBuilderOrBuilt<F, T, U> {
#[serde(skip)]
#[doc(hidden)]
built: PhantomData<T>,

primary_role: AbstractRoleBuilderOrBuilt<F, U>,
recovery_role: AbstractRoleBuilderOrBuilt<F, U>,
confirmation_role: AbstractRoleBuilderOrBuilt<F, U>,
}
pub type MatrixBuilderMutateResult = Result<(), MatrixBuilderValidation>;
pub type MatrixBuilderBuildResult = Result<MatrixWithFactorSourceIds, MatrixBuilderValidation>;

pub type MatrixWithFactorSourceIds = AbstractMatrixBuilderOrBuilt<FactorSourceID, (), ()>;
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<T> {
fn into_matrix_err(self, role: RoleKind) -> Result<T, MatrixBuilderValidation>;
}
impl<T> IntoMatrixErr<T> for Result<T, RoleBuilderValidation> {
fn into_matrix_err(self, role: RoleKind) -> Result<T, MatrixBuilderValidation> {
self.map_err(|violation| MatrixBuilderValidation::RoleInIsolation { role, violation })
}
}

pub type MatrixBuilderMutateResult = Result<(), MatrixBuilderValidation>;
pub type MatrixBuilderBuildResult = Result<MatrixWithFactorSourceIds, MatrixBuilderValidation>;

#[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 {
Expand All @@ -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::<HashSet<_>>();
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()?;

Expand Down Expand Up @@ -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::<HashSet<_>>();
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 {

Expand Down
6 changes: 6 additions & 0 deletions crates/rules/src/matrices/builder/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mod error;
mod matrix_builder;

pub use error::*;
#[allow(unused_imports)]
pub use matrix_builder::*;
36 changes: 36 additions & 0 deletions crates/rules/src/matrices/matrix_with_factor_source_ids.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use crate::prelude::*;

pub type MatrixWithFactorSourceIds = AbstractMatrixBuilderOrBuilt<FactorSourceID, (), ()>;

#[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
}
}
8 changes: 6 additions & 2 deletions crates/rules/src/matrices/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;

0 comments on commit 3b5adb3

Please sign in to comment.