Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update signing interactor's IO with per factor models #333

Merged
merged 6 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
"version" : "1.1.4"
"revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
"version" : "1.1.0"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1",
"version" : "1.3.3"
"revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c",
"version" : "1.3.0"
}
},
{
Expand All @@ -41,8 +41,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1",
"version" : "1.4.3"
"revision" : "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2",
"version" : "1.1.2"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
public class ThrowingHostInteractor: HostInteractor {
public nonisolated(unsafe) static var shared: HostInteractor = ThrowingHostInteractor()

public func signAuth(request: SargonUniFFI.SignRequestOfAuthIntent) async throws -> SargonUniFFI.SignWithFactorsOutcomeOfAuthIntentHash {
public func signAuth(request: SargonUniFFI.SignRequestOfAuthIntent) async throws -> SargonUniFFI.SignResponseOfAuthIntentHash {
throw CommonError.SigningRejected
}

public func signTransactions(request: SargonUniFFI.SignRequestOfTransactionIntent) async throws -> SargonUniFFI.SignWithFactorsOutcomeOfTransactionIntentHash {
public func signTransactions(request: SargonUniFFI.SignRequestOfTransactionIntent) async throws -> SargonUniFFI.SignResponseOfTransactionIntentHash {
throw CommonError.SigningRejected
}

public func signSubintents(request: SargonUniFFI.SignRequestOfSubintent) async throws -> SargonUniFFI.SignWithFactorsOutcomeOfSubintentHash {
public func signSubintents(request: SargonUniFFI.SignRequestOfSubintent) async throws -> SargonUniFFI.SignResponseOfSubintentHash {
throw CommonError.SigningRejected
}

Expand Down
48 changes: 48 additions & 0 deletions apple/Tests/TestCases/System/ThrowingHostInteractorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation
import Sargon
import SargonUniFFI
import XCTest

final class ThrowingHostInteractorTests: TestCase {
typealias SUT = ThrowingHostInteractor

func testDeriveKeysThrows() async throws {
do {
let _ = try await SUT.shared.deriveKeys(
request: SargonUniFFI.KeyDerivationRequest(derivationPurpose: .creatingNewAccount, perFactorSource: [])
)
} catch {
XCTAssertEqual(error as? CommonError, CommonError.SigningRejected)
}
}

func testSignAuthThrows() async throws {
do {
let _ = try await SUT.shared.signAuth(
request: SargonUniFFI.SignRequestOfAuthIntent(factorSourceKind: .device, perFactorSource: [])
)
} catch {
XCTAssertEqual(error as? CommonError, CommonError.SigningRejected)
}
}

func testSignTransactionsThrows() async throws {
do {
let _ = try await SUT.shared.signTransactions(
request: SargonUniFFI.SignRequestOfTransactionIntent(factorSourceKind: .device, perFactorSource: [])
)
} catch {
XCTAssertEqual(error as? CommonError, CommonError.SigningRejected)
}
}

func testSignSubintentsThrows() async throws {
do {
let _ = try await SUT.shared.signSubintents(
request: SargonUniFFI.SignRequestOfSubintent(factorSourceKind: .device, perFactorSource: [])
)
} catch {
XCTAssertEqual(error as? CommonError, CommonError.SigningRejected)
}
}
}
1 change: 1 addition & 0 deletions crates/app/signing-traits/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition = "2021"
prelude = { workspace = true }
error = { workspace = true }
ecc = { workspace = true }
enum-as-inner = { workspace = true }
core-collections = { workspace = true }
bytes = { workspace = true }
hash = { workspace = true }
Expand Down
207 changes: 207 additions & 0 deletions crates/app/signing-traits/src/host_interaction/factor_outcome.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use crate::prelude::*;

/// The outcome of the signing process for each factor source as collected by the `SignInteractor`.
#[derive(
Clone, PartialEq, Eq, enum_as_inner::EnumAsInner, derive_more::Debug,
)]
pub enum FactorOutcome<ID: SignableID> {
/// The user successfully signed with the factor source, the associated
/// value `produced_signatures` contains the produced signatures and any relevant metadata.
#[debug("Signed: {:#?}", produced_signatures)]
Signed {
produced_signatures: IndexSet<HDSignature<ID>>,
},

/// The factor source got neglected, either due to user explicitly skipping
/// or due to failure
#[debug("Neglected")]
Neglected(NeglectedFactor),
}

impl<ID: SignableID + HasSampleValues> HasSampleValues for FactorOutcome<ID> {
fn sample() -> Self {
let signature = HDSignature::<ID>::sample();

Self::signed(IndexSet::just(signature)).unwrap()
}

fn sample_other() -> Self {
Self::skipped(FactorSourceIDFromHash::sample_other())
}
}

impl<ID: SignableID> FactorOutcome<ID> {
pub fn signed(
produced_signatures: IndexSet<HDSignature<ID>>,
) -> Result<Self> {
if produced_signatures.is_empty() {
return Err(CommonError::ExpectedNonEmptyCollection);
}

let factor_source_id = &produced_signatures
.first()
.expect("Should have at least one signature")
.factor_source_id();

if produced_signatures
.iter()
.any(|s| s.factor_source_id() != *factor_source_id)
{
return Err(CommonError::FactorOutcomeSignedFactorSourceIDMismatch);
}

Ok(FactorOutcome::Signed {
produced_signatures,
})
}

pub fn failure(factor: FactorSourceIDFromHash) -> Self {
FactorOutcome::Neglected(NeglectedFactor::new(
NeglectFactorReason::Failure,
factor,
))
}

pub fn skipped(factor: FactorSourceIDFromHash) -> Self {
FactorOutcome::Neglected(NeglectedFactor::new(
NeglectFactorReason::UserExplicitlySkipped,
factor,
))
}

pub fn irrelevant(factor: FactorSourceIDFromHash) -> Self {
FactorOutcome::Neglected(NeglectedFactor::new(
NeglectFactorReason::Irrelevant,
factor,
))
}

pub fn factor_source_id(&self) -> FactorSourceIDFromHash {
match self {
FactorOutcome::Signed {
produced_signatures,
} => {
let signature = produced_signatures
.first()
.expect("Should have at least one signature");

signature.factor_source_id()
}
FactorOutcome::Neglected(neglected_factor) => {
neglected_factor.factor_source_id()
}
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[allow(clippy::upper_case_acronyms)]
type SUT = FactorOutcome<TransactionIntentHash>;

#[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());
}

#[test]
pub fn test_signed() {
let signature = HDSignature::sample();
let signatures = IndexSet::just(signature.clone());

let sut = SUT::signed(signatures.clone()).unwrap();

assert_eq!(sut.as_signed().unwrap().clone(), signatures);
}

#[test]
pub fn test_signed_no_signatures_failure() {
let result = SUT::signed(IndexSet::new());

assert_eq!(result, Err(CommonError::ExpectedNonEmptyCollection));
}

#[test]
pub fn test_signed_different_factor_source_id_failure() {
let result = SUT::signed(
IndexSet::from([
HDSignature::fake_sign_by_looking_up_mnemonic_amongst_samples(
HDSignatureInput::new(
TransactionIntentHash::sample(),
OwnedFactorInstance::new(
AddressOfAccountOrPersona::sample(),
HierarchicalDeterministicFactorInstance::sample_mainnet_tx_account(
Hardened::from_local_key_space_unsecurified(0u32).unwrap(),
FactorSourceIDFromHash::sample_device(),
),
)
)
),
HDSignature::fake_sign_by_looking_up_mnemonic_amongst_samples(
HDSignatureInput::new(
TransactionIntentHash::sample(),
OwnedFactorInstance::new(
AddressOfAccountOrPersona::sample(),
HierarchicalDeterministicFactorInstance::sample_mainnet_tx_account(
Hardened::from_local_key_space_unsecurified(1u32).unwrap(),
FactorSourceIDFromHash::sample_device_other(),
),
)
)
)
])
);

assert_eq!(
result,
Err(CommonError::FactorOutcomeSignedFactorSourceIDMismatch)
);
}

#[test]
pub fn test_failure() {
let factor_source_id = FactorSourceIDFromHash::sample();

let sut = SUT::failure(factor_source_id);

assert_eq!(sut.as_neglected().unwrap().content, factor_source_id);
assert_eq!(
sut.as_neglected().unwrap().reason,
NeglectFactorReason::Failure
);
}

#[test]
pub fn test_skipped() {
let factor_source_id = FactorSourceIDFromHash::sample();

let sut = SUT::skipped(factor_source_id);

assert_eq!(sut.as_neglected().unwrap().content, factor_source_id);
assert_eq!(
sut.as_neglected().unwrap().reason,
NeglectFactorReason::UserExplicitlySkipped
);
}

#[test]
pub fn test_irrelevant() {
let factor_source_id = FactorSourceIDFromHash::sample();

let sut = SUT::irrelevant(factor_source_id);

assert_eq!(sut.as_neglected().unwrap().content, factor_source_id);
assert_eq!(
sut.as_neglected().unwrap().reason,
NeglectFactorReason::Irrelevant
);
}
}
6 changes: 4 additions & 2 deletions crates/app/signing-traits/src/host_interaction/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
mod factor_outcome;
mod per_factor_source_input;
mod sign_interactor;
mod sign_request;
mod sign_response;
mod sign_with_factors_outcome;
mod transaction_sign_request_input;

pub use factor_outcome::*;
pub use per_factor_source_input::*;
pub use sign_interactor::*;
pub use sign_request::*;
pub use sign_response::*;
pub use sign_with_factors_outcome::*;
pub use transaction_sign_request_input::*;
Loading
Loading