diff --git a/Cargo.lock b/Cargo.lock
index 4f6cf3cbc..33a0bb97c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2761,7 +2761,7 @@ dependencies = [
[[package]]
name = "sargon"
-version = "1.1.86"
+version = "1.1.87"
dependencies = [
"actix-rt",
"aes-gcm",
@@ -2816,7 +2816,7 @@ dependencies = [
[[package]]
name = "sargon-uniffi"
-version = "1.1.86"
+version = "1.1.87"
dependencies = [
"actix-rt",
"assert-json-diff",
diff --git a/README.md b/README.md
index b1a777725..46e436f91 100644
--- a/README.md
+++ b/README.md
@@ -37,11 +37,14 @@ xcode-select --install
Or install `Xcode` from App Store
After installing, you should run the following command to verify the SDK path is set to the Xcode app under `Applications`.
+
```sh
$ xcrun --show-sdk-path
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
```
+
If it doesn't point to the expected path (and instead points to something like `/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk`), make sure you have the PATH exported on your profile (e.g. `.zshrc`):
+
```
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
```
@@ -161,131 +164,138 @@ Alternatively if you wanna skip code cove
## Android
### Prerequisites
+
MacOs
-* #### Install `jenv`
- ```sh
- brew install jenv
- ```
- Dont forget to add to eval to zsh
- ```sh
- export PATH="$HOME/.jenv/bin:$PATH"
- eval "$(jenv init -)"
- ```
-
-* #### Install Java (openjdk@17)
- ```sh
- brew install openjdk@17
- ```
- #### Add `openjdk` version to `jenv`
- ```sh
- jenv add /opt/homebrew/Cellar/openjdk@17/17.0.10/libexec/openjdk.jdk/Contents/Home/
- ```
-* #### `cargo-ndk`
- ```sh
- cargo install cargo-ndk
- ```
-* Download the Command Line tools [here](https://developer.android.com/studio#command-tools) and unzip
- ```sh
- mkdir -p ~/Library/Android/sdk/cmdline-tools
- mv /cmdline-tools ~/Library/Android/sdk/cmdline-tools/latest
- ```
- ```sh
- ## In your profile (like .zshrc)
- export ANDROID_HOME=$HOME/Library/Android/sdk
- export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
- export PATH=$PATH:$ANDROID_HOME/platform-tools
- # Make sure to also include the SDK ROOT in order to build the mac os desktop binaries
- export SDKROOT="`xcrun --show-sdk-path`
- ```
- ```sh
- ## Source your profile
- source ~/.zshrc
- ```
- ```sh
- ## Assert that it works with
- sdkmanager --version
- ```
-* Download the latest ndk
- ```sh
- ## Print the list of available ANDKs
- sdkmanager --list | grep ndk
- ```
- ```sh
- ## Install the latest ndk like ndk;27.1.12297006
- sdkmanager --install "ndk;"
- ```
- ```sh
- ## Export ndk
- ## In your profile (like .bashrc, or .zshrc etc)
- export ANDROID_NDK_HOME=$ANDROID_HOME/ndk
- ```
-
+- #### Install `jenv`
+
+ ```sh
+ brew install jenv
+ ```
+
+ Dont forget to add to eval to zsh
+
+ ```sh
+ export PATH="$HOME/.jenv/bin:$PATH"
+ eval "$(jenv init -)"
+ ```
+
+- #### Install Java (openjdk@17)
+ ```sh
+ brew install openjdk@17
+ ```
+ #### Add `openjdk` version to `jenv`
+ ```sh
+ jenv add /opt/homebrew/Cellar/openjdk@17/17.0.10/libexec/openjdk.jdk/Contents/Home/
+ ```
+- #### `cargo-ndk`
+ ```sh
+ cargo install cargo-ndk
+ ```
+- Download the Command Line tools [here](https://developer.android.com/studio#command-tools) and unzip
+ ```sh
+ mkdir -p ~/Library/Android/sdk/cmdline-tools
+ mv /cmdline-tools ~/Library/Android/sdk/cmdline-tools/latest
+ ```
+ ```sh
+ ## In your profile (like .zshrc)
+ export ANDROID_HOME=$HOME/Library/Android/sdk
+ export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
+ export PATH=$PATH:$ANDROID_HOME/platform-tools
+ # Make sure to also include the SDK ROOT in order to build the mac os desktop binaries
+ export SDKROOT="`xcrun --show-sdk-path`
+ ```
+ ```sh
+ ## Source your profile
+ source ~/.zshrc
+ ```
+ ```sh
+ ## Assert that it works with
+ sdkmanager --version
+ ```
+- Download the latest ndk
+ ```sh
+ ## Print the list of available ANDKs
+ sdkmanager --list | grep ndk
+ ```
+ ```sh
+ ## Install the latest ndk like ndk;27.1.12297006
+ sdkmanager --install "ndk;"
+ ```
+ ```sh
+ ## Export ndk
+ ## In your profile (like .bashrc, or .zshrc etc)
+ export ANDROID_NDK_HOME=$ANDROID_HOME/ndk
+ ```
+
Linux
-* #### (Optional) Install build essentials
- ```sh
- apt-get install build-essential
- apt-get install cmake
- ```
-* #### Install Java (openjdk-17)
- ```sh
- apt-get install openjdk-17-jre
- ```
-* #### `cargo-ndk`
- ```sh
- cargo install cargo-ndk
- ```
-* Download the Command Line tools [here](https://developer.android.com/studio#command-tools) and unzip
- ```sh
- mkdir -p ~/Library/Android/sdk/cmdline-tools
- mv /cmdline-tools ~/Library/Android/sdk/cmdline-tools/latest
- ```
- ```sh
- ## In your profile (like .zshrc)
- export ANDROID_HOME=$HOME/Library/Android/sdk
- export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
- export PATH=$PATH:$ANDROID_HOME/platform-tools
- ```
- ```sh
- ## Source your profile
- source ~/.bashrc
- ```
- ```sh
- ## Assert that it works with
- sdkmanager --version
- ```
-* Download the latest ndk
- ```sh
- ## Print the list of available ANDKs
- sdkmanager --list | grep ndk
- ```
- ```sh
- ## Install the latest ndk like ndk;27.1.12297006
- sdkmanager --install "ndk;"
- ```
- ```sh
- ## Export ndk
- ## In your profile (like .bashrc, or .zshrc etc)
- export ANDROID_NDK_HOME=$ANDROID_HOME/ndk
- ```
-
+- #### (Optional) Install build essentials
+ ```sh
+ apt-get install build-essential
+ apt-get install cmake
+ ```
+- #### Install Java (openjdk-17)
+ ```sh
+ apt-get install openjdk-17-jre
+ ```
+- #### `cargo-ndk`
+ ```sh
+ cargo install cargo-ndk
+ ```
+- Download the Command Line tools [here](https://developer.android.com/studio#command-tools) and unzip
+ ```sh
+ mkdir -p ~/Library/Android/sdk/cmdline-tools
+ mv /cmdline-tools ~/Library/Android/sdk/cmdline-tools/latest
+ ```
+ ```sh
+ ## In your profile (like .zshrc)
+ export ANDROID_HOME=$HOME/Library/Android/sdk
+ export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
+ export PATH=$PATH:$ANDROID_HOME/platform-tools
+ ```
+ ```sh
+ ## Source your profile
+ source ~/.bashrc
+ ```
+ ```sh
+ ## Assert that it works with
+ sdkmanager --version
+ ```
+- Download the latest ndk
+ ```sh
+ ## Print the list of available ANDKs
+ sdkmanager --list | grep ndk
+ ```
+ ```sh
+ ## Install the latest ndk like ndk;27.1.12297006
+ sdkmanager --install "ndk;"
+ ```
+ ```sh
+ ## Export ndk
+ ## In your profile (like .bashrc, or .zshrc etc)
+ export ANDROID_NDK_HOME=$ANDROID_HOME/ndk
+ ```
+
### Install Rust targets (Android)
+
```sh
rustup target add aarch64-linux-android armv7-linux-androideabi
```
### Install Rust targets (Desktop Binaries)
+
MacOs
```sh
rustup target add aarch64-apple-darwin
```
+
@@ -294,9 +304,11 @@ rustup target add aarch64-apple-darwin
```sh
rustup target add x86_64-unknown-linux-gnu
```
+
### Build
+
```sh
cd jvm
# (Debug)
@@ -306,10 +318,13 @@ cd jvm
```
### Unit Tests (Running locally)
+
```sh
./gradlew sargon-android:testDebugUnitTest
```
+
### Instrumentation Tests (Running on a device)
+
```sh
# Make sure you have a device or emulator is connected to ADB
./gradlew sargon-android:connectedDebugAndroidTest
@@ -321,6 +336,9 @@ cd jvm
### Locally
+> [!TIP]
+> We really recommend you release using CD.
+
#### Prerequisites
> [!IMPORTANT]
@@ -379,22 +397,28 @@ See [`.github/workflows/release.yml`](.github/workflows/release.yml)
## Android
### Locally
-In order to build sargon for local development we will leverage the local maven repository. Instead of publishing the package in a maven server, we can publish it locally.
-In order to publish both android and desktop binaries with a simple command run
+In order to build sargon for local development we will leverage the local maven repository. Instead of publishing the package in a maven server, we can publish it locally.
+
+In order to publish both android and desktop binaries with a simple command run
+
```sh
cd jvm/
./gradlew sargon-android:buildForLocalDev // This builds both sargon-android and sargon-desktop-bins
```
+
This will produce the following message when successfully finished
+
```txt
> Task :sargon-android:buildForLocalDev
✅ Library is published in maven local with version:
1.1.19-c74d9cbf-SNAPSHOT
```
+
Note that such local maven builds are in debug mode and have a `-SNAPSHOT` suffix.
-Copy the version name to your project but make sure that `mavenLocal()` is included in your project's `settings.gradle`
+Copy the version name to your project but make sure that `mavenLocal()` is included in your project's `settings.gradle`
+
```gradle
dependencyResolutionManagement {
...
@@ -404,8 +428,10 @@ dependencyResolutionManagement {
}
}
```
+
> [!TIP]
> The libraries that are published in local maven will reside in:
+>
> ```
> $HOME/.m2/repository/com/radixdlt/sargon
> ```
@@ -414,29 +440,29 @@ dependencyResolutionManagement {
Two modules are published in [Github's maven](https://github.com/radixdlt/sargon/packages/).
-- `sargon-android`
+- `sargon-android`
- (See [`.github/workflows/release-android.yml`](.github/workflows/release-android.yml))
+ (See [`.github/workflows/release-android.yml`](.github/workflows/release-android.yml))
- Contains the generated UniFFi Kotlin code and the runtime sargon binaries, in different architectures. It also contains the JNA dependency.
+ Contains the generated UniFFi Kotlin code and the runtime sargon binaries, in different architectures. It also contains the JNA dependency.
- Import with:
+ Import with:
- ```
- implementation("com.radixdlt.sargon:sargon-android:")
- ```
+ ```
+ implementation("com.radixdlt.sargon:sargon-android:")
+ ```
-- `sargon-desktop-bins`
+- `sargon-desktop-bins`
- (See [`.github/workflows/release-desktop-bins.yml`](.github/workflows/release-desktop.yml))
+ (See [`.github/workflows/release-desktop-bins.yml`](.github/workflows/release-desktop.yml))
- Contains only the runtime sargon binaries, built for desktop. Used when running Unit tests.
+ Contains only the runtime sargon binaries, built for desktop. Used when running Unit tests.
- Import with:
+ Import with:
- ```
- testRuntimeOnly("com.radixdlt.sargon:sargon-desktop-bins:")
- ```
+ ```
+ testRuntimeOnly("com.radixdlt.sargon:sargon-desktop-bins:")
+ ```
> [!IMPORTANT]
> Currently only supporting `aarch64-apple-darwin` (apple silicon) and `x86_64-unknown-linux-gnu`. So when running Unit tests for your client app, make sure to run them on an apple silicon or linux machine. We can add more architectures in the future, if requested.
diff --git a/apple/Sources/Sargon/Protocols/EntityProtocol.swift b/apple/Sources/Sargon/Protocols/EntityProtocol.swift
index 507df188f..8c151dda3 100644
--- a/apple/Sources/Sargon/Protocols/EntityProtocol.swift
+++ b/apple/Sources/Sargon/Protocols/EntityProtocol.swift
@@ -9,7 +9,9 @@ public protocol BaseBaseEntityProtocol: SargonModel {}
#endif // DEBUG
// MARK: - EntityBaseProtocol
-public protocol EntityBaseProtocol: BaseBaseEntityProtocol, CustomStringConvertible, Identifiable where ID == EntityAddress {
+public protocol EntityBaseProtocol: BaseBaseEntityProtocol, CustomStringConvertible, Identifiable
+ where ID == EntityAddress
+{
associatedtype EntityAddress: BaseEntityAddressProtocol
var networkId: NetworkID { get }
var displayName: DisplayName { get }
@@ -41,31 +43,27 @@ extension EntityBaseProtocol {
flags.contains(.hiddenByUser)
}
- public var virtualHierarchicalDeterministicFactorInstances: Set {
+ // TODO: MOVE TO SARGON
+ public var virtualHierarchicalDeterministicFactorInstances:
+ Set
+ {
var factorInstances = Set()
switch securityState {
case let .unsecured(unsecuredEntityControl):
factorInstances.insert(unsecuredEntityControl.transactionSigning)
- if let authSigning = unsecuredEntityControl.authenticationSigning {
- factorInstances.insert(authSigning)
- }
return factorInstances
- // TODO: Handle when MFA is integrated
-// case .securified(value: let value):
-// return []
}
}
+ // TODO: MOVE TO SARGON
public var hasAuthenticationSigningKey: Bool {
switch securityState {
case let .unsecured(unsecuredEntityControl):
- unsecuredEntityControl.authenticationSigning != nil
- // TODO: Handle when MFA is integrated
-// case .securified(value: let value):
-// false // TODO handle that in the future
+ false
}
}
+ // TODO: MOVE TO SARGON
public var deviceFactorSourceID: FactorSourceIDFromHash? {
switch self.securityState {
case let .unsecured(control):
@@ -75,9 +73,6 @@ extension EntityBaseProtocol {
}
return factorSourceID
- // TODO: Handle when MFA is integrated
-// case .securified(value: _):
-// return nil // TODO handle that in the future
}
}
}
@@ -155,7 +150,8 @@ extension EntityProtocol {
self.init(
networkID: networkID,
address: address,
- securityState: .unsecured(value: .init(transactionSigning: factorInstance, authenticationSigning: nil)),
+ securityState: .unsecured(
+ value: .init(transactionSigning: factorInstance, provisionalSecurifiedConfig: nil)),
displayName: displayName,
extraProperties: extraProperties
)
diff --git a/apple/Tests/TestCases/Profile/Account/AccountTests.swift b/apple/Tests/TestCases/Profile/Account/AccountTests.swift
index a98dc0e20..2b56e5962 100644
--- a/apple/Tests/TestCases/Profile/Account/AccountTests.swift
+++ b/apple/Tests/TestCases/Profile/Account/AccountTests.swift
@@ -43,12 +43,6 @@ final class AccountTests: EntityProtocolTest {
XCTAssertNil(sut.deviceFactorSourceID)
}
- func test_virtual_hd_deterministic_factor_instances_includes_auth_signing_if_set() {
- var sut = SUT.sample
- sut.securityState = .unsecured(value: .init(transactionSigning: .sample, authenticationSigning: .sampleOther))
- XCTAssertEqual(sut.virtualHierarchicalDeterministicFactorInstances.count, 2)
- }
-
func test_new() {
let fi: HierarchicalDeterministicFactorInstance = .sample
let sut = SUT(networkID: .sample, factorInstance: fi, displayName: .sample, extraProperties: .sample)
diff --git a/crates/sargon-uniffi/Cargo.toml b/crates/sargon-uniffi/Cargo.toml
index 5eb2f842f..44b7532aa 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.86"
+version = "1.1.87"
edition = "2021"
build = "build.rs"
@@ -25,8 +25,8 @@ radix-engine-toolkit = { git = "https://github.com/radixdlt/radix-engine-toolkit
# zeroize = "1.7.0"
zeroize = { git = "https://github.com/RustCrypto/utils", rev = "df6d2f48a5e8afe8eef04ba32e2af55bacb41375", features = [
- "zeroize_derive",
- "derive",
+ "zeroize_derive",
+ "derive",
] }
log = "0.4.20"
@@ -37,9 +37,9 @@ pretty_env_logger = { git = "https://github.com/seanmonstar/pretty-env-logger/",
# derive_more = "1.0.0-beta.6"
derive_more = { git = "https://github.com/JelteF/derive_more", rev = "1196b2dd7a366c06db621093884adbc379fc0f0a", features = [
- "debug",
- "display",
- "from_str",
+ "debug",
+ "display",
+ "from_str",
] }
# thiserror = "1.0.50"
@@ -47,19 +47,19 @@ thiserror = { git = "https://github.com/dtolnay/thiserror/", rev = "a7d220d7915f
# iso8601-timestamp = "0.2.16"
iso8601-timestamp = { git = "https://github.com/Lantern-chat/iso8601-timestamp/", rev = "e5a3f2a5911759bc6b0d8100b032a6b4dd6e12c1", features = [
- "serde",
- "std",
+ "serde",
+ "std",
] }
# uuid = "1.6.1"
uuid = { git = "https://github.com/uuid-rs/uuid/", rev = "c8891073248ddc7faa8c53ac9ceb629a341c7b9b", features = [
- "v4",
- "serde",
+ "v4",
+ "serde",
] }
# strum = "0.26.1"
strum = { git = "https://github.com/Peternator7/strum/", rev = "f746c3699acf150112e26c00e6c8ca666d8d068d", features = [
- "derive",
+ "derive",
] }
# enum-iterator = "1.4.1"
@@ -82,7 +82,7 @@ enum-as-inner = { git = "https://github.com/bluejekyll/enum-as-inner/", rev = "c
# uniffi = PRE "0.29.X" + Alex Cyons unmerged JNA Kotlin binding fix
uniffi = { git = "https://github.com/sajjon/uniffi-rs/", rev = "fe40d992b32103876112713cb5e8303b54647183", features = [
- "cli",
+ "cli",
] }
# assert-json-diff = "2.0.2"
@@ -100,8 +100,8 @@ regex = { git = "https://github.com/rust-lang/regex/", rev = "72f889ef3cca59ebac
# clap = "4.5.1"
clap = { git = "https://github.com/clap-rs/clap/", rev = "8a7a13a5618cfdc4ff328624a5266e7b4d88649a", default-features = false, features = [
- "std",
- "derive",
+ "std",
+ "derive",
], optional = true }
# camino = "1.0.8"
@@ -129,7 +129,7 @@ security-framework-sys = "=2.10.0"
[dev-dependencies]
# uniffi = PRE "0.29.X" + Alex Cyons unmerged JNA Kotlin binding fix
uniffi = { git = "https://github.com/sajjon/uniffi-rs/", rev = "fe40d992b32103876112713cb5e8303b54647183", features = [
- "bindgen-tests",
+ "bindgen-tests",
] }
# actix-rt = "3.3.0"
@@ -138,7 +138,7 @@ actix-rt = { git = "https://github.com/actix/actix-net", rev = "57fd6ea8098d1f2d
[build-dependencies]
# uniffi = PRE "0.29.X" + Alex Cyons unmerged JNA Kotlin binding fix
uniffi = { git = "https://github.com/sajjon/uniffi-rs/", rev = "fe40d992b32103876112713cb5e8303b54647183", features = [
- "build",
+ "build",
] }
# cargo_toml = "0.15.3"
diff --git a/crates/sargon-uniffi/src/core/error/common_error.rs b/crates/sargon-uniffi/src/core/error/common_error.rs
index c75ab7c86..759c67792 100644
--- a/crates/sargon-uniffi/src/core/error/common_error.rs
+++ b/crates/sargon-uniffi/src/core/error/common_error.rs
@@ -830,6 +830,38 @@ pub enum CommonError {
#[error("Failed to automatically build shield, reason: '{underlying}'")]
AutomaticShieldBuildingFailure { underlying: String } = 10233,
+
+ #[error("No Transaction Signing Factor")]
+ NoTransactionSigningFactorInstance = 10234,
+
+ #[error("Authentication Signing FactorInstance not securified")]
+ AuthenticationSigningFactorInstanceNotSecurified = 10235,
+
+ #[error("SecurityEntityControl has no QueuedTransaction, unable to mark it as cancelled")]
+ SecurityEntityControlHasNoProvisionallyQueuedTransaction = 10236,
+
+ #[error("SecurityEntityControl has derived instances, which would be lost if discarded. Implement a way to put them back in the cache.")]
+ SecurityEntityControlCannotChangeProvisionalAlreadyDerivedInstances = 10237,
+
+ #[error("SecurityEntityControl has QueuedTransaction, unable override it, use `cancel_queued_transaction`")]
+ SecurityEntityControlCannotChangeProvisionalAlreadyHasQueuedTransaction =
+ 10238,
+
+ #[error(
+ "Entity kind of FactorInstances does not match EntityKind of entity"
+ )]
+ SecurityStructureOfFactorInstancesEntityDiscrepancyInEntityKind {
+ entity_kind_of_entity: CAP26EntityKind,
+ entity_kind_of_factor_instances: CAP26EntityKind,
+ } = 10239,
+
+ #[error(
+ "Cannot securify entity it is already securified according to profile"
+ )]
+ CannotSecurifyEntityItIsAlreadySecurifiedAccordingToProfile = 10240,
+
+ #[error("Cannot securify entity that has provisional security config")]
+ CannotSecurifyEntityHasProvisionalSecurityConfig = 10241,
}
#[uniffi::export]
diff --git a/crates/sargon-uniffi/src/profile/mfa/secured_entity_control/secured_entity_control.rs b/crates/sargon-uniffi/src/profile/mfa/secured_entity_control/secured_entity_control.rs
index eb3565423..84874d88e 100644
--- a/crates/sargon-uniffi/src/profile/mfa/secured_entity_control/secured_entity_control.rs
+++ b/crates/sargon-uniffi/src/profile/mfa/secured_entity_control/secured_entity_control.rs
@@ -5,10 +5,16 @@ use crate::prelude::*;
/// which user has created has been applied to it.
#[derive(Clone, PartialEq, Eq, Hash, uniffi::Record)]
pub struct SecuredEntityControl {
+ /// Virtual Entity Creation (Factor)Instance
+ ///
+ /// Optional since if we recovered this SecuredEntityControl part of
+ /// account recovery scan we might not know the veci
+ pub veci: Option,
+
/// The address of the access controller which controls this entity.
///
/// Looking up the public key (hashes) set in the key-value store at
- /// this address reveils the true factors (public keys) used to protect
+ /// this address reveals the true factors (public keys) used to protect
/// this entity. It will be the same as the ones in `security_structure`
/// if we have not changed them locally, which we should not do unless
/// we are sure the Ledger corresponds to the values in `security_structure`.
@@ -17,4 +23,8 @@ pub struct SecuredEntityControl {
/// The believed-to-be-current security structure of FactorInstances which
/// secures this entity.
pub security_structure: SecurityStructureOfFactorInstances,
+
+ /// A provisional new security structure configuration which user
+ /// is about to change to
+ pub provisional_securified_config: Option,
}
diff --git a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_instances.rs b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_instances.rs
index 8563e0846..78c7806b1 100644
--- a/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_instances.rs
+++ b/crates/sargon-uniffi/src/profile/mfa/security_structures/security_structures/security_structure_of_factor_instances.rs
@@ -4,6 +4,9 @@ use sargon::SecurityStructureOfFactorInstances as InternalSecurityStructureOfFac
/// A MatrixOfFactorInstances and an ID which identifies it, this is
/// the Profile data structure representation of the owner key hashes which
/// have been uploaded as Scrypto AccessRules on the AccessController on-ledger.
+///
+/// Also contains an authentication signing factor instance which is used for
+/// Rola.
#[derive(Clone, PartialEq, Eq, Hash, InternalConversion, uniffi::Record)]
pub struct SecurityStructureOfFactorInstances {
/// The ID of the `SecurityStructureOfFactorSourceIDs` in
@@ -15,4 +18,16 @@ pub struct SecurityStructureOfFactorInstances {
/// The structure of factors to use for certain roles, Primary, Recovery
/// and Confirmation role.
pub matrix_of_factors: MatrixOfFactorInstances,
+
+ /// The authentication signing factor instance which is used to sign
+ /// proof of ownership - aka "True Rola Key". User can select which FactorSource
+ /// to use during Shield Building, but typically most users will use the
+ /// DeviceFactorSource which is default. DerivationPath is in securified
+ /// KeySpace of course.
+ ///
+ /// Non-optional since we can replace it with a new one for entities
+ /// we have recovered during Onboarding Account Recovery Scan for securified
+ /// entities
+ pub authentication_signing_factor_instance:
+ HierarchicalDeterministicFactorInstance,
}
diff --git a/crates/sargon-uniffi/src/profile/v100/entity_security_state/mod.rs b/crates/sargon-uniffi/src/profile/v100/entity_security_state/mod.rs
index a7af15c95..644a39423 100644
--- a/crates/sargon-uniffi/src/profile/v100/entity_security_state/mod.rs
+++ b/crates/sargon-uniffi/src/profile/v100/entity_security_state/mod.rs
@@ -1,5 +1,7 @@
mod entity_security_state;
+mod provisional_securified_config;
mod unsecured_entity_control;
pub use entity_security_state::*;
+pub use provisional_securified_config::*;
pub use unsecured_entity_control::*;
diff --git a/crates/sargon-uniffi/src/profile/v100/entity_security_state/provisional_securified_config.rs b/crates/sargon-uniffi/src/profile/v100/entity_security_state/provisional_securified_config.rs
new file mode 100644
index 000000000..ebf499462
--- /dev/null
+++ b/crates/sargon-uniffi/src/profile/v100/entity_security_state/provisional_securified_config.rs
@@ -0,0 +1,46 @@
+use crate::prelude::*;
+use sargon::{
+ ProvisionalSecurifiedConfig as InternalProvisionalSecurifiedConfig,
+ ProvisionalSecurifiedTransactionQueued as InternalProvisionalSecurifiedTransactionQueued,
+};
+
+/// A tuple of a `SecurityStructureOfFactorInstances` and a `TransactionIntentHash`
+/// which represents a queued transaction to which is changing the security structure
+/// if some entity. Since this provisional state is set on the entity itself, no
+/// need to store the entity address here.
+#[derive(Clone, PartialEq, Eq, Hash, InternalConversion, uniffi::Record)]
+pub struct ProvisionalSecurifiedTransactionQueued {
+ /// The FactorInstances we are changing to.
+ pub factor_instances: SecurityStructureOfFactorInstances,
+
+ /// The ID of the queued transaction which is changing the security structure
+ /// to `factor_instances`.
+ pub txid: TransactionIntentHash,
+}
+
+/// The different intermediary states of changing the security structure of an entity.
+/// This type is put in an `Option` on either `UnsecuredEntityControl` or `SecurifiedEntityControl`,
+/// and if `None` it means user has no provisionally changed security structure. If set, it contains
+/// these different variants:
+/// * `ShieldSelected` - User has selected which security shield to use for some entity,
+/// * `FactorInstancesDerived` - Sargon has provided a `SecurityStructureOfFactorInstances` but
+/// user has not made a transaction to apply it to the entity yet.
+/// * `TransactionQueued` - User has signed and queued a transaction changing to `SecurityStructureOfFactorInstances`
+#[derive(Clone, PartialEq, Eq, Hash, InternalConversion, uniffi::Enum)]
+pub enum ProvisionalSecurifiedConfig {
+ /// User has selected which security shield to use for some entity,
+ /// but no FactorInstances has been provided yet.
+ ShieldSelected { value: SecurityStructureID },
+
+ /// User has fully prepared a `SecurityStructureOfFactorInstances` but
+ /// not made a transaction to apply it to the entity yet.
+ FactorInstancesDerived {
+ value: SecurityStructureOfFactorInstances,
+ },
+
+ /// User has made queued a transaction to apply a `SecurityStructureOfFactorInstances`
+ /// but it has not been submitted (confirmed) yet.
+ TransactionQueued {
+ value: ProvisionalSecurifiedTransactionQueued,
+ },
+}
diff --git a/crates/sargon-uniffi/src/profile/v100/entity_security_state/unsecured_entity_control.rs b/crates/sargon-uniffi/src/profile/v100/entity_security_state/unsecured_entity_control.rs
index f8e5486bd..faa951d6f 100644
--- a/crates/sargon-uniffi/src/profile/v100/entity_security_state/unsecured_entity_control.rs
+++ b/crates/sargon-uniffi/src/profile/v100/entity_security_state/unsecured_entity_control.rs
@@ -11,8 +11,8 @@ pub struct UnsecuredEntityControl {
// /// also controls this entity and is used for signing transactions.
pub transaction_signing: HierarchicalDeterministicFactorInstance,
- /// The factor instance which can be used for ROLA.
- pub authentication_signing: Option,
+ /// The provisional security structure configuration
+ pub provisional_securified_config: Option,
}
#[uniffi::export]
diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml
index 557c9dfa7..eac4de308 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.86"
+version = "1.1.87"
edition = "2021"
build = "build.rs"
@@ -15,8 +15,8 @@ crate-type = ["staticlib", "cdylib", "lib"]
# zeroize = "1.7.0"
zeroize = { git = "https://github.com/RustCrypto/utils", rev = "df6d2f48a5e8afe8eef04ba32e2af55bacb41375", features = [
- "zeroize_derive",
- "derive",
+ "zeroize_derive",
+ "derive",
] }
log = "0.4.20"
@@ -27,22 +27,22 @@ pretty_env_logger = { git = "https://github.com/seanmonstar/pretty-env-logger/",
# derive_more = "1.0.0"
derive_more = { git = "https://github.com/JelteF/derive_more", rev = "d7f5b9e94d024790682f6fc4dcca13941cce64c8", features = [
- "add",
- "as_ref",
- "debug",
- "deref",
- "deref_mut",
- "display",
- "from",
- "from_str",
- "mul",
+ "add",
+ "as_ref",
+ "debug",
+ "deref",
+ "deref_mut",
+ "display",
+ "from",
+ "from_str",
+ "mul",
] }
serde = { version = "1.0.193", features = ["derive", "rc", "std"] }
# serde_json = "1.0.108"
serde_json = { git = "https://github.com/serde-rs/json/", rev = "4bc1eaa03a6160593575bc9bc60c94dba4cab1e3", features = [
- "preserve_order",
+ "preserve_order",
] }
# serde_with = "3.4.0"
@@ -59,29 +59,29 @@ thiserror = { git = "https://github.com/dtolnay/thiserror/", rev = "a7d220d7915f
# iso8601-timestamp = "0.2.16"
iso8601-timestamp = { git = "https://github.com/Lantern-chat/iso8601-timestamp/", rev = "e5a3f2a5911759bc6b0d8100b032a6b4dd6e12c1", features = [
- "serde",
- "std",
+ "serde",
+ "std",
] }
# uuid = "1.6.1"
uuid = { git = "https://github.com/uuid-rs/uuid/", rev = "c8891073248ddc7faa8c53ac9ceb629a341c7b9b", features = [
- "v4",
- "serde",
+ "v4",
+ "serde",
] }
# strum = "0.26.1"
strum = { git = "https://github.com/Peternator7/strum/", rev = "f746c3699acf150112e26c00e6c8ca666d8d068d", features = [
- "derive",
+ "derive",
] }
sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v1.3.0" }
radix-rust = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v1.3.0", features = [
- "serde",
+ "serde",
] }
radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v1.3.0" }
radix-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v1.3.0", features = [
- "serde",
- "secp256k1_sign_and_validate",
+ "serde",
+ "secp256k1_sign_and_validate",
] }
radix-common-derive = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v1.3.0" }
radix-engine-interface = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v1.3.0" }
@@ -112,10 +112,10 @@ enum-as-inner = { git = "https://github.com/bluejekyll/enum-as-inner/", rev = "c
# SLIP10 implementation
# iota_crypto = "0.23.2"
iota-crypto = { git = "https://github.com/iotaledger/crypto.rs", rev = "8c13125541e762206e2dc57b8bfde89c7f6ce8e3", features = [
- "slip10",
- "ed25519",
- "secp256k1",
- "x25519",
+ "slip10",
+ "ed25519",
+ "secp256k1",
+ "x25519",
] }
# Transitive dependency of iota_crypto - used to construct PubKey from uncompressed bytes.
# k256 = "0.13.3"
@@ -123,9 +123,9 @@ k256 = { git = "https://github.com/RustCrypto/elliptic-curves", rev = "e158ce5cf
# bip39 = "2.0.0"
bip39 = { git = "https://github.com/rust-bitcoin/rust-bip39", rev = "a30760beac21d595b2bda376df4f4e6bf029bcc5", features = [
- "serde",
- "zeroize",
- "french",
+ "serde",
+ "zeroize",
+ "french",
] }
# assert-json-diff = "2.0.2"
@@ -143,8 +143,8 @@ regex = { git = "https://github.com/rust-lang/regex/", rev = "72f889ef3cca59ebac
# clap = "4.5.1"
clap = { git = "https://github.com/clap-rs/clap/", rev = "8a7a13a5618cfdc4ff328624a5266e7b4d88649a", default-features = false, features = [
- "std",
- "derive",
+ "std",
+ "derive",
], optional = true }
# camino = "1.0.8"
@@ -159,10 +159,10 @@ pretty_assertions = { git = "https://github.com/rust-pretty-assertions/rust-pret
# AES for Profile Encryption
# aes-gcm = "10.3"
aes-gcm = { git = "https://github.com/RustCrypto/AEADs", rev = "7e82b01cd4901f6a35b5153536f11b87f5e4e622", default-features = false, features = [
- "aes",
- "alloc",
- "getrandom",
- "zeroize",
+ "aes",
+ "alloc",
+ "getrandom",
+ "zeroize",
] }
# hkdf = "0.12.4"
@@ -172,7 +172,7 @@ base64 = { git = "https://github.com/marshallpierce/rust-base64.git", rev = "e14
# reqwest = "0.12.3"
reqwest = { git = "https://github.com/seanmonstar/reqwest", rev = "0720159f6369f54e045a1fd315e0f24b7a0b4a39", default-features = false, features = [
- "native-tls-vendored",
+ "native-tls-vendored",
] }
# async-std = "1.13.0"
diff --git a/crates/sargon/fixtures/transaction/create_access_controller_for_account.rtm b/crates/sargon/fixtures/transaction/create_access_controller_for_account.rtm
new file mode 100644
index 000000000..feecabe59
--- /dev/null
+++ b/crates/sargon/fixtures/transaction/create_access_controller_for_account.rtm
@@ -0,0 +1,98 @@
+CALL_METHOD
+ Address("account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87")
+ "securify"
+;
+TAKE_FROM_WORKTOP
+ Address("resource_rdx1nfxxxxxxxxxxaccwnrxxxxxxxxx006664022062xxxxxxxxxaccwnr")
+ Decimal("1")
+ Bucket("bucket1")
+;
+CREATE_ACCESS_CONTROLLER
+ Bucket("bucket1")
+ Tuple(
+ Enum<2u8>(
+ Enum<1u8>(
+ Array(
+ Enum<0u8>(
+ Enum<2u8>(
+ 2u8,
+ Array(
+ Enum<0u8>(
+ NonFungibleGlobalId("resource_rdx1nfxxxxxxxxxxed25sgxxxxxxxxx002236757237xxxxxxxxxed25sg:[2dabcc6872c45a625bccc21be9e666bfbc62b1f87a16f3848dd877ba22]")
+ ),
+ Enum<0u8>(
+ NonFungibleGlobalId("resource_rdx1nfxxxxxxxxxxed25sgxxxxxxxxx002236757237xxxxxxxxxed25sg:[7225b29de13d7d06e0e9f406fe15165677573c9106ee036ad52bee2864]")
+ )
+ )
+ )
+ ),
+ Enum<0u8>(
+ Enum<4u8>(
+ Array()
+ )
+ )
+ )
+ )
+ ),
+ Enum<2u8>(
+ Enum<1u8>(
+ Array(
+ Enum<0u8>(
+ Enum<2u8>(
+ 0u8,
+ Array()
+ )
+ ),
+ Enum<0u8>(
+ Enum<4u8>(
+ Array(
+ Enum<0u8>(
+ NonFungibleGlobalId("resource_rdx1nfxxxxxxxxxxed25sgxxxxxxxxx002236757237xxxxxxxxxed25sg:[2dabcc6872c45a625bccc21be9e666bfbc62b1f87a16f3848dd877ba22]")
+ ),
+ Enum<0u8>(
+ NonFungibleGlobalId("resource_rdx1nfxxxxxxxxxxed25sgxxxxxxxxx002236757237xxxxxxxxxed25sg:[7225b29de13d7d06e0e9f406fe15165677573c9106ee036ad52bee2864]")
+ )
+ )
+ )
+ )
+ )
+ )
+ ),
+ Enum<2u8>(
+ Enum<1u8>(
+ Array(
+ Enum<0u8>(
+ Enum<2u8>(
+ 0u8,
+ Array()
+ )
+ ),
+ Enum<0u8>(
+ Enum<4u8>(
+ Array(
+ Enum<0u8>(
+ NonFungibleGlobalId("resource_rdx1nfxxxxxxxxxxed25sgxxxxxxxxx002236757237xxxxxxxxxed25sg:[31262cc8dc5e9a49d1fe0ea8e60a17ef36d1ea857db94cca3e1e2a52dd]")
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ Enum<1u8>(
+ 20160u32
+ )
+ Enum<0u8>()
+;
+SET_METADATA
+ Address("account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87")
+ "owner_keys"
+ Enum<143u8>(
+ Array(
+ Enum<1u8>(
+ Bytes("cb3f6086cd08a1d0ab10139a9c6d191783edb534059f7b4716dc5d239e")
+ )
+ )
+ )
+;
\ No newline at end of file
diff --git a/crates/sargon/fixtures/transaction/create_access_controller_for_persona.rtm b/crates/sargon/fixtures/transaction/create_access_controller_for_persona.rtm
new file mode 100644
index 000000000..c4809655d
--- /dev/null
+++ b/crates/sargon/fixtures/transaction/create_access_controller_for_persona.rtm
@@ -0,0 +1,92 @@
+CALL_METHOD
+ Address("identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw")
+ "securify"
+;
+TAKE_FROM_WORKTOP
+ Address("resource_rdx1nfxxxxxxxxxxdntwnrxxxxxxxxx002876444928xxxxxxxxxdntwnr")
+ Decimal("1")
+ Bucket("bucket1")
+;
+CREATE_ACCESS_CONTROLLER
+ Bucket("bucket1")
+ Tuple(
+ Enum<2u8>(
+ Enum<1u8>(
+ Array(
+ Enum<0u8>(
+ Enum<2u8>(
+ 1u8,
+ Array(
+ Enum<0u8>(
+ NonFungibleGlobalId("resource_rdx1nfxxxxxxxxxxed25sgxxxxxxxxx002236757237xxxxxxxxxed25sg:[99b417749d9022e73a6d2e025648a928ffbc499be8dc9e55eda900b11f]")
+ )
+ )
+ )
+ ),
+ Enum<0u8>(
+ Enum<4u8>(
+ Array()
+ )
+ )
+ )
+ )
+ ),
+ Enum<2u8>(
+ Enum<1u8>(
+ Array(
+ Enum<0u8>(
+ Enum<2u8>(
+ 0u8,
+ Array()
+ )
+ ),
+ Enum<0u8>(
+ Enum<4u8>(
+ Array(
+ Enum<0u8>(
+ NonFungibleGlobalId("resource_rdx1nfxxxxxxxxxxed25sgxxxxxxxxx002236757237xxxxxxxxxed25sg:[97d5d7196e49781708520322aaf5872214d854122600dd0125c837aefe]")
+ )
+ )
+ )
+ )
+ )
+ )
+ ),
+ Enum<2u8>(
+ Enum<1u8>(
+ Array(
+ Enum<0u8>(
+ Enum<2u8>(
+ 0u8,
+ Array()
+ )
+ ),
+ Enum<0u8>(
+ Enum<4u8>(
+ Array(
+ Enum<0u8>(
+ NonFungibleGlobalId("resource_rdx1nfxxxxxxxxxxed25sgxxxxxxxxx002236757237xxxxxxxxxed25sg:[0b9bdb05d848b70041d7ed45c28dd9d6743a19eb129524d1c623b31784]")
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ Enum<1u8>(
+ 20160u32
+ )
+ Enum<0u8>()
+;
+SET_METADATA
+ Address("identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw")
+ "owner_keys"
+ Enum<143u8>(
+ Array(
+ Enum<1u8>(
+ Bytes("675506ad8d7ce4c602cb06c593c0f10e1cc4dcdf2c4144360ef33ebeef")
+ )
+ )
+ )
+;
\ No newline at end of file
diff --git a/crates/sargon/src/core/error/common_error.rs b/crates/sargon/src/core/error/common_error.rs
index 4528817a7..48a760836 100644
--- a/crates/sargon/src/core/error/common_error.rs
+++ b/crates/sargon/src/core/error/common_error.rs
@@ -827,6 +827,38 @@ pub enum CommonError {
#[error("Failed to automatically build shield, reason: '{underlying}'")]
AutomaticShieldBuildingFailure { underlying: String } = 10233,
+
+ #[error("No Transaction Signing Factor")]
+ NoTransactionSigningFactorInstance = 10234,
+
+ #[error("Authentication Signing FactorInstance not securified")]
+ AuthenticationSigningFactorInstanceNotSecurified = 10235,
+
+ #[error("SecurityEntityControl has no QueuedTransaction, unable to mark it as cancelled")]
+ SecurityEntityControlHasNoProvisionallyQueuedTransaction = 10236,
+
+ #[error("SecurityEntityControl has derived instances, which would be lost if discarded. Implement a way to put them back in the cache.")]
+ SecurityEntityControlCannotChangeProvisionalAlreadyDerivedInstances = 10237,
+
+ #[error("SecurityEntityControl has QueuedTransaction, unable override it, use `cancel_queued_transaction`")]
+ SecurityEntityControlCannotChangeProvisionalAlreadyHasQueuedTransaction =
+ 10238,
+
+ #[error(
+ "Entity kind of FactorInstances does not match EntityKind of entity"
+ )]
+ SecurityStructureOfFactorInstancesEntityDiscrepancyInEntityKind {
+ entity_kind_of_entity: CAP26EntityKind,
+ entity_kind_of_factor_instances: CAP26EntityKind,
+ } = 10239,
+
+ #[error(
+ "Cannot securify entity it is already securified according to profile"
+ )]
+ CannotSecurifyEntityItIsAlreadySecurifiedAccordingToProfile = 10240,
+
+ #[error("Cannot securify entity that has provisional security config")]
+ CannotSecurifyEntityHasProvisionalSecurityConfig = 10241,
}
impl CommonError {
diff --git a/crates/sargon/src/core/types/signatures/signature.rs b/crates/sargon/src/core/types/signatures/signature.rs
index 47514e747..98a873091 100644
--- a/crates/sargon/src/core/types/signatures/signature.rs
+++ b/crates/sargon/src/core/types/signatures/signature.rs
@@ -18,6 +18,16 @@ pub enum Signature {
Ed25519 { value: Ed25519Signature },
}
+impl Signature {
+ /// Returns a `SLIP10Curve`, being the curve of the `Signature`.
+ pub fn curve(&self) -> SLIP10Curve {
+ match self {
+ Self::Ed25519 { .. } => SLIP10Curve::Curve25519,
+ Self::Secp256k1 { .. } => SLIP10Curve::Secp256k1,
+ }
+ }
+}
+
impl Signature {
pub fn to_bytes(&self) -> Vec {
match self {
@@ -118,6 +128,12 @@ mod tests {
assert_ne!(SUT::sample(), SUT::sample_other());
}
+ #[test]
+ fn curve() {
+ assert_eq!(SUT::sample().curve(), SLIP10Curve::Curve25519);
+ assert_eq!(SUT::sample_other().curve(), SLIP10Curve::Secp256k1);
+ }
+
#[test]
fn enum_as_inner() {
assert_eq!(
diff --git a/crates/sargon/src/core/utils/constants.rs b/crates/sargon/src/core/utils/constants.rs
index b5aeba374..649fe36b5 100644
--- a/crates/sargon/src/core/utils/constants.rs
+++ b/crates/sargon/src/core/utils/constants.rs
@@ -27,3 +27,6 @@ pub const MIN_REQUIRED_XRD_FOR_ACCOUNT_DELETION: f64 = 4.0;
/// The delay increment among polling requests is set to 1 second.
/// This means that there will be a 2s delay after first call, a 3s delay after second call, 4s after third and so on.
pub const POLLING_DELAY_INCREMENT_IN_SECONDS: u64 = 1;
+
+/// Number of minutes per day.
+pub const MINUTES_PER_DAY: u32 = 24 * 60;
diff --git a/crates/sargon/src/lib.rs b/crates/sargon/src/lib.rs
index c0d6396ed..2b5e88723 100644
--- a/crates/sargon/src/lib.rs
+++ b/crates/sargon/src/lib.rs
@@ -146,7 +146,8 @@ pub mod prelude {
NonFungibleGlobalId as ScryptoNonFungibleGlobalId,
NonFungibleIdType as ScryptoNonFungibleIdType,
UpperBound as ScryptoUpperBound,
- ACCOUNT_OWNER_BADGE as SCRYPTO_ACCOUNT_OWNER_BADGE, XRD,
+ ACCOUNT_OWNER_BADGE as SCRYPTO_ACCOUNT_OWNER_BADGE,
+ IDENTITY_OWNER_BADGE as SCRYPTO_IDENTITY_OWNER_BADGE, XRD,
},
types::{
ComponentAddress as ScryptoComponentAddress,
@@ -156,11 +157,18 @@ pub mod prelude {
},
ManifestSbor as ScryptoManifestSbor, ScryptoSbor,
};
+
pub(crate) use radix_engine_interface::blueprints::{
+ access_controller::{
+ RecoveryProposal as ScryptoRecoveryProposal,
+ RuleSet as ScryptoRuleSet,
+ },
account::{
DefaultDepositRule as ScryptoDefaultDepositRule,
ResourcePreference as ScryptoResourcePreference,
+ ACCOUNT_SECURIFY_IDENT as SCRYPTO_ACCOUNT_SECURIFY_IDENT,
},
+ identity::IDENTITY_SECURIFY_IDENT as SCRYPTO_IDENTITY_SECURIFY_IDENT,
resource::ResourceOrNonFungible as ScryptoResourceOrNonFungible,
};
pub(crate) use radix_engine_interface::prelude::{
diff --git a/crates/sargon/src/profile/logic/profile_network/profile_network_details.rs b/crates/sargon/src/profile/logic/profile_network/profile_network_details.rs
index c74bbab18..1b7d3b8fe 100644
--- a/crates/sargon/src/profile/logic/profile_network/profile_network_details.rs
+++ b/crates/sargon/src/profile/logic/profile_network/profile_network_details.rs
@@ -118,12 +118,7 @@ impl AuthorizedPersonaSimple {
let shared_accounts = self.accounts_for_display(non_hidden_accounts)?;
- let has_auth_signing_key = match &persona.security_state {
- EntitySecurityState::Unsecured { value: uec } => {
- uec.authentication_signing.is_some()
- }
- _ => false, // TODO change that
- };
+ let has_auth_signing_key = persona.is_securified();
Ok(AuthorizedPersonaDetailed::new(
persona.address,
persona.display_name.clone(),
diff --git a/crates/sargon/src/profile/mfa/secured_entity_control/secured_entity_control.rs b/crates/sargon/src/profile/mfa/secured_entity_control/secured_entity_control.rs
index 4d7fa30e2..d25425ee2 100644
--- a/crates/sargon/src/profile/mfa/secured_entity_control/secured_entity_control.rs
+++ b/crates/sargon/src/profile/mfa/secured_entity_control/secured_entity_control.rs
@@ -26,13 +26,112 @@ pub struct SecuredEntityControl {
/// The believed-to-be-current security structure of FactorInstances which
/// secures this entity.
pub security_structure: SecurityStructureOfFactorInstances,
+
+ /// A provisional new security structure configuration which user
+ /// is about to change to
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub provisional_securified_config: Option,
+}
+
+impl HasProvisionalSecurifiedConfig for SecuredEntityControl {
+ fn get_provisional(&self) -> Option {
+ self.provisional_securified_config.clone()
+ }
+
+ fn set_provisional_unchecked(
+ &mut self,
+ provisional_securified_config: impl Into<
+ Option,
+ >,
+ ) {
+ self.provisional_securified_config =
+ provisional_securified_config.into();
+ }
+}
+
+pub trait HasProvisionalSecurifiedConfig {
+ fn get_provisional(&self) -> Option;
+
+ /// # Throws
+ /// Throws if the provisional is already `Some(ProvisionalSecurifiedConfig::TransactionQueued)`,
+ /// you need to first cancel the transaction and then call the method `cancel_queued_transaction` on
+ /// this type
+ fn set_provisional(
+ &mut self,
+ provisional_securified_config: impl Into<
+ Option,
+ >,
+ ) -> Result<()> {
+ let provisional = provisional_securified_config.into();
+ let maybe_existing = self.get_provisional();
+ let Some(existing) = maybe_existing.as_ref() else {
+ return self.set_provisional_unchecked_ok(provisional);
+ };
+ match existing {
+ ProvisionalSecurifiedConfig::FactorInstancesDerived { value: _ } => {
+ if let Some(new) = provisional.as_ref() {
+ if new.is_transaction_queued() {
+ // We allow `FactorInstancesDerived` -> `TransactionQueued` transition ofc...
+ return self.set_provisional_unchecked_ok(provisional);
+ }
+ }
+ // We have already consumed FactorInstances from the FactorInstancesCache.
+ // We currently do not have a way of putting these back. We don't want to
+ // create gaps if we can. Wallet Features might change, to REQUIRE us to
+ // allow this, if so we can change this - allowing transition from
+ // `FactorInstancesDerived` to Non-TransactionQueued state.
+ Err(
+ CommonError::SecurityEntityControlCannotChangeProvisionalAlreadyDerivedInstances,
+ )},
+ ProvisionalSecurifiedConfig::TransactionQueued { value: _ } => {
+ Err(CommonError::SecurityEntityControlCannotChangeProvisionalAlreadyHasQueuedTransaction)
+ }
+ _ => self.set_provisional_unchecked_ok(provisional),
+ }
+ }
+ /// Call this once you have cancelled the queued transaction from the transaction queue,
+ /// this method will change the provisional state back to `Some(ProvisionalSecurifiedConfig::FactorInstancesDerived(_))`
+ fn cancel_queued_transaction(&mut self) -> Result<()> {
+ if let Some(ProvisionalSecurifiedConfig::TransactionQueued { value }) =
+ self.get_provisional().as_ref()
+ {
+ self.set_provisional_unchecked_ok(
+ ProvisionalSecurifiedConfig::FactorInstancesDerived {
+ value: value.factor_instances.clone(),
+ },
+ )
+ } else {
+ Err(CommonError::SecurityEntityControlHasNoProvisionallyQueuedTransaction)
+ }
+ }
+
+ fn set_provisional_unchecked_ok(
+ &mut self,
+ provisional_securified_config: impl Into<
+ Option,
+ >,
+ ) -> Result<()> {
+ self.set_provisional_unchecked(provisional_securified_config);
+ Ok(())
+ }
+
+ fn set_provisional_unchecked(
+ &mut self,
+ provisional_securified_config: impl Into<
+ Option,
+ >,
+ );
}
impl HasFactorInstances for SecuredEntityControl {
- fn unique_factor_instances(&self) -> IndexSet {
+ fn unique_all_factor_instances(&self) -> IndexSet {
+ self.security_structure.unique_all_factor_instances()
+ }
+
+ fn unique_tx_signing_factor_instances(&self) -> IndexSet {
self.security_structure
.matrix_of_factors
- .unique_factor_instances()
+ .unique_tx_signing_factor_instances()
}
}
@@ -55,8 +154,17 @@ impl SecuredEntityControl {
veci,
access_controller_address,
security_structure,
+ provisional_securified_config: None,
})
}
+
+ pub fn authentication_signing_factor_instance(
+ &self,
+ ) -> HierarchicalDeterministicFactorInstance {
+ self.security_structure
+ .authentication_signing_factor_instance
+ .clone()
+ }
}
impl SecuredEntityControl {
@@ -67,13 +175,17 @@ impl SecuredEntityControl {
impl HasSampleValues for SecuredEntityControl {
fn sample() -> Self {
- Self::new(
- None,
+ let mut sample = Self::new(
+ HierarchicalDeterministicFactorInstance::sample(),
AccessControllerAddress::sample(),
SecurityStructureOfFactorInstances::sample(),
)
- .unwrap()
+ .unwrap();
+ sample.provisional_securified_config =
+ Some(ProvisionalSecurifiedConfig::sample_other());
+ sample
}
+
fn sample_other() -> Self {
Self::new(HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(0), AccessControllerAddress::sample_other(), SecurityStructureOfFactorInstances::sample_other()).unwrap()
}
@@ -92,6 +204,17 @@ mod tests {
assert_eq!(SUT::sample_other(), SUT::sample_other());
}
+ #[test]
+ fn unique_all_factor_instances_includes_rola() {
+ let sut = SUT::sample();
+ assert!(sut
+ .unique_all_factor_instances()
+ .into_iter()
+ .map(|f| f.try_as_hd_factor_instances().unwrap())
+ .any(|f| f.derivation_path().get_key_kind()
+ == CAP26KeyKind::AuthenticationSigning));
+ }
+
#[test]
fn inequality() {
assert_ne!(SUT::sample(), SUT::sample_other());
@@ -109,4 +232,343 @@ mod tests {
SecurityStructureOfFactorInstances::sample(),
);
}
+
+ #[test]
+ fn json_roundtrip_sample() {
+ let model = SUT::sample();
+ assert_eq_after_json_roundtrip(
+ &model,
+ r#"
+ {
+ "veci": {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "c05f9fa53f203a01cbe43e89086cae29f6c7cdd5a435daa9e52b69e656739b36"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0H"
+ }
+ }
+ }
+ }
+ },
+ "accessControllerAddress": "accesscontroller_rdx1c0duj4lq0dc3cpl8qd420fpn5eckh8ljeysvjm894lyl5ja5yq6y5a",
+ "securityStructure": {
+ "securityStructureId": "ffffffff-ffff-ffff-ffff-ffffffffffff",
+ "matrixOfFactors": {
+ "primaryRole": {
+ "threshold": 2,
+ "thresholdFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ },
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "overrideFactors": []
+ },
+ "recoveryRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ },
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "confirmationRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "password",
+ "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "4af49eb56b1af579aaf03f1760ec526f56e2297651f7a067f4b362f685417a81"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "numberOfDaysUntilAutoConfirm": 14
+ },
+ "authenticationSigningFactorInstance": {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "136b3a73595315517f921767bc49ae3ba43fc25d2e34e51fbff434a329176ee8"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1678H/0S"
+ }
+ }
+ }
+ }
+ }
+ },
+ "provisionalSecurifiedConfig": {
+ "discriminator": "factorInstancesDerived",
+ "value": {
+ "securityStructureId": "dededede-dede-dede-dede-dededededede",
+ "matrixOfFactors": {
+ "primaryRole": {
+ "threshold": 1,
+ "thresholdFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "a40a1850ade79f5b24956b4abdb94624ba8189f68ad39fd2bb92ecdc2cbe17d2"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "overrideFactors": []
+ },
+ "recoveryRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "6f7ac7d9031e321d1762431941b672f164ebb5a6dd2ded9b0c8da2b278143c74"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "confirmationRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "52ef052a0642a94279b296d6b3b17dedc035a7ae37b76c1d60f11f2725100077"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "e867cd64b70cccad642f47ee4acff014b982870cf5218fbd56da79b0eb6e9fba"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "numberOfDaysUntilAutoConfirm": 14
+ },
+ "authenticationSigningFactorInstance": {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "d2343d84e7970224ad4f605782f78b096b750f03990c927492ba5308258c689a"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1678H/0S"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ "#,
+ );
+ }
}
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 29bc0c978..7c1fde0a2 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
@@ -2,12 +2,80 @@ use crate::prelude::*;
pub type MatrixOfFactorInstances = AbstractMatrixBuilt;
+impl MatrixOfFactorInstances {
+ pub fn timed_recovery_delay_in_minutes(&self) -> u32 {
+ let timed_recovery_in_days =
+ self.number_of_days_until_auto_confirm as u32;
+ MINUTES_PER_DAY * timed_recovery_in_days
+ }
+}
pub trait HasFactorInstances {
- fn unique_factor_instances(&self) -> IndexSet;
+ fn assert_has_entity_kind(
+ &self,
+ entity_kind_of_entity: CAP26EntityKind,
+ ) -> Result<()> {
+ let entity_kind_of_factor_instances =
+ self.entity_kind_of_all_factors()?;
+
+ if entity_kind_of_entity != entity_kind_of_factor_instances {
+ return Err(CommonError::SecurityStructureOfFactorInstancesEntityDiscrepancyInEntityKind { entity_kind_of_entity, entity_kind_of_factor_instances });
+ }
+
+ Ok(())
+ }
+
+ fn entity_kind_of_all_factors(&self) -> Result {
+ let index_agnostic_path =
+ self.index_agnostic_path_of_all_tx_signing_factor_instances()?;
+ Ok(index_agnostic_path.entity_kind)
+ }
+
+ fn index_agnostic_path_of_all_tx_signing_factor_instances(
+ &self,
+ ) -> Result {
+ let factors = self
+ .unique_tx_signing_factor_instances()
+ .into_iter()
+ .filter_map(|f| f.try_as_hd_factor_instances().ok())
+ .collect_vec();
+
+ if factors.is_empty() {
+ return Err(CommonError::NoTransactionSigningFactorInstance);
+ }
+
+ let index_agnostic_path =
+ factors.first().unwrap().derivation_path().agnostic();
+
+ if factors
+ .iter()
+ .any(|f| f.get_entity_kind() != index_agnostic_path.entity_kind)
+ {
+ return Err(CommonError::WrongEntityKindOfInFactorInstancesPath);
+ }
+
+ if factors
+ .iter()
+ .any(|f| f.get_key_kind() != CAP26KeyKind::TransactionSigning)
+ {
+ return Err(
+ CommonError::WrongKeyKindOfTransactionSigningFactorInstance,
+ );
+ }
+
+ Ok(index_agnostic_path)
+ }
+
+ fn unique_tx_signing_factor_instances(&self) -> IndexSet;
+
+ /// Override this method for types which has an authentication signing factor
+ /// instance, e.g. `SecurityStructureOfFactorInstances`.
+ fn unique_all_factor_instances(&self) -> IndexSet {
+ self.unique_tx_signing_factor_instances()
+ }
/// Returns whether the entity is linked to the given factor source.
fn is_linked_to_factor_source(&self, factor_source: FactorSource) -> bool {
- self.unique_factor_instances().iter().any(|factor| {
+ self.unique_all_factor_instances().iter().any(|factor| {
factor.factor_source_id == factor_source.factor_source_id()
})
}
@@ -26,7 +94,7 @@ impl HasFactorSourceKindObjectSafe for FactorSourceID {
}
impl HasFactorInstances for MatrixOfFactorInstances {
- fn unique_factor_instances(&self) -> IndexSet {
+ fn unique_tx_signing_factor_instances(&self) -> IndexSet {
let mut set = IndexSet::new();
set.extend(self.primary_role.all_factors().into_iter().cloned());
set.extend(self.recovery_role.all_factors().into_iter().cloned());
@@ -105,12 +173,17 @@ impl MnemonicWithPassphrase {
impl MatrixOfFactorInstances {
fn sample_from_matrix_of_sources(
matrix_of_sources: MatrixOfFactorSources,
+ entity_kind: CAP26EntityKind,
) -> Self {
let mut consuming_instances =
MnemonicWithPassphrase::derive_instances_for_factor_sources(
NetworkID::Mainnet,
1,
- [DerivationPreset::AccountMfa],
+ [if entity_kind == CAP26EntityKind::Account {
+ DerivationPreset::AccountMfa
+ } else {
+ DerivationPreset::IdentityMfa
+ }],
matrix_of_sources.all_factors().into_iter().cloned(),
);
@@ -123,13 +196,19 @@ impl MatrixOfFactorInstances {
}
impl HasSampleValues for MatrixOfFactorInstances {
+ /// Account
fn sample() -> Self {
- Self::sample_from_matrix_of_sources(MatrixOfFactorSources::sample())
+ Self::sample_from_matrix_of_sources(
+ MatrixOfFactorSources::sample(),
+ CAP26EntityKind::Account,
+ )
}
+ /// Persona
fn sample_other() -> Self {
Self::sample_from_matrix_of_sources(
MatrixOfFactorSources::sample_other(),
+ CAP26EntityKind::Identity,
)
}
}
@@ -221,12 +300,21 @@ mod tests {
assert_eq!(SUT::sample_other(), SUT::sample_other());
}
+ #[test]
+ fn timed_recovery_delay_in_minutes() {
+ let sut = SUT::sample();
+ assert_eq!(
+ sut.timed_recovery_delay_in_minutes(),
+ SUT::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM as u32 * 24 * 60
+ );
+ }
+
#[test]
fn inequality() {
assert_ne!(SUT::sample(), SUT::sample_other());
assert_ne!(
- SUT::sample().unique_factor_instances(),
- SUT::sample_other().unique_factor_instances()
+ SUT::sample().unique_tx_signing_factor_instances(),
+ SUT::sample_other().unique_tx_signing_factor_instances()
);
}
@@ -241,6 +329,101 @@ mod tests {
));
}
+ #[test]
+ fn empty_is_err() {
+ let invalid = unsafe {
+ SUT::unbuilt_with_roles_and_days(
+ PrimaryRoleWithFactorInstances::unbuilt_with_factors(0, [], []),
+ RecoveryRoleWithFactorInstances::unbuilt_with_factors(
+ 0,
+ [],
+ [],
+ ),
+ ConfirmationRoleWithFactorInstances::unbuilt_with_factors(
+ 0,
+ [],
+ [],
+ ),
+ 1,
+ )
+ };
+ let res =
+ invalid.index_agnostic_path_of_all_tx_signing_factor_instances();
+ assert!(matches!(
+ res,
+ Err(CommonError::NoTransactionSigningFactorInstance)
+ ));
+ }
+
+ #[test]
+ fn wrong_entity_kind() {
+ let invalid = unsafe {
+ SUT::unbuilt_with_roles_and_days(
+ PrimaryRoleWithFactorInstances::unbuilt_with_factors(0, [
+ HierarchicalDeterministicFactorInstance::sample_mainnet_entity_device_factor_fs_0_securified_at_index(
+ CAP26EntityKind::Account,
+ 0,
+ ).into(), HierarchicalDeterministicFactorInstance::sample_mainnet_entity_device_factor_fs_0_securified_at_index(
+ CAP26EntityKind::Identity, // <--- Wrong entity kind
+ 1,
+ ).into()], []),
+ RecoveryRoleWithFactorInstances::unbuilt_with_factors(
+ 0,
+ [],
+ [],
+ ),
+ ConfirmationRoleWithFactorInstances::unbuilt_with_factors(
+ 0,
+ [],
+ [],
+ ),
+ 1,
+ )
+ };
+ let res =
+ invalid.index_agnostic_path_of_all_tx_signing_factor_instances();
+ assert!(matches!(
+ res,
+ Err(CommonError::WrongEntityKindOfInFactorInstancesPath)
+ ));
+ }
+
+ #[test]
+ fn wrong_key_kind() {
+ let invalid = unsafe {
+ SUT::unbuilt_with_roles_and_days(
+ PrimaryRoleWithFactorInstances::unbuilt_with_factors(0, [
+ HierarchicalDeterministicFactorInstance::sample_mainnet_entity_device_factor_fs_0_securified_at_index(
+ CAP26EntityKind::Account,
+ 0,
+ ).into(),
+ HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(
+ NetworkID::Mainnet,
+ CAP26KeyKind::AuthenticationSigning, // <-- Wrong key kind
+ CAP26EntityKind::Account,
+ SecurifiedU30::ZERO
+ ).into()], []),
+ RecoveryRoleWithFactorInstances::unbuilt_with_factors(
+ 0,
+ [],
+ [],
+ ),
+ ConfirmationRoleWithFactorInstances::unbuilt_with_factors(
+ 0,
+ [],
+ [],
+ ),
+ 1,
+ )
+ };
+ let res =
+ invalid.index_agnostic_path_of_all_tx_signing_factor_instances();
+ assert!(matches!(
+ res,
+ Err(CommonError::WrongKeyKindOfTransactionSigningFactorInstance)
+ ));
+ }
+
#[test]
fn err_if_empty_instance_found_for_factor_source() {
assert!(matches!(
diff --git a/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/mod.rs b/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/mod.rs
index bb3e03464..5624c37f6 100644
--- a/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/mod.rs
+++ b/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/mod.rs
@@ -2,10 +2,12 @@ mod confirmation_role_with_factor_instances;
mod general_role_with_hierarchical_deterministic_factor_instances;
mod primary_role_with_factor_instances;
mod recovery_role_with_factor_instances;
+mod role_into_scrypto_access_rule;
mod role_with_factor_instances;
pub use confirmation_role_with_factor_instances::*;
pub use general_role_with_hierarchical_deterministic_factor_instances::*;
pub use primary_role_with_factor_instances::*;
pub use recovery_role_with_factor_instances::*;
+pub use role_into_scrypto_access_rule::*;
pub use role_with_factor_instances::*;
diff --git a/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/role_into_scrypto_access_rule.rs b/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/role_into_scrypto_access_rule.rs
new file mode 100644
index 000000000..99c0e1330
--- /dev/null
+++ b/crates/sargon/src/profile/mfa/security_structures/roles/factor_levels/factor_instance_level/role_into_scrypto_access_rule.rs
@@ -0,0 +1,54 @@
+use crate::prelude::*;
+
+impl From> for ScryptoAccessRule {
+ fn from(value: RoleWithFactorInstances) -> Self {
+ let from_factors =
+ |factors: &Vec| -> Vec {
+ factors
+ .iter()
+ .map(|instance| instance.badge.clone())
+ .map(ScryptoResourceOrNonFungible::from)
+ .collect()
+ };
+ ScryptoAccessRule::Protected(ScryptoCompositeRequirement::AnyOf(vec![
+ ScryptoCompositeRequirement::BasicRequirement(
+ ScryptoBasicRequirement::CountOf(
+ value.get_threshold(),
+ from_factors(value.get_threshold_factors()),
+ ),
+ ),
+ ScryptoCompositeRequirement::BasicRequirement(
+ ScryptoBasicRequirement::AnyOf(from_factors(
+ value.get_override_factors(),
+ )),
+ ),
+ ]))
+ }
+}
+
+impl From for ScryptoRuleSet {
+ fn from(
+ MatrixOfFactorInstances {
+ primary_role,
+ recovery_role,
+ confirmation_role,
+ ..
+ }: MatrixOfFactorInstances,
+ ) -> Self {
+ Self {
+ primary_role: primary_role.into(),
+ recovery_role: recovery_role.into(),
+ confirmation_role: confirmation_role.into(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn number_of_minutes_per_day() {
+ assert_eq!(MINUTES_PER_DAY, 1440);
+ }
+}
diff --git a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/mod.rs b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/mod.rs
index 9e2b04e3b..7aa81f664 100644
--- a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/mod.rs
+++ b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/mod.rs
@@ -1,7 +1,9 @@
mod abstract_security_structure_of_factors;
+mod security_structure_of_factor_instances;
mod security_structure_of_factor_source_ids;
mod security_structure_of_factor_sources;
pub(crate) use abstract_security_structure_of_factors::*;
+pub use security_structure_of_factor_instances::*;
pub use security_structure_of_factor_source_ids::*;
pub use security_structure_of_factor_sources::*;
diff --git a/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_instances.rs b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_instances.rs
new file mode 100644
index 000000000..62794519a
--- /dev/null
+++ b/crates/sargon/src/profile/mfa/security_structures/security_structure_of_factors/security_structure_of_factor_instances.rs
@@ -0,0 +1,507 @@
+use indexmap::IndexSet;
+
+use crate::prelude::*;
+
+/// A structure of factors to use for certain roles, Primary, Recovery and
+/// Confirmation, as well as an authentication signing factor instance which is
+/// used for Rola.
+///
+/// This structure is identified by the `security_structure_id` which is the ID
+/// of the `SecurityStructureOfFactorSourceIDs` which was used to derive the
+/// instances in this structure.
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+#[serde(rename_all = "camelCase")]
+pub struct SecurityStructureOfFactorInstances {
+ /// The ID of the `SecurityStructureOfFactorSourceIDs` in
+ /// `profile.app_preferences.security.security_structures_of_factor_source_ids`
+ /// which was used to derive the factor instances in this structure. Or rather:
+ /// The id of `SecurityStructureOfFactorSources`.
+ pub security_structure_id: SecurityStructureID,
+
+ /// The structure of factors to use for certain roles, Primary, Recovery
+ /// and Confirmation role.
+ pub matrix_of_factors: MatrixOfFactorInstances,
+
+ /// The authentication signing factor instance which is used to sign
+ /// proof of ownership - aka "True Rola Key". User can select which FactorSource
+ /// to use during Shield Building, but typically most users will use the
+ /// DeviceFactorSource which is default. DerivationPath is in securified
+ /// KeySpace of course.
+ ///
+ /// Non-optional since we can replace it with a new one for entities
+ /// we have recovered during Onboarding Account Recovery Scan for securified
+ /// entities
+ pub authentication_signing_factor_instance:
+ HierarchicalDeterministicFactorInstance,
+}
+
+impl HasFactorInstances for SecurityStructureOfFactorInstances {
+ fn unique_tx_signing_factor_instances(&self) -> IndexSet {
+ self.matrix_of_factors.unique_tx_signing_factor_instances()
+ }
+
+ fn unique_all_factor_instances(&self) -> IndexSet {
+ let mut instances = self.unique_tx_signing_factor_instances();
+ instances
+ .insert(self.authentication_signing_factor_instance.clone().into());
+ instances
+ }
+}
+
+impl SecurityStructureOfFactorInstances {
+ pub fn new(
+ security_structure_id: SecurityStructureID,
+ matrix_of_factors: MatrixOfFactorInstances,
+ authentication_signing: HierarchicalDeterministicFactorInstance,
+ ) -> Result {
+ let index_agnostic_path = matrix_of_factors
+ .index_agnostic_path_of_all_tx_signing_factor_instances()?;
+
+ if authentication_signing.get_key_kind()
+ != CAP26KeyKind::AuthenticationSigning
+ {
+ return Err(
+ CommonError::WrongKeyKindOfAuthenticationSigningFactorInstance,
+ );
+ }
+
+ if authentication_signing.get_entity_kind()
+ != index_agnostic_path.entity_kind
+ {
+ return Err(CommonError::WrongEntityKindOfInFactorInstancesPath);
+ }
+
+ if !authentication_signing.is_securified() {
+ return Err(
+ CommonError::AuthenticationSigningFactorInstanceNotSecurified,
+ );
+ }
+
+ Ok(Self {
+ security_structure_id,
+ matrix_of_factors,
+ authentication_signing_factor_instance: authentication_signing,
+ })
+ }
+}
+
+impl SecurityStructureOfFactorInstances {
+ pub fn timed_recovery_delay_in_minutes(&self) -> u32 {
+ self.matrix_of_factors.timed_recovery_delay_in_minutes()
+ }
+}
+
+impl Identifiable for SecurityStructureOfFactorInstances {
+ type ID = ::ID;
+
+ fn id(&self) -> Self::ID {
+ self.security_structure_id
+ }
+}
+
+impl HasSampleValues for SecurityStructureOfFactorInstances {
+ /// Account
+ fn sample() -> Self {
+ Self::new(
+ SecurityStructureID::sample(),
+ MatrixOfFactorInstances::sample(),
+ HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(NetworkID::Mainnet, CAP26KeyKind::AuthenticationSigning, CAP26EntityKind::Account, Hardened::Securified(SecurifiedU30::ZERO))
+ )
+ .unwrap()
+ }
+
+ /// Persona
+ fn sample_other() -> Self {
+ Self::new(
+ SecurityStructureID::sample_other(),
+ MatrixOfFactorInstances::sample_other(),
+ HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(NetworkID::Mainnet, CAP26KeyKind::AuthenticationSigning, CAP26EntityKind::Identity, Hardened::Securified(SecurifiedU30::ZERO))
+ )
+ .unwrap()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[allow(clippy::upper_case_acronyms)]
+ type SUT = SecurityStructureOfFactorInstances;
+
+ #[test]
+ fn equality() {
+ assert_eq!(SUT::sample(), SUT::sample());
+ assert_eq!(SUT::sample_other(), SUT::sample_other());
+ }
+
+ #[test]
+ fn unique_all_factor_instances() {
+ let sut = SUT::sample();
+ assert!(sut
+ .unique_all_factor_instances()
+ .into_iter()
+ .map(|f| f.try_as_hd_factor_instances().unwrap())
+ .any(|f| f.derivation_path().get_key_kind()
+ == CAP26KeyKind::AuthenticationSigning));
+ }
+
+ #[test]
+ fn inequality() {
+ assert_ne!(SUT::sample(), SUT::sample_other());
+ }
+
+ #[test]
+ fn timed_recovery_delay_in_minutes() {
+ let sut = SUT::sample();
+ assert_eq!(sut.timed_recovery_delay_in_minutes(), 20160);
+ }
+
+ #[test]
+ fn wrong_entity_kind_of_auth_signing_factor() {
+ let res = SUT::new(SecurityStructureID::sample(), MatrixOfFactorInstances::sample(), HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(NetworkID::Mainnet, CAP26KeyKind::AuthenticationSigning, CAP26EntityKind::Identity, Hardened::Securified(SecurifiedU30::ZERO)));
+ assert!(matches!(
+ res,
+ Err(CommonError::WrongEntityKindOfInFactorInstancesPath)
+ ));
+ }
+
+ #[test]
+ fn id() {
+ assert_eq!(SUT::sample().id(), SUT::sample().security_structure_id);
+ }
+
+ #[test]
+ fn wrong_key_kind_of_auth_signing_factor() {
+ let res = SUT::new(SecurityStructureID::sample(), MatrixOfFactorInstances::sample(), HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, CAP26EntityKind::Account, Hardened::Securified(SecurifiedU30::ZERO)));
+ assert!(matches!(
+ res,
+ Err(CommonError::WrongKeyKindOfAuthenticationSigningFactorInstance)
+ ));
+ }
+
+ #[test]
+ fn auth_signing_factor_not_signing() {
+ let res = SUT::new(SecurityStructureID::sample(), MatrixOfFactorInstances::sample(), HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(NetworkID::Mainnet, CAP26KeyKind::AuthenticationSigning, CAP26EntityKind::Account, Hardened::Unsecurified(UnsecurifiedHardened::ZERO)));
+ assert!(matches!(
+ res,
+ Err(CommonError::AuthenticationSigningFactorInstanceNotSecurified)
+ ));
+ }
+
+ #[test]
+ fn json_roundtrip_sample_other() {
+ let sut = SUT::sample_other();
+ assert_eq_after_json_roundtrip(
+ &sut,
+ r#"
+ {
+ "securityStructureId": "dededede-dede-dede-dede-dededededede",
+ "matrixOfFactors": {
+ "primaryRole": {
+ "threshold": 1,
+ "thresholdFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "a40a1850ade79f5b24956b4abdb94624ba8189f68ad39fd2bb92ecdc2cbe17d2"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "overrideFactors": []
+ },
+ "recoveryRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "6f7ac7d9031e321d1762431941b672f164ebb5a6dd2ded9b0c8da2b278143c74"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "confirmationRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "52ef052a0642a94279b296d6b3b17dedc035a7ae37b76c1d60f11f2725100077"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "e867cd64b70cccad642f47ee4acff014b982870cf5218fbd56da79b0eb6e9fba"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "numberOfDaysUntilAutoConfirm": 14
+ },
+ "authenticationSigningFactorInstance": {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "d2343d84e7970224ad4f605782f78b096b750f03990c927492ba5308258c689a"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1678H/0S"
+ }
+ }
+ }
+ }
+ }
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn json_roundtrip() {
+ let sut = SUT::sample();
+ assert_eq_after_json_roundtrip(
+ &sut,
+ r#"
+ {
+ "securityStructureId": "ffffffff-ffff-ffff-ffff-ffffffffffff",
+ "matrixOfFactors": {
+ "primaryRole": {
+ "threshold": 2,
+ "thresholdFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ },
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "overrideFactors": []
+ },
+ "recoveryRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ },
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "confirmationRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "password",
+ "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "4af49eb56b1af579aaf03f1760ec526f56e2297651f7a067f4b362f685417a81"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "numberOfDaysUntilAutoConfirm": 14
+ },
+ "authenticationSigningFactorInstance": {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "136b3a73595315517f921767bc49ae3ba43fc25d2e34e51fbff434a329176ee8"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1678H/0S"
+ }
+ }
+ }
+ }
+ }
+ }
+ "#,
+ );
+ }
+}
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 23096b5c9..ec9cd65eb 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
@@ -6,56 +6,6 @@ pub type SecurityStructureOfFactorSourceIds =
pub type SecurityStructureOfFactorSourceIDs =
SecurityStructureOfFactorSourceIds;
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
-#[serde(rename_all = "camelCase")]
-pub struct SecurityStructureOfFactorInstances {
- /// The ID of the `SecurityStructureOfFactorSourceIDs` in
- /// `profile.app_preferences.security.security_structures_of_factor_source_ids`
- /// which was used to derive the factor instances in this structure. Or rather:
- /// The id of `SecurityStructureOfFactorSources`.
- pub security_structure_id: SecurityStructureID,
-
- /// The structure of factors to use for certain roles, Primary, Recovery
- /// and Confirmation role.
- pub matrix_of_factors: MatrixOfFactorInstances,
-}
-
-impl SecurityStructureOfFactorInstances {
- pub fn new(
- security_structure_id: SecurityStructureID,
- matrix_of_factors: MatrixOfFactorInstances,
- ) -> Self {
- Self {
- security_structure_id,
- matrix_of_factors,
- }
- }
-}
-
-impl Identifiable for SecurityStructureOfFactorInstances {
- type ID = ::ID;
-
- fn id(&self) -> Self::ID {
- self.security_structure_id
- }
-}
-
-impl HasSampleValues for SecurityStructureOfFactorInstances {
- fn sample() -> Self {
- Self {
- security_structure_id: SecurityStructureID::sample(),
- matrix_of_factors: MatrixOfFactorInstances::sample(),
- }
- }
-
- fn sample_other() -> Self {
- Self {
- security_structure_id: SecurityStructureID::sample_other(),
- matrix_of_factors: MatrixOfFactorInstances::sample_other(),
- }
- }
-}
-
impl HasSampleValues for SecurityStructureOfFactorSourceIds {
fn sample() -> Self {
let metadata = SecurityStructureMetadata::sample();
diff --git a/crates/sargon/src/profile/v100/entity/account/account.rs b/crates/sargon/src/profile/v100/entity/account/account.rs
index b30659157..09b268a67 100644
--- a/crates/sargon/src/profile/v100/entity/account/account.rs
+++ b/crates/sargon/src/profile/v100/entity/account/account.rs
@@ -84,6 +84,11 @@ impl HasSecurityState for Account {
}
}
+impl IsSecurityStateAware for Account {
+ fn is_securified(&self) -> bool {
+ self.security_state().is_securified()
+ }
+}
impl IsBaseEntity for Account {
type Address = AccountAddress;
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 26fc26b05..389977716 100644
--- a/crates/sargon/src/profile/v100/entity/has_security_state.rs
+++ b/crates/sargon/src/profile/v100/entity/has_security_state.rs
@@ -1,7 +1,8 @@
use crate::prelude::*;
-pub trait HasSecurityState: HasFactorInstances {
+pub trait HasSecurityState: HasFactorInstances + IsSecurityStateAware {
fn security_state(&self) -> EntitySecurityState;
+
fn try_get_secured_control(&self) -> Result {
self.security_state()
.as_securified()
@@ -18,14 +19,7 @@ pub trait HasSecurityState: HasFactorInstances {
}
impl HasFactorInstances for T {
- fn unique_factor_instances(&self) -> IndexSet {
- match self.security_state() {
- EntitySecurityState::Securified { value } => {
- value.unique_factor_instances()
- }
- EntitySecurityState::Unsecured { value } => {
- value.unique_factor_instances()
- }
- }
+ fn unique_tx_signing_factor_instances(&self) -> IndexSet {
+ self.security_state().unique_tx_signing_factor_instances()
}
}
diff --git a/crates/sargon/src/profile/v100/entity/persona/persona.rs b/crates/sargon/src/profile/v100/entity/persona/persona.rs
index 000c40ac1..d59181548 100644
--- a/crates/sargon/src/profile/v100/entity/persona/persona.rs
+++ b/crates/sargon/src/profile/v100/entity/persona/persona.rs
@@ -66,7 +66,11 @@ impl HasEntityKind for Persona {
CAP26EntityKind::Identity
}
}
-
+impl IsSecurityStateAware for Persona {
+ fn is_securified(&self) -> bool {
+ self.security_state().is_securified()
+ }
+}
impl HasSecurityState for Persona {
fn security_state(&self) -> EntitySecurityState {
self.security_state.clone()
diff --git a/crates/sargon/src/profile/v100/entity_security_state/entity_security_state.rs b/crates/sargon/src/profile/v100/entity_security_state/entity_security_state.rs
index dc67b1ccd..1bab332e7 100644
--- a/crates/sargon/src/profile/v100/entity_security_state/entity_security_state.rs
+++ b/crates/sargon/src/profile/v100/entity_security_state/entity_security_state.rs
@@ -7,19 +7,48 @@ use crate::prelude::*;
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, EnumAsInner,
)]
#[serde(untagged, remote = "Self")]
+#[allow(clippy::large_enum_variant)]
pub enum EntitySecurityState {
/// The account is controlled by a single factor (private key)
Unsecured {
+ /// The current state of the unsecured entity
#[serde(rename = "unsecuredEntityControl")]
value: UnsecuredEntityControl,
},
+
/// The account is controlled by multi-factor
Securified {
+ /// The current state of the securified entity
#[serde(rename = "securedEntityControl")]
value: SecuredEntityControl,
},
}
+impl HasProvisionalSecurifiedConfig for EntitySecurityState {
+ fn get_provisional(&self) -> Option {
+ match self {
+ Self::Unsecured { value } => value.get_provisional(),
+ Self::Securified { value } => value.get_provisional(),
+ }
+ }
+
+ fn set_provisional_unchecked(
+ &mut self,
+ provisional_securified_config: impl Into<
+ Option,
+ >,
+ ) {
+ match self {
+ Self::Unsecured { value } => {
+ value.set_provisional_unchecked(provisional_securified_config)
+ }
+ Self::Securified { value } => {
+ value.set_provisional_unchecked(provisional_securified_config)
+ }
+ }
+ }
+}
+
impl<'de> Deserialize<'de> for EntitySecurityState {
#[cfg(not(tarpaulin_include))] // false negative
fn deserialize>(
@@ -86,13 +115,23 @@ impl HasSampleValues for EntitySecurityState {
}
impl HasFactorInstances for EntitySecurityState {
- fn unique_factor_instances(&self) -> IndexSet {
+ fn unique_tx_signing_factor_instances(&self) -> IndexSet {
+ match self {
+ EntitySecurityState::Unsecured { value } => {
+ value.unique_tx_signing_factor_instances()
+ }
+ EntitySecurityState::Securified { value } => {
+ value.unique_tx_signing_factor_instances()
+ }
+ }
+ }
+ fn unique_all_factor_instances(&self) -> IndexSet {
match self {
EntitySecurityState::Unsecured { value } => {
- value.unique_factor_instances()
+ value.unique_all_factor_instances()
}
EntitySecurityState::Securified { value } => {
- value.unique_factor_instances()
+ value.unique_all_factor_instances()
}
}
}
@@ -116,6 +155,170 @@ mod tests {
assert_ne!(SUT::sample(), SUT::sample_other());
}
+ fn set_provisional_is_err_when_provisional_is_tx_queued(sut: SUT) {
+ let mut sut = sut;
+ sut.set_provisional(ProvisionalSecurifiedConfig::TransactionQueued {
+ value: ProvisionalSecurifiedTransactionQueued::sample(),
+ })
+ .unwrap();
+ let mut clone = sut.clone();
+ let mut test = |x: ProvisionalSecurifiedConfig| {
+ assert!(clone.set_provisional(x).is_err());
+ };
+
+ test(ProvisionalSecurifiedConfig::ShieldSelected {
+ value: SecurityStructureID::sample(),
+ });
+ test(ProvisionalSecurifiedConfig::FactorInstancesDerived {
+ value: SecurityStructureOfFactorInstances::sample(),
+ });
+ test(ProvisionalSecurifiedConfig::TransactionQueued {
+ value: ProvisionalSecurifiedTransactionQueued::sample_other(),
+ });
+
+ assert_eq!(clone, sut); // unchanged
+ }
+
+ fn can_change_shield(sut: SUT) {
+ let mut sut = sut;
+ let id1 = ProvisionalSecurifiedConfig::ShieldSelected {
+ value: SecurityStructureID::sample(),
+ };
+ let id2 = ProvisionalSecurifiedConfig::ShieldSelected {
+ value: SecurityStructureID::sample_other(),
+ };
+ sut.set_provisional(id1.clone()).unwrap();
+ assert_eq!(sut.get_provisional().unwrap(), id1);
+ assert!(sut.set_provisional(id2.clone()).is_ok());
+ assert_eq!(sut.get_provisional().unwrap(), id2);
+ }
+
+ #[test]
+ fn can_change_shield_id_unsecured() {
+ can_change_shield(SUT::from(UnsecuredEntityControl::sample()));
+ }
+
+ #[test]
+ fn can_change_shield_id_secured() {
+ can_change_shield(SUT::from(SecuredEntityControl::sample_other()));
+ }
+
+ fn set_provisional_to_non_tx_queued_is_err_when_provisional_is_instances_derived(
+ sut: SUT,
+ ) {
+ let mut sut = sut;
+ sut.set_provisional(
+ ProvisionalSecurifiedConfig::FactorInstancesDerived {
+ value: SecurityStructureOfFactorInstances::sample(),
+ },
+ )
+ .unwrap();
+ let mut clone = sut.clone();
+ let mut test = |x: Option| {
+ assert!(clone.set_provisional(x).is_err());
+ };
+ test(None);
+ test(Some(ProvisionalSecurifiedConfig::ShieldSelected {
+ value: SecurityStructureID::sample(),
+ }));
+ test(Some(ProvisionalSecurifiedConfig::FactorInstancesDerived {
+ value: SecurityStructureOfFactorInstances::sample(),
+ }));
+
+ assert_eq!(clone, sut); // unchanged
+ }
+
+ #[test]
+ fn set_provisional_to_non_tx_queued_is_err_when_provisional_is_instances_derived_unsecure(
+ ) {
+ set_provisional_to_non_tx_queued_is_err_when_provisional_is_instances_derived(SUT::from(UnsecuredEntityControl::sample()));
+ }
+
+ #[test]
+ fn set_provisional_to_non_tx_queued_is_err_when_provisional_is_instances_derived_secure(
+ ) {
+ set_provisional_to_non_tx_queued_is_err_when_provisional_is_instances_derived(SUT::from(SecuredEntityControl::sample_other()));
+ }
+
+ #[test]
+ fn set_provisional_is_err_when_provisional_is_tx_queued_unsecured() {
+ set_provisional_is_err_when_provisional_is_tx_queued(SUT::from(
+ UnsecuredEntityControl::sample(),
+ ));
+ }
+
+ #[test]
+ fn set_provisional_is_err_when_provisional_is_tx_queued_secured() {
+ set_provisional_is_err_when_provisional_is_tx_queued(SUT::from(
+ SecuredEntityControl::sample_other(),
+ ));
+ }
+
+ fn cancel_works(sut: SUT) {
+ let mut sut = sut;
+ let tx_queued = ProvisionalSecurifiedTransactionQueued::sample();
+ sut.set_provisional(ProvisionalSecurifiedConfig::TransactionQueued {
+ value: tx_queued.clone(),
+ })
+ .unwrap();
+ assert!(sut.cancel_queued_transaction().is_ok());
+
+ assert_eq!(
+ sut.get_provisional().unwrap(),
+ ProvisionalSecurifiedConfig::FactorInstancesDerived {
+ value: tx_queued.clone().factor_instances
+ }
+ ); // assert is cancelled
+
+ // can re-queue tx
+ sut.set_provisional(ProvisionalSecurifiedConfig::TransactionQueued {
+ value: tx_queued.clone(),
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn cancel_works_unsecured() {
+ cancel_works(SUT::from(UnsecuredEntityControl::sample()));
+ }
+
+ #[test]
+ fn cancel_works_secured() {
+ cancel_works(SUT::from(SecuredEntityControl::sample_other()));
+ }
+
+ fn cancel_when_no_tx_queued_is_err(val: SUT) {
+ let test = |x: Option| {
+ let mut sut = val.clone();
+ sut.set_provisional(x).unwrap();
+ let clone = sut.clone();
+ assert!(sut.cancel_queued_transaction().is_err());
+ assert_eq!(clone, sut) // unchanged
+ };
+
+ test(None);
+ test(Some(ProvisionalSecurifiedConfig::ShieldSelected {
+ value: SecurityStructureID::sample(),
+ }));
+ test(Some(ProvisionalSecurifiedConfig::FactorInstancesDerived {
+ value: SecurityStructureOfFactorInstances::sample(),
+ }));
+ }
+
+ #[test]
+ fn cancel_when_no_tx_queued_is_err_unsecured() {
+ cancel_when_no_tx_queued_is_err(SUT::from(
+ UnsecuredEntityControl::sample(),
+ ))
+ }
+
+ #[test]
+ fn cancel_when_no_tx_queued_is_err_secured() {
+ cancel_when_no_tx_queued_is_err(SUT::from(
+ SecuredEntityControl::sample_other(),
+ ))
+ }
+
#[test]
fn json_roundtrip_unsecurified() {
let model = SUT::sample();
@@ -164,14 +367,37 @@ mod tests {
let model = EntitySecurityState::Securified {
value: secured_entity_control,
};
-
assert_eq_after_json_roundtrip(
&model,
r#"
- {
+ {
"discriminator": "securified",
"securedEntityControl": {
- "veci": null,
+ "veci": {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "c05f9fa53f203a01cbe43e89086cae29f6c7cdd5a435daa9e52b69e656739b36"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1460H/0H"
+ }
+ }
+ }
+ }
+ },
"accessControllerAddress": "accesscontroller_rdx1c0duj4lq0dc3cpl8qd420fpn5eckh8ljeysvjm894lyl5ja5yq6y5a",
"securityStructure": {
"securityStructureId": "ffffffff-ffff-ffff-ffff-ffffffffffff",
@@ -320,6 +546,158 @@ mod tests {
]
},
"numberOfDaysUntilAutoConfirm": 14
+ },
+ "authenticationSigningFactorInstance": {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "136b3a73595315517f921767bc49ae3ba43fc25d2e34e51fbff434a329176ee8"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/525H/1678H/0S"
+ }
+ }
+ }
+ }
+ }
+ },
+ "provisionalSecurifiedConfig": {
+ "discriminator": "factorInstancesDerived",
+ "value": {
+ "securityStructureId": "dededede-dede-dede-dede-dededededede",
+ "matrixOfFactors": {
+ "primaryRole": {
+ "threshold": 1,
+ "thresholdFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "a40a1850ade79f5b24956b4abdb94624ba8189f68ad39fd2bb92ecdc2cbe17d2"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "overrideFactors": []
+ },
+ "recoveryRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "6f7ac7d9031e321d1762431941b672f164ebb5a6dd2ded9b0c8da2b278143c74"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "confirmationRole": {
+ "threshold": 0,
+ "thresholdFactors": [],
+ "overrideFactors": [
+ {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "ledgerHQHardwareWallet",
+ "body": "52ef052a0642a94279b296d6b3b17dedc035a7ae37b76c1d60f11f2725100077"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "e867cd64b70cccad642f47ee4acff014b982870cf5218fbd56da79b0eb6e9fba"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1460H/0S"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "numberOfDaysUntilAutoConfirm": 14
+ },
+ "authenticationSigningFactorInstance": {
+ "factorSourceID": {
+ "discriminator": "fromHash",
+ "fromHash": {
+ "kind": "device",
+ "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a"
+ }
+ },
+ "badge": {
+ "discriminator": "virtualSource",
+ "virtualSource": {
+ "discriminator": "hierarchicalDeterministicPublicKey",
+ "hierarchicalDeterministicPublicKey": {
+ "publicKey": {
+ "curve": "curve25519",
+ "compressedData": "d2343d84e7970224ad4f605782f78b096b750f03990c927492ba5308258c689a"
+ },
+ "derivationPath": {
+ "scheme": "cap26",
+ "path": "m/44H/1022H/1H/618H/1678H/0S"
+ }
+ }
+ }
+ }
+ }
}
}
}
@@ -329,14 +707,14 @@ mod tests {
}
#[test]
- fn unique_factor_instances() {
+ fn unique_tx_signing_factor_instances() {
let unsecured = UnsecuredEntityControl::sample();
let sut = SUT::Unsecured {
value: unsecured.clone(),
};
assert_eq!(
- sut.unique_factor_instances(),
- unsecured.unique_factor_instances()
+ sut.unique_tx_signing_factor_instances(),
+ unsecured.unique_tx_signing_factor_instances()
);
let secured = SecuredEntityControl::sample();
@@ -344,8 +722,8 @@ mod tests {
value: secured.clone(),
};
assert_eq!(
- sut.unique_factor_instances(),
- secured.unique_factor_instances()
+ sut.unique_tx_signing_factor_instances(),
+ secured.unique_tx_signing_factor_instances()
);
}
}
diff --git a/crates/sargon/src/profile/v100/entity_security_state/mod.rs b/crates/sargon/src/profile/v100/entity_security_state/mod.rs
index a7af15c95..48ba2abb0 100644
--- a/crates/sargon/src/profile/v100/entity_security_state/mod.rs
+++ b/crates/sargon/src/profile/v100/entity_security_state/mod.rs
@@ -1,5 +1,9 @@
mod entity_security_state;
+mod provisional_securified_config;
+mod provisional_securified_transaction_queued;
mod unsecured_entity_control;
pub use entity_security_state::*;
+pub use provisional_securified_config::*;
+pub use provisional_securified_transaction_queued::*;
pub use unsecured_entity_control::*;
diff --git a/crates/sargon/src/profile/v100/entity_security_state/provisional_securified_config.rs b/crates/sargon/src/profile/v100/entity_security_state/provisional_securified_config.rs
new file mode 100644
index 000000000..34226de58
--- /dev/null
+++ b/crates/sargon/src/profile/v100/entity_security_state/provisional_securified_config.rs
@@ -0,0 +1,66 @@
+use crate::prelude::*;
+
+/// The different intermediary states of changing the security structure of an entity.
+/// This type is put in an `Option` on either `UnsecuredEntityControl` or `SecurifiedEntityControl`,
+/// and if `None` it means user has no provisionally changed security structure. If set, it contains
+/// these different variants:
+/// * `ShieldSelected` - User has selected which security shield to use for some entity,
+/// * `FactorInstancesDerived` - Sargon has provided a `SecurityStructureOfFactorInstances` but
+/// user has not made a transaction to apply it to the entity yet.
+/// * `TransactionQueued` - User has signed and queued a transaction changing to `SecurityStructureOfFactorInstances`
+#[derive(
+ Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, EnumAsInner,
+)]
+#[serde(tag = "discriminator")]
+pub enum ProvisionalSecurifiedConfig {
+ /// User has selected which security shield to use for some entity,
+ /// but no FactorInstances has been provided yet.
+ #[serde(rename = "shieldSelected")]
+ ShieldSelected { value: SecurityStructureID },
+
+ /// User has fully prepared a `SecurityStructureOfFactorInstances` but
+ /// not made a transaction to apply it to the entity yet.
+ #[serde(rename = "factorInstancesDerived")]
+ FactorInstancesDerived {
+ value: SecurityStructureOfFactorInstances,
+ },
+
+ /// User has signed and queued a transaction to apply a `SecurityStructureOfFactorInstances`
+ /// but it has not been submitted (confirmed) yet.
+ #[serde(rename = "transactionQueued")]
+ TransactionQueued {
+ value: ProvisionalSecurifiedTransactionQueued,
+ },
+}
+
+impl HasSampleValues for ProvisionalSecurifiedConfig {
+ fn sample() -> Self {
+ Self::ShieldSelected {
+ value: SecurityStructureID::sample(),
+ }
+ }
+ fn sample_other() -> Self {
+ Self::FactorInstancesDerived {
+ value: SecurityStructureOfFactorInstances::sample_other(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[allow(clippy::upper_case_acronyms)]
+ type SUT = ProvisionalSecurifiedConfig;
+
+ #[test]
+ fn equality() {
+ assert_eq!(SUT::sample(), SUT::sample());
+ assert_eq!(SUT::sample_other(), SUT::sample_other());
+ }
+
+ #[test]
+ fn inequality() {
+ assert_ne!(SUT::sample(), SUT::sample_other());
+ }
+}
diff --git a/crates/sargon/src/profile/v100/entity_security_state/provisional_securified_transaction_queued.rs b/crates/sargon/src/profile/v100/entity_security_state/provisional_securified_transaction_queued.rs
new file mode 100644
index 000000000..14c8851d2
--- /dev/null
+++ b/crates/sargon/src/profile/v100/entity_security_state/provisional_securified_transaction_queued.rs
@@ -0,0 +1,59 @@
+use crate::prelude::*;
+
+/// A tuple of a `SecurityStructureOfFactorInstances` and a `TransactionIntentHash`
+/// which represents a queued transaction to which is changing the security structure
+/// if some entity. Since this provisional state is set on the entity itself, no
+/// need to store the entity address here.
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
+pub struct ProvisionalSecurifiedTransactionQueued {
+ /// The FactorInstances we are changing to.
+ pub factor_instances: SecurityStructureOfFactorInstances,
+
+ /// The ID of the queued transaction which is changing the security structure
+ /// to `factor_instances`.
+ pub txid: TransactionIntentHash,
+}
+
+impl ProvisionalSecurifiedTransactionQueued {
+ pub fn new(
+ factor_instances: SecurityStructureOfFactorInstances,
+ txid: TransactionIntentHash,
+ ) -> Self {
+ Self {
+ factor_instances,
+ txid,
+ }
+ }
+}
+impl HasSampleValues for ProvisionalSecurifiedTransactionQueued {
+ fn sample() -> Self {
+ Self::new(
+ SecurityStructureOfFactorInstances::sample(),
+ TransactionIntentHash::sample(),
+ )
+ }
+ fn sample_other() -> Self {
+ Self::new(
+ SecurityStructureOfFactorInstances::sample_other(),
+ TransactionIntentHash::sample_other(),
+ )
+ }
+}
+#[cfg(test)]
+mod provisional_securified_transaction_queued_tests {
+ use super::*;
+
+ #[allow(clippy::upper_case_acronyms)]
+ type SUT = ProvisionalSecurifiedTransactionQueued;
+
+ #[test]
+ fn equality() {
+ assert_eq!(SUT::sample(), SUT::sample());
+ assert_eq!(SUT::sample_other(), SUT::sample_other());
+ }
+
+ #[test]
+ fn inequality() {
+ assert_ne!(SUT::sample(), SUT::sample_other());
+ }
+}
diff --git a/crates/sargon/src/profile/v100/entity_security_state/unsecured_entity_control.rs b/crates/sargon/src/profile/v100/entity_security_state/unsecured_entity_control.rs
index a9118e410..481ac357b 100644
--- a/crates/sargon/src/profile/v100/entity_security_state/unsecured_entity_control.rs
+++ b/crates/sargon/src/profile/v100/entity_security_state/unsecured_entity_control.rs
@@ -7,25 +7,34 @@ use crate::prelude::*;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct UnsecuredEntityControl {
- // /// The factor instance which was used to create this unsecured entity, which
- // /// also controls this entity and is used for signing transactions.
+ /// The factor instance which was used to create this unsecured entity, which
+ /// also controls this entity and is used for signing transactions.
pub transaction_signing: HierarchicalDeterministicFactorInstance,
- /// The factor instance which can be used for ROLA.
+ /// The provisional security structure configuration
#[serde(skip_serializing_if = "Option::is_none")]
- pub authentication_signing: Option,
+ pub provisional_securified_config: Option,
+}
+
+impl HasProvisionalSecurifiedConfig for UnsecuredEntityControl {
+ fn get_provisional(&self) -> Option {
+ self.provisional_securified_config.clone()
+ }
+
+ fn set_provisional_unchecked(
+ &mut self,
+ provisional_securified_config: impl Into<
+ Option,
+ >,
+ ) {
+ self.provisional_securified_config =
+ provisional_securified_config.into();
+ }
}
impl HasFactorInstances for UnsecuredEntityControl {
- fn unique_factor_instances(&self) -> IndexSet {
- let mut set = IndexSet::new();
- set.insert(self.transaction_signing.factor_instance());
- if let Some(authentication_signing) =
- self.authentication_signing.as_ref()
- {
- set.insert(authentication_signing.factor_instance());
- }
- set
+ fn unique_tx_signing_factor_instances(&self) -> IndexSet {
+ IndexSet::just(self.transaction_signing.factor_instance())
}
}
@@ -38,28 +47,17 @@ impl UnsecuredEntityControl {
{
Self {
transaction_signing: entity_creating_factor_instance.into(),
- authentication_signing: None,
+ provisional_securified_config: None,
}
}
#[cfg(not(tarpaulin_include))] // false negative
pub fn new(
transaction_signing: HierarchicalDeterministicFactorInstance,
- authentication_signing: Option,
+ provisional_securified_config: impl Into<
+ Option,
+ >,
) -> Result {
- let is_invalid_auth_signing_key = authentication_signing
- .as_ref()
- .map(|auth| {
- auth.get_key_kind() != CAP26KeyKind::AuthenticationSigning
- })
- .unwrap_or(false);
-
- if is_invalid_auth_signing_key {
- return Err(
- CommonError::WrongKeyKindOfAuthenticationSigningFactorInstance,
- );
- }
-
let key_kind = transaction_signing.get_key_kind();
if key_kind != CAP26KeyKind::TransactionSigning {
return Err(
@@ -68,7 +66,7 @@ impl UnsecuredEntityControl {
}
Ok(Self {
transaction_signing,
- authentication_signing,
+ provisional_securified_config: provisional_securified_config.into(),
})
}
@@ -114,15 +112,6 @@ mod tests {
assert_ne!(SUT::sample(), SUT::sample_other());
}
- #[test]
- fn with_auth_signing() {
- let tx_sign = HierarchicalDeterministicFactorInstance::sample();
- let auth_sign =
- HierarchicalDeterministicFactorInstance::sample_auth_signing();
- let control = SUT::new(tx_sign, Some(auth_sign.clone())).unwrap();
- assert_eq!(control.authentication_signing, Some(auth_sign));
- }
-
#[test]
fn json_roundtrip() {
let model = SUT::sample();
diff --git a/crates/sargon/src/profile/v100/factors/hierarchical_deterministic_factor_instance.rs b/crates/sargon/src/profile/v100/factors/hierarchical_deterministic_factor_instance.rs
index f37b88963..bfedeacd3 100644
--- a/crates/sargon/src/profile/v100/factors/hierarchical_deterministic_factor_instance.rs
+++ b/crates/sargon/src/profile/v100/factors/hierarchical_deterministic_factor_instance.rs
@@ -6,6 +6,11 @@ pub struct HierarchicalDeterministicFactorInstance {
pub public_key: HierarchicalDeterministicPublicKey,
}
+impl HasEntityKindObjectSafe for HierarchicalDeterministicFactorInstance {
+ fn get_entity_kind(&self) -> CAP26EntityKind {
+ self.public_key.derivation_path.get_entity_kind()
+ }
+}
impl IsKeySpaceAware for HierarchicalDeterministicFactorInstance {
fn key_space(&self) -> KeySpace {
self.public_key.key_space()
@@ -89,7 +94,11 @@ impl HierarchicalDeterministicFactorInstance {
let seed = factor_source_id.sample_associated_mnemonic().to_seed();
let hd_private_key = seed.derive_private_key(&derivation_path);
- Self::new(factor_source_id, hd_private_key.public_key())
+ assert_eq!(derivation_path.get_entity_kind(), entity_kind);
+
+ let self_ = Self::new(factor_source_id, hd_private_key.public_key());
+ assert_eq!(self_.get_entity_kind(), entity_kind);
+ self_
}
/// Mainnet
@@ -218,7 +227,10 @@ impl HierarchicalDeterministicFactorInstance {
/// Account | Mainnet
/// A sample used to facilitate unit tests.
- fn sample_with_key_kind(key_kind: CAP26KeyKind, index: u32) -> Self {
+ pub(crate) fn sample_with_key_kind(
+ key_kind: CAP26KeyKind,
+ index: u32,
+ ) -> Self {
Self::sample_with_key_kind_entity_kind(
key_kind,
CAP26EntityKind::Account,
@@ -240,7 +252,7 @@ impl HierarchicalDeterministicFactorInstance {
}
/// A sample used to facilitate unit tests.
- fn sample_with_key_kind_entity_kind(
+ pub(crate) fn sample_with_key_kind_entity_kind(
key_kind: CAP26KeyKind,
entity_kind: CAP26EntityKind,
index: u32,
@@ -260,19 +272,28 @@ impl HierarchicalDeterministicFactorInstance {
entity_kind: CAP26EntityKind,
index: u32,
) -> Self {
+ Self::sample_with_key_kind_entity_kind_on_network_and_hardened_index(
+ network_id,
+ key_kind,
+ entity_kind,
+ UnsecurifiedHardened::from_local_key_space(index).unwrap(),
+ )
+ }
+
+ pub(crate) fn sample_with_key_kind_entity_kind_on_network_and_hardened_index(
+ network_id: NetworkID,
+ key_kind: CAP26KeyKind,
+ entity_kind: CAP26EntityKind,
+ hardened: impl Into,
+ ) -> Self {
+ let hardened = hardened.into();
let path = match entity_kind {
CAP26EntityKind::Account => DerivationPath::from(AccountPath::new(
- network_id,
- key_kind,
- UnsecurifiedHardened::from_local_key_space(index).unwrap(),
+ network_id, key_kind, hardened,
)),
- CAP26EntityKind::Identity => {
- DerivationPath::from(IdentityPath::new(
- network_id,
- key_kind,
- UnsecurifiedHardened::from_local_key_space(index).unwrap(),
- ))
- }
+ CAP26EntityKind::Identity => DerivationPath::from(
+ IdentityPath::new(network_id, key_kind, hardened),
+ ),
};
let mwp = MnemonicWithPassphrase::sample();
diff --git a/crates/sargon/src/profile/v100/profile.rs b/crates/sargon/src/profile/v100/profile.rs
index 1b8dde339..1644a7450 100644
--- a/crates/sargon/src/profile/v100/profile.rs
+++ b/crates/sargon/src/profile/v100/profile.rs
@@ -294,7 +294,7 @@ impl Profile {
.items()
.into_iter()
.map(Into::::into)
- .map(|e| (e.clone(), e.unique_factor_instances()))
+ .map(|e| (e.clone(), e.unique_all_factor_instances()))
.collect::>>();
let Some(duplicate_instances) = self
@@ -331,7 +331,7 @@ impl Profile {
) -> IndexMap> {
self.all_entities_on_all_networks()
.into_iter()
- .map(|e| (e.clone(), e.unique_factor_instances()))
+ .map(|e| (e.clone(), e.unique_all_factor_instances()))
.collect()
}
diff --git a/crates/sargon/src/profile/v100/profile_legacy_state_bugs.rs b/crates/sargon/src/profile/v100/profile_legacy_state_bugs.rs
index dadac57c2..ea3fa8367 100644
--- a/crates/sargon/src/profile/v100/profile_legacy_state_bugs.rs
+++ b/crates/sargon/src/profile/v100/profile_legacy_state_bugs.rs
@@ -60,8 +60,8 @@ impl Profile {
value: UnsecuredEntityControl::new(hd_fi, None).unwrap(),
};
assert_eq!(
- account.unique_factor_instances(),
- persona.unique_factor_instances()
+ account.unique_tx_signing_factor_instances(),
+ persona.unique_tx_signing_factor_instances()
);
sut.networks = ProfileNetworks::just(ProfileNetwork::new(
NetworkID::Mainnet,
@@ -103,8 +103,8 @@ impl Profile {
};
assert_eq!(
- account.unique_factor_instances(),
- account2.unique_factor_instances()
+ account.unique_tx_signing_factor_instances(),
+ account2.unique_tx_signing_factor_instances()
);
sut.networks = ProfileNetworks::just(ProfileNetwork::new(
NetworkID::Mainnet,
@@ -116,39 +116,6 @@ impl Profile {
sut
}
- fn with_instance_collision_authentication_signing_key_kind() -> Self {
- let mwp = MnemonicWithPassphrase::sample_device();
- let mut sut = Profile::from_mnemonic_with_passphrase(
- mwp.clone(),
- HostId::sample(),
- HostInfo::sample(),
- );
- let mut account1 = Account::sample();
- let mut account2 = Account::sample_other();
- let mut uec1 = account1.try_get_unsecured_control().unwrap();
- uec1.authentication_signing = Some(
- HierarchicalDeterministicFactorInstance::sample_auth_signing(),
- );
- account1.security_state =
- EntitySecurityState::Unsecured { value: uec1 };
-
- let mut uec2 = account1.try_get_unsecured_control().unwrap();
- uec2.authentication_signing = Some(
- HierarchicalDeterministicFactorInstance::sample_auth_signing(),
- );
- account2.security_state =
- EntitySecurityState::Unsecured { value: uec2 };
-
- sut.networks = ProfileNetworks::just(ProfileNetwork::new(
- NetworkID::Mainnet,
- Accounts::from_iter([account1, account2]),
- Personas::default(),
- AuthorizedDapps::default(),
- ResourcePreferences::default(),
- ));
- sut
- }
-
fn with_instance_collision_securified() -> Self {
let mwp = MnemonicWithPassphrase::sample_device();
let mut sut = Profile::from_mnemonic_with_passphrase(
@@ -208,8 +175,8 @@ impl Profile {
};
assert_eq!(
- persona1.unique_factor_instances(),
- persona2.unique_factor_instances()
+ persona1.unique_tx_signing_factor_instances(),
+ persona2.unique_tx_signing_factor_instances()
);
sut.networks = ProfileNetworks::just(ProfileNetwork::new(
NetworkID::Mainnet,
@@ -245,7 +212,7 @@ mod tests {
let accounts = sut.accounts_on_current_network().unwrap();
let acc = accounts.first().unwrap();
let factor_instance = acc
- .unique_factor_instances()
+ .unique_tx_signing_factor_instances()
.into_iter()
.next()
.clone()
@@ -291,17 +258,6 @@ mod tests {
instance_detection(sut, acc1, acc2)
}
- #[test]
- fn instance_detection_auth_sign() {
- let sut =
- SUT::with_instance_collision_authentication_signing_key_kind();
- let accounts = sut.accounts_on_current_network().unwrap();
- let acc1 = accounts.clone().first().unwrap().clone();
- let acc2 = accounts.items().into_iter().next_back().unwrap();
-
- instance_detection(sut, acc1, acc2)
- }
-
#[test]
fn instance_detection_both_personas() {
let sut = SUT::with_instance_collision_both_personas();
@@ -330,7 +286,7 @@ mod tests {
let e1 = e1.into();
let factor_instance = e1
- .unique_factor_instances()
+ .unique_tx_signing_factor_instances()
.into_iter()
.next()
.clone()
diff --git a/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_proof.rs b/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_proof.rs
index c30102460..5ebf2e01a 100644
--- a/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_proof.rs
+++ b/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_proof.rs
@@ -14,32 +14,27 @@ pub struct WalletToDappInteractionAuthProof {
impl WalletToDappInteractionAuthProof {
pub fn new(
public_key: impl Into,
- curve: SLIP10Curve,
signature: impl Into,
) -> Self {
+ let public_key = public_key.into();
+ let signature = signature.into();
+ let curve = public_key.curve();
+ assert_eq!(signature.curve(), curve, "Discrepancy between the curve of the public key and the curve of the signature.");
Self {
- public_key: public_key.into(),
+ public_key,
curve,
- signature: signature.into(),
+ signature,
}
}
}
impl HasSampleValues for WalletToDappInteractionAuthProof {
fn sample() -> Self {
- Self::new(
- PublicKey::sample(),
- SLIP10Curve::sample(),
- Signature::sample(),
- )
+ Self::new(PublicKey::sample(), Signature::sample())
}
fn sample_other() -> Self {
- Self::new(
- PublicKey::sample_other(),
- SLIP10Curve::sample_other(),
- Signature::sample_other(),
- )
+ Self::new(PublicKey::sample_other(), Signature::sample_other())
}
}
@@ -60,4 +55,10 @@ mod tests {
fn inequality() {
assert_ne!(SUT::sample(), SUT::sample_other());
}
+
+ #[test]
+ #[should_panic]
+ fn panics_if_curve_discrepancy() {
+ let _ = SUT::new(PublicKey::sample(), Signature::sample_other());
+ }
}
diff --git a/crates/sargon/src/signing/authentication/authentication_signer.rs b/crates/sargon/src/signing/authentication/authentication_signer.rs
index 113ad6ecd..d623be2e7 100644
--- a/crates/sargon/src/signing/authentication/authentication_signer.rs
+++ b/crates/sargon/src/signing/authentication/authentication_signer.rs
@@ -63,7 +63,6 @@ mod test {
result,
WalletToDappInteractionAuthProof::new(
factor_instance.clone().public_key.public_key,
- factor_instance.clone().public_key.public_key.curve(),
Signature::try_from(
BagOfBytes::from_hex(
"eaa9a0eb1c9061e1999afa309db7bc9eecd30ad008f09cbfb2c4cf202759e914f6dbe01f67a7b8b1f1149f02b0e2662982fff4c1a765ee0d4d77651f1b91100c"
@@ -98,7 +97,6 @@ mod test {
result,
WalletToDappInteractionAuthProof::new(
factor_instance.clone().public_key.public_key,
- factor_instance.clone().public_key.public_key.curve(),
Signature::try_from(
BagOfBytes::from_hex(
"fb4f502e6a8bdbe3e66d365fe619270bafb9237e79a3c68dcf34448293f00840a0e5d48d1060eea49bf219cd3727c15cd6e305871956bc8d8ed8bcdc7fe97909"
@@ -132,20 +130,50 @@ mod test {
assert!(result.is_err());
}
- #[test]
- #[should_panic(
- expected = "Authentication signing not yet implemented for securified entities."
- )]
- fn test_with_multi_factor() {
+ #[actix_rt::test]
+ async fn securified_account() {
let factor_sources = FactorSource::sample_all();
let securified_account = Account::sample_at(2);
- _ = SUT::new(
+ let factor_instance = securified_account
+ .security_state()
+ .as_securified()
+ .unwrap()
+ .authentication_signing_factor_instance();
+ let sut = SUT::new(
+ Arc::new(TestAuthenticationInteractor::new_succeeding()),
+ &Profile::sample_from(factor_sources, [&securified_account], []),
+ AddressOfAccountOrPersona::from(securified_account.address),
+ DappToWalletInteractionAuthChallengeNonce::sample(),
+ DappToWalletInteractionMetadata::sample(),
+ )
+ .unwrap();
+
+ let result = sut.sign().await.unwrap();
+
+ pretty_assertions::assert_eq!(
+ result,
+ WalletToDappInteractionAuthProof::new(
+ factor_instance.clone().public_key.public_key,
+ "be359ca8edf8dccb2155e841952e88591fe42903da7e9a1560e863fe2fea94ed412d9eed282d201d48e5602fcde9a1dc201f440e86cb5f919b493bafc0ba2002".parse::().unwrap()
+ )
+ );
+ }
+
+ #[actix_rt::test]
+ async fn securified_account_with_failing_interactor() {
+ let factor_sources = FactorSource::sample_all();
+
+ let securified_account = Account::sample_at(2);
+ let sut = SUT::new(
Arc::new(TestAuthenticationInteractor::new_failing()),
&Profile::sample_from(factor_sources, [&securified_account], []),
AddressOfAccountOrPersona::from(securified_account.address),
DappToWalletInteractionAuthChallengeNonce::sample(),
DappToWalletInteractionMetadata::sample(),
)
+ .unwrap();
+ let res = sut.sign().await;
+ assert!(res.is_err());
}
}
diff --git a/crates/sargon/src/signing/authentication/authentication_signing_input.rs b/crates/sargon/src/signing/authentication/authentication_signing_input.rs
index 2656ddb43..2596c1ce2 100644
--- a/crates/sargon/src/signing/authentication/authentication_signing_input.rs
+++ b/crates/sargon/src/signing/authentication/authentication_signing_input.rs
@@ -51,11 +51,11 @@ impl AuthenticationSigningInput {
}?;
let factor_instance = match security_state {
- EntitySecurityState::Unsecured { value } => value
- .authentication_signing
- .unwrap_or(value.transaction_signing),
- EntitySecurityState::Securified { value: _ } => {
- panic!("Authentication signing not yet implemented for securified entities.")
+ EntitySecurityState::Unsecured { value } => {
+ value.transaction_signing
+ }
+ EntitySecurityState::Securified { value } => {
+ value.authentication_signing_factor_instance()
}
};
diff --git a/crates/sargon/src/signing/authentication/authentication_signing_interactor.rs b/crates/sargon/src/signing/authentication/authentication_signing_interactor.rs
index daa59e871..4f90758fe 100644
--- a/crates/sargon/src/signing/authentication/authentication_signing_interactor.rs
+++ b/crates/sargon/src/signing/authentication/authentication_signing_interactor.rs
@@ -85,11 +85,7 @@ impl From for WalletToDappInteractionAuthProof {
let signature_with_public_key = value.signature_with_public_key;
let public_key = signature_with_public_key.public_key();
- Self::new(
- public_key,
- public_key.curve(),
- signature_with_public_key.signature(),
- )
+ Self::new(public_key, signature_with_public_key.signature())
}
}
diff --git a/crates/sargon/src/signing/collector/signatures_collector.rs b/crates/sargon/src/signing/collector/signatures_collector.rs
index 80810b950..2e7108da5 100644
--- a/crates/sargon/src/signing/collector/signatures_collector.rs
+++ b/crates/sargon/src/signing/collector/signatures_collector.rs
@@ -1894,17 +1894,27 @@ mod tests {
let collector =
SignaturesCollector::test_lazy_sign_minimum_no_failures([
SignableWithEntities::::sample([
- sample_securified_mainnet::("Alice", HierarchicalDeterministicFactorInstance::sample_fii10(), || {
- GeneralRoleWithHierarchicalDeterministicFactorInstances::with_factors_and_role(
+ sample_securified_mainnet::(
+ "Alice",
+ if E::entity_kind() == CAP26EntityKind::Identity
+ {
+ HierarchicalDeterministicFactorInstance::sample_fii10()
+ } else {
+ HierarchicalDeterministicFactorInstance::sample_fia10()
+ },
+ || {
+ GeneralRoleWithHierarchicalDeterministicFactorInstances::with_factors_and_role(
RoleKind::Primary, [], 0,
FactorSource::sample_all().into_iter().map(|f| {
- HierarchicalDeterministicFactorInstance::sample_mainnet_tx_account(
- Hardened::from_local_key_space(0, IsSecurified(true)).unwrap(),
+ HierarchicalDeterministicFactorInstance::new_for_entity(
*f.factor_source_id().as_hash().unwrap(),
+ E::entity_kind(),
+ Hardened::from_local_key_space(0, IsSecurified(true)).unwrap(),
)
}),
).unwrap()
- }),
+ },
+ ),
]),
]);
let outcome = collector.collect_signatures().await.unwrap();
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 6e12aac3b..8ba4e4927 100644
--- a/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs
+++ b/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs
@@ -858,8 +858,9 @@ impl SargonOS {
)?;
SecurityStructureOfFactorInstances::new(
security_structure_id,
- matrix_of_factor_instances
- )
+ 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)),
+ )?
};
Ok((entity_address, security_structure_of_factor_instances))
}).collect::>>()?;
diff --git a/crates/sargon/src/system/sargon_os/sargon_os_entities_linked_to_factor_source.rs b/crates/sargon/src/system/sargon_os/sargon_os_entities_linked_to_factor_source.rs
index 2563786d2..dff0497a5 100644
--- a/crates/sargon/src/system/sargon_os/sargon_os_entities_linked_to_factor_source.rs
+++ b/crates/sargon/src/system/sargon_os/sargon_os_entities_linked_to_factor_source.rs
@@ -232,6 +232,7 @@ mod tests {
/// - 1 visible Account (sample_stokenet_nadia)
/// - 1 hidden Account (sample_stokenet_olivia)
/// - 1 visible Persona (sample_stokenet_leia_skywalker)
+ ///
/// And the corresponding mocked secure/unsafe storages.
async fn boot_with_entities(
device_mnemonic_in_secure_storage: bool,
diff --git a/crates/sargon/src/types/samples/account_samples.rs b/crates/sargon/src/types/samples/account_samples.rs
index 8c718a7ba..3d3c44d1f 100644
--- a/crates/sargon/src/types/samples/account_samples.rs
+++ b/crates/sargon/src/types/samples/account_samples.rs
@@ -171,9 +171,27 @@ 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);
+
+ let security_structure_of_factor_instances =
+ SecurityStructureOfFactorInstances::new(
+ SecurityStructureID::sample(),
+ matrix,
+ HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(
+ NetworkID::Mainnet,
+ CAP26KeyKind::AuthenticationSigning,
+ CAP26EntityKind::Account,
+ SecurifiedU30::try_from(rola_index).unwrap(),
+ ),
+ )
+ .unwrap();
+
Self {
network_id,
address,
@@ -181,10 +199,7 @@ impl Account {
security_state: SecuredEntityControl::new(
Some(veci.clone()),
AccessControllerAddress::sample_from_account_address(address),
- SecurityStructureOfFactorInstances {
- security_structure_id: SecurityStructureID::sample(),
- matrix_of_factors: matrix,
- },
+ security_structure_of_factor_instances,
)
.unwrap()
.into(),
diff --git a/crates/sargon/src/types/samples/persona_samples.rs b/crates/sargon/src/types/samples/persona_samples.rs
index 278b7dd69..c4f50722c 100644
--- a/crates/sargon/src/types/samples/persona_samples.rs
+++ b/crates/sargon/src/types/samples/persona_samples.rs
@@ -93,6 +93,39 @@ static ALL_PERSONA_SAMPLES: Lazy<[Persona; 8]> = Lazy::new(|| {
]
});
+impl DerivationPath {
+ /// # Safety
+ /// Crashes for Bip44LikePath, this is only meant to be used for tests
+ /// to map between IdentityPath -> IdentityPath
+ unsafe fn as_persona(&self) -> Self {
+ match self {
+ Self::Account { value } => {
+ IdentityPath::new(value.network_id, value.key_kind, value.index)
+ .into()
+ }
+ Self::Identity { value: _ } => self.clone(),
+ Self::Bip44Like { value: _ } => panic!("unsupported"),
+ }
+ }
+}
+
+impl HierarchicalDeterministicFactorInstance {
+ /// # Safety
+ /// Completely unsafe, this is an invalid FactorInstance! It hardcodes
+ /// the derivation path as a persona, resulting in an invalid (DerivationPath, PublicKey) pair.!
+ unsafe fn invalid_hard_coding_derivation_path_as_persona(&self) -> Self {
+ unsafe {
+ Self::new(
+ self.factor_source_id(),
+ HierarchicalDeterministicPublicKey::new(
+ self.public_key(),
+ self.derivation_path().as_persona(),
+ ),
+ )
+ }
+ }
+}
+
impl Persona {
pub fn sample_unsecurified_mainnet(
name: impl AsRef,
@@ -121,6 +154,7 @@ impl Persona {
veci: HierarchicalDeterministicFactorInstance,
make_role: impl Fn() -> GeneralRoleWithHierarchicalDeterministicFactorInstances,
) -> Self {
+ assert_eq!(veci.get_entity_kind(), CAP26EntityKind::Identity);
let role = make_role();
assert_eq!(role.get_role_kind(), RoleKind::Primary, "If this tests fails you can update the code below to not be hardcoded to set the primary role...");
let mut matrix = MatrixOfFactorInstances::sample();
@@ -135,8 +169,58 @@ impl Persona {
.map(FactorInstance::from)
.collect_vec(),
);
+ unsafe {
+ matrix.recovery_role =
+ RecoveryRoleWithFactorInstances::with_factors(
+ 0,
+ [],
+ matrix
+ .recovery()
+ .get_override_factors()
+ .iter()
+ .filter_map(|f| f.try_as_hd_factor_instances().ok())
+ .map(|f| {
+ f.invalid_hard_coding_derivation_path_as_persona()
+ })
+ .map(FactorInstance::from)
+ .collect_vec(),
+ );
+ matrix.confirmation_role =
+ ConfirmationRoleWithFactorInstances::with_factors(
+ 0,
+ [],
+ matrix
+ .confirmation()
+ .get_override_factors()
+ .iter()
+ .filter_map(|f| f.try_as_hd_factor_instances().ok())
+ .map(|f| {
+ f.invalid_hard_coding_derivation_path_as_persona()
+ })
+ .map(FactorInstance::from)
+ .collect_vec(),
+ );
+ }
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(),
+ matrix,
+ HierarchicalDeterministicFactorInstance::sample_with_key_kind_entity_kind_on_network_and_hardened_index(
+ NetworkID::Mainnet,
+ CAP26KeyKind::AuthenticationSigning,
+ CAP26EntityKind::Identity,
+ SecurifiedU30::try_from(rola_index).unwrap(),
+ ),
+ )
+ .unwrap();
+
Self {
network_id: NetworkID::Mainnet,
address,
@@ -144,10 +228,7 @@ impl Persona {
security_state: SecuredEntityControl::new(
veci.clone(),
AccessControllerAddress::sample_from_identity_address(address),
- SecurityStructureOfFactorInstances {
- security_structure_id: SecurityStructureID::sample(),
- matrix_of_factors: matrix,
- },
+ security_structure_of_factor_instances,
)
.unwrap()
.into(),
diff --git a/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/addresses_manifest_builder_support.rs b/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/addresses_manifest_builder_support.rs
index 0ba27b566..4e8874b0a 100644
--- a/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/addresses_manifest_builder_support.rs
+++ b/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/addresses_manifest_builder_support.rs
@@ -95,6 +95,19 @@ macro_rules! is_dynamic_resource_address {
};
}
+impl TryInto for &AddressOfAccountOrPersona {
+ type Error = crate::CommonError;
+ fn try_into(self) -> Result {
+ match self {
+ AddressOfAccountOrPersona::Account(value) => {
+ TryInto::::try_into(value)
+ }
+ AddressOfAccountOrPersona::Identity(value) => {
+ TryInto::::try_into(value)
+ }
+ }
+ }
+}
is_dynamic_component_address!(AccountAddress);
is_dynamic_component_address!(AccessControllerAddress);
is_dynamic_component_address!(ComponentAddress);
diff --git a/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests.rs b/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests.rs
index c0c24536a..3c2c04596 100644
--- a/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests.rs
+++ b/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests.rs
@@ -38,12 +38,29 @@ impl TransactionManifest {
address_of_account_or_persona: &AddressOfAccountOrPersona,
owner_key_hashes: Vec,
) -> Self {
- Self::set_metadata(
+ let builder = Self::set_owner_keys_hashes_on_builder(
+ address_of_account_or_persona,
+ owner_key_hashes,
+ ScryptoTransactionManifestBuilder::new(),
+ );
+ TransactionManifest::sargon_built(
+ builder,
+ address_of_account_or_persona.network_id(),
+ )
+ }
+
+ pub fn set_owner_keys_hashes_on_builder(
+ address_of_account_or_persona: &AddressOfAccountOrPersona,
+ owner_key_hashes: Vec,
+ builder: ScryptoTransactionManifestBuilder,
+ ) -> ScryptoTransactionManifestBuilder {
+ Self::set_metadata_on_builder(
address_of_account_or_persona,
MetadataKey::OwnerKeys,
ScryptoMetadataValue::PublicKeyHashArray(
owner_key_hashes.into_iter().map(|h| h.into()).collect_vec(),
),
+ builder,
)
}
@@ -126,14 +143,26 @@ impl TransactionManifest {
where
A: IntoScryptoAddress,
{
- let builder = ScryptoTransactionManifestBuilder::new().set_metadata(
- address.scrypto(),
+ let builder = Self::set_metadata_on_builder(
+ address,
key,
value,
+ ScryptoTransactionManifestBuilder::new(),
);
-
TransactionManifest::sargon_built(builder, address.network_id())
}
+
+ fn set_metadata_on_builder(
+ address: &A,
+ key: MetadataKey,
+ value: impl ScryptoToMetadataEntry,
+ builder: ScryptoTransactionManifestBuilder,
+ ) -> ScryptoTransactionManifestBuilder
+ where
+ A: IntoScryptoAddress,
+ {
+ builder.set_metadata(address.scrypto(), key, value)
+ }
}
#[cfg(test)]
diff --git a/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests_access_controller.rs b/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests_access_controller.rs
new file mode 100644
index 000000000..ce39dcf24
--- /dev/null
+++ b/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests_access_controller.rs
@@ -0,0 +1,225 @@
+use radix_engine_interface::blueprints::access_controller::AccessControllerCreateManifestInput as ScryptoAccessControllerCreateManifestInput;
+
+use crate::prelude::*;
+
+impl TransactionManifest {
+ pub fn securify_unsecurified_entity(
+ entity: E,
+ security_structure_of_factor_instances: SecurityStructureOfFactorInstances,
+ ) -> Result {
+ let Ok(unsecurified) = entity.security_state().into_unsecured() else {
+ return Err(CommonError::CannotSecurifyEntityItIsAlreadySecurifiedAccordingToProfile);
+ };
+
+ if unsecurified.provisional_securified_config.is_some() {
+ return Err(
+ CommonError::CannotSecurifyEntityHasProvisionalSecurityConfig,
+ );
+ };
+
+ Self::_securify_unsecurified_entity(
+ Into::::into(entity.address()),
+ security_structure_of_factor_instances,
+ )
+ }
+
+ fn _securify_unsecurified_entity(
+ entity_address: AddressOfAccountOrPersona,
+ security_structure_of_factor_instances: SecurityStructureOfFactorInstances,
+ ) -> Result {
+ security_structure_of_factor_instances
+ .assert_has_entity_kind(entity_address.get_entity_kind())?;
+
+ let (security_entity_identifier, owner_badge) =
+ if entity_address.is_identity() {
+ (
+ SCRYPTO_IDENTITY_SECURIFY_IDENT,
+ SCRYPTO_IDENTITY_OWNER_BADGE,
+ )
+ } else {
+ (SCRYPTO_ACCOUNT_SECURIFY_IDENT, SCRYPTO_ACCOUNT_OWNER_BADGE)
+ };
+
+ let mut builder = ScryptoTransactionManifestBuilder::new();
+ let bucket_factory = BucketFactory::default();
+
+ // Securify the entity which will return an entity owner badge onto the worktop.
+ let owner_badge_bucket = &{
+ builder = builder.call_method(
+ &entity_address,
+ security_entity_identifier,
+ (),
+ );
+
+ // Create a bucket out of the entity owner badge.
+ let owner_badge_bucket = bucket_factory.next();
+ builder =
+ builder.take_from_worktop(owner_badge, 1, &owner_badge_bucket);
+ owner_badge_bucket
+ };
+
+ // Create an access controller for the entity.
+ {
+ let timed_recovery_delay_in_minutes =
+ security_structure_of_factor_instances
+ .timed_recovery_delay_in_minutes();
+ let rule_set = ScryptoRuleSet::from(
+ security_structure_of_factor_instances.matrix_of_factors,
+ );
+
+ builder = builder.create_access_controller(
+ owner_badge_bucket,
+ rule_set.primary_role,
+ rule_set.recovery_role,
+ rule_set.confirmation_role,
+ Some(timed_recovery_delay_in_minutes),
+ );
+ }
+
+ // Set Rola Key
+ {
+ let rola_key_hash = PublicKeyHash::hash(
+ security_structure_of_factor_instances
+ .authentication_signing_factor_instance
+ .public_key(),
+ );
+ let owner_key_hashes = vec![rola_key_hash];
+ builder = Self::set_owner_keys_hashes_on_builder(
+ &entity_address,
+ owner_key_hashes,
+ builder,
+ );
+ }
+
+ let manifest = TransactionManifest::sargon_built(
+ builder,
+ entity_address.network_id(),
+ );
+
+ Ok(manifest)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+
+ #[test]
+ fn cannot_securify_entity_it_is_already_securified_according_to_profile() {
+ let account = Account::sample_at(2);
+ assert!(account.is_securified());
+ let res = TransactionManifest::securify_unsecurified_entity(
+ account,
+ SecurityStructureOfFactorInstances::sample(),
+ );
+ assert_eq!(res, Err(CommonError::CannotSecurifyEntityItIsAlreadySecurifiedAccordingToProfile));
+ }
+
+ #[test]
+ fn cannot_securify_entity_with_provisional() {
+ let mut account = Account::sample_alice();
+ assert!(!account.is_securified());
+ account
+ .security_state
+ .set_provisional(ProvisionalSecurifiedConfig::ShieldSelected {
+ value: SecurityStructureID::sample(),
+ })
+ .unwrap();
+ let res = TransactionManifest::securify_unsecurified_entity(
+ account,
+ SecurityStructureOfFactorInstances::sample(),
+ );
+ assert_eq!(
+ res,
+ Err(CommonError::CannotSecurifyEntityHasProvisionalSecurityConfig)
+ );
+ }
+
+ #[test]
+ fn test_securify_unsecurified_account() {
+ let expected_manifest_str = include_str!(concat!(
+ env!("FIXTURES_TX"),
+ "create_access_controller_for_account.rtm"
+ ));
+ let entity = Account::sample();
+ let security_structure_of_factor_instances =
+ SecurityStructureOfFactorInstances::sample();
+ let manifest = TransactionManifest::securify_unsecurified_entity(
+ entity.clone(),
+ security_structure_of_factor_instances.clone(),
+ )
+ .unwrap();
+ manifest_eq(manifest, expected_manifest_str);
+ assert!(expected_manifest_str.contains("securify"));
+ assert!(expected_manifest_str.contains(
+ &security_structure_of_factor_instances
+ .timed_recovery_delay_in_minutes()
+ .to_string()
+ ));
+
+ for fi in security_structure_of_factor_instances
+ .unique_all_factor_instances()
+ .into_iter()
+ .filter_map(|f| f.try_as_hd_factor_instances().ok())
+ {
+ assert!(expected_manifest_str
+ .contains(&PublicKeyHash::hash(fi.public_key()).to_string()));
+ }
+
+ assert!(expected_manifest_str.contains(&entity.address.to_string()));
+ }
+
+ #[test]
+ fn test_securify_unsecurified_persona() {
+ let expected_manifest_str = include_str!(concat!(
+ env!("FIXTURES_TX"),
+ "create_access_controller_for_persona.rtm"
+ ));
+ let entity = Persona::sample_other();
+ let security_structure_of_factor_instances =
+ SecurityStructureOfFactorInstances::sample_other();
+ let manifest = TransactionManifest::securify_unsecurified_entity(
+ entity.clone(),
+ security_structure_of_factor_instances.clone(),
+ )
+ .unwrap();
+ manifest_eq(manifest, expected_manifest_str);
+
+ assert!(expected_manifest_str.contains("securify"));
+ assert!(expected_manifest_str.contains(
+ &security_structure_of_factor_instances
+ .timed_recovery_delay_in_minutes()
+ .to_string()
+ ));
+
+ for fi in security_structure_of_factor_instances
+ .unique_all_factor_instances()
+ .into_iter()
+ .filter_map(|f| f.try_as_hd_factor_instances().ok())
+ {
+ assert!(expected_manifest_str
+ .contains(&PublicKeyHash::hash(fi.public_key()).to_string()));
+ }
+
+ assert!(expected_manifest_str.contains(&entity.address.to_string()));
+ }
+
+ #[test]
+ fn test_mismatch_entity_kind_account_persona() {
+ let manifest = TransactionManifest::securify_unsecurified_entity(
+ Account::sample_other(),
+ SecurityStructureOfFactorInstances::sample_other(),
+ );
+ assert_eq!(manifest, Err(CommonError::SecurityStructureOfFactorInstancesEntityDiscrepancyInEntityKind { entity_kind_of_entity: CAP26EntityKind::Account, entity_kind_of_factor_instances: CAP26EntityKind::Identity }));
+ }
+
+ #[test]
+ fn test_mismatch_entity_kind_persona_account() {
+ let manifest = TransactionManifest::securify_unsecurified_entity(
+ Persona::sample_other(),
+ SecurityStructureOfFactorInstances::sample(),
+ );
+ assert_eq!(manifest, Err(CommonError::SecurityStructureOfFactorInstancesEntityDiscrepancyInEntityKind { entity_kind_of_entity: CAP26EntityKind::Identity, entity_kind_of_factor_instances: CAP26EntityKind::Account }));
+ }
+}
diff --git a/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs b/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs
index 68a211c81..d9e8cc0a8 100644
--- a/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs
+++ b/crates/sargon/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs
@@ -6,6 +6,7 @@ mod delete_account;
mod manifest_account_locker;
mod manifest_assets_transfers;
mod manifests;
+mod manifests_access_controller;
mod manifests_create_tokens;
mod metadata;
mod modify_manifest;
@@ -19,6 +20,7 @@ pub use delete_account::*;
pub use manifest_account_locker::*;
pub use manifest_assets_transfers::*;
pub use manifests::*;
+pub use manifests_access_controller::*;
pub use manifests_create_tokens::*;
pub use metadata::*;
pub use modify_manifest::*;
diff --git a/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/public_key_hash.rs b/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/public_key_hash.rs
index 67c31a4c4..a49854fdd 100644
--- a/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/public_key_hash.rs
+++ b/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/public_key_hash.rs
@@ -2,7 +2,16 @@ use crate::prelude::*;
/// Hashes of public keys, either Ed25519PublicKey or Secp256k1PublicKey
#[derive(
- Clone, Copy, Debug, PartialEq, PartialOrd, Ord, EnumAsInner, Eq, Hash,
+ Clone,
+ Copy,
+ Debug,
+ PartialEq,
+ PartialOrd,
+ Ord,
+ EnumAsInner,
+ Eq,
+ Hash,
+ derive_more::Display,
)]
pub enum PublicKeyHash {
Ed25519 { value: Exactly29Bytes },
diff --git a/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/transaction_hashes.rs b/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/transaction_hashes.rs
index b8e7ba793..76cab7a91 100644
--- a/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/transaction_hashes.rs
+++ b/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/transaction_hashes.rs
@@ -18,7 +18,7 @@ macro_rules! decl_tx_hash {
#[doc = $expr]
)*
#[derive(
- Clone, PartialEq, Eq, Hash, derive_more::Display, derive_more::Debug,
+ Clone, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr, derive_more::Display, derive_more::Debug,
)]
#[display("{}", self.bech32_encoded_tx_id)]
#[debug("{}", self.bech32_encoded_tx_id)]
diff --git a/crates/sargon/tests/vectors/main.rs b/crates/sargon/tests/vectors/main.rs
index 8c97fbd54..27e86f854 100644
--- a/crates/sargon/tests/vectors/main.rs
+++ b/crates/sargon/tests/vectors/main.rs
@@ -770,7 +770,6 @@ mod wallet_to_dapp_interaction_tests {
WalletToDappInteractionAuthProof::new(
PublicKey::from_str("ff8aee4c625738e35d837edb11e33b8abe0d6f40849ca1451edaba84d04d0699")
.unwrap(),
- SLIP10Curve::Curve25519,
Signature::from_str("10177ac7d486691777133ffe59d46d55529d86cb1c4ce66aa82f432372f33e24d803d8498f42e26fe113c030fce68c526aeacff94334ba5a7f7ef84c2936eb05")
.unwrap()
),
@@ -786,7 +785,6 @@ mod wallet_to_dapp_interaction_tests {
WalletToDappInteractionAuthProof::new(
PublicKey::from_str("11b162e3343ce770b6e9ed8a29d125b5580d1272b0dc4e2bd0fcae33320d9566")
.unwrap(),
- SLIP10Curve::Curve25519,
Signature::from_str("e18617b527d4d33607a8adb6a040c26ca97642ec89dd8a6fe7a41fa724473e4cc69b0729c1df57aba77455801f2eef6f28848a5d206e3739de29ca2288957502")
.unwrap(),
),
@@ -796,7 +794,6 @@ mod wallet_to_dapp_interaction_tests {
WalletToDappInteractionAuthProof::new(
PublicKey::from_str("5386353e4cc27e3d27d064d777d811e242a16ba7aefd425062ed46631739619d")
.unwrap(),
- SLIP10Curve::Curve25519,
Signature::from_str("0143fd941d51f531c8265b0f6b24f4cfcdfd24b40aac47dee6fb3386ce0d400563c892e3894a33840d1c7af2dd43ecd0729fd209171003765d109a04d7485605")
.unwrap(),
),
@@ -888,7 +885,6 @@ mod wallet_to_dapp_interaction_tests {
WalletToDappInteractionAuthProof::new(
PublicKey::from_str("ff8aee4c625738e35d837edb11e33b8abe0d6f40849ca1451edaba84d04d0699")
.unwrap(),
- SLIP10Curve::Curve25519,
Signature::from_str("10177ac7d486691777133ffe59d46d55529d86cb1c4ce66aa82f432372f33e24d803d8498f42e26fe113c030fce68c526aeacff94334ba5a7f7ef84c2936eb05")
.unwrap()
),
@@ -931,7 +927,6 @@ mod wallet_to_dapp_interaction_tests {
WalletToDappInteractionAuthProof::new(
PublicKey::from_str("ff8aee4c625738e35d837edb11e33b8abe0d6f40849ca1451edaba84d04d0699")
.unwrap(),
- SLIP10Curve::Curve25519,
Signature::from_str("10177ac7d486691777133ffe59d46d55529d86cb1c4ce66aa82f432372f33e24d803d8498f42e26fe113c030fce68c526aeacff94334ba5a7f7ef84c2936eb05")
.unwrap()
),
@@ -941,7 +936,6 @@ mod wallet_to_dapp_interaction_tests {
WalletToDappInteractionAuthProof::new(
PublicKey::from_str("ff8aee4c625738e35d837edb11e33b8abe0d6f40849ca1451edaba84d04d0699")
.unwrap(),
- SLIP10Curve::Curve25519,
Signature::from_str("10177ac7d486691777133ffe59d46d55529d86cb1c4ce66aa82f432372f33e24d803d8498f42e26fe113c030fce68c526aeacff94334ba5a7f7ef84c2936eb05")
.unwrap()
),