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

[ABW-1599] Change FactorSource into enum #534

Merged
merged 72 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
952293d
WIP
CyonAlexRDX May 30, 2023
9e6eb09
WIP
CyonAlexRDX May 30, 2023
49e9ca8
WIP
CyonAlexRDX May 30, 2023
6ccf273
WIP
CyonAlexRDX May 30, 2023
3e8cc03
WIP
CyonAlexRDX May 30, 2023
9b4f3bb
WIP
CyonAlexRDX May 30, 2023
25eb4d7
WIP
CyonAlexRDX May 30, 2023
1db9587
WIP
CyonAlexRDX May 30, 2023
262b582
WIP
CyonAlexRDX May 30, 2023
2fa2264
WIP
CyonAlexRDX May 30, 2023
a93dbfc
WIP
CyonAlexRDX May 30, 2023
10f3615
WIP
CyonAlexRDX May 30, 2023
573b135
WIP
CyonAlexRDX May 30, 2023
971545f
WIP
CyonAlexRDX May 30, 2023
3353f24
WIP
CyonAlexRDX May 30, 2023
6306c91
WIP
CyonAlexRDX May 30, 2023
46e3bf9
WIP
CyonAlexRDX May 30, 2023
e6fd59a
merge
CyonAlexRDX May 30, 2023
8dbab26
WIP
CyonAlexRDX May 30, 2023
6327236
WIP
CyonAlexRDX May 30, 2023
1d6b3a7
WIP
CyonAlexRDX May 30, 2023
596fdba
WIP
CyonAlexRDX May 30, 2023
f746dcb
WIP
CyonAlexRDX May 30, 2023
3178684
WIP
CyonAlexRDX May 30, 2023
e8b40ab
WIP
CyonAlexRDX May 30, 2023
b993f0b
WIP
CyonAlexRDX May 30, 2023
ba3b47d
WIP
CyonAlexRDX May 30, 2023
ef6b6c2
WIP
CyonAlexRDX May 30, 2023
b7365c3
WIP
CyonAlexRDX May 30, 2023
b53b69a
WIP
CyonAlexRDX May 30, 2023
5063c89
WIP
CyonAlexRDX May 30, 2023
8341e3f
WIP
CyonAlexRDX May 30, 2023
c8fdf7e
WIP
CyonAlexRDX May 30, 2023
2857907
WIP
CyonAlexRDX May 30, 2023
97515ad
WIP
CyonAlexRDX May 30, 2023
41a1cd1
WIP
CyonAlexRDX May 30, 2023
744bded
WIP
CyonAlexRDX May 30, 2023
b391398
WIP
CyonAlexRDX May 30, 2023
941d29c
WIP
CyonAlexRDX May 30, 2023
6bf2ae6
WIP
CyonAlexRDX May 30, 2023
834add3
WIP
CyonAlexRDX May 30, 2023
9c24120
WIP
CyonAlexRDX May 30, 2023
8c7e79e
WIP
CyonAlexRDX May 30, 2023
f3a2665
WIP
CyonAlexRDX May 30, 2023
b1acc02
WIP
CyonAlexRDX May 30, 2023
a4f4997
WIP
CyonAlexRDX May 30, 2023
4ea98b9
WIP
CyonAlexRDX May 30, 2023
368b069
WIP
CyonAlexRDX May 30, 2023
b7b726c
WIP
CyonAlexRDX May 30, 2023
ce1267d
WIP
CyonAlexRDX May 30, 2023
2741948
WIP
CyonAlexRDX May 30, 2023
6a18559
WIP
CyonAlexRDX May 30, 2023
c049877
WIP
CyonAlexRDX May 30, 2023
f95b409
WIP
CyonAlexRDX May 30, 2023
7d72913
WIP
CyonAlexRDX May 30, 2023
c6fe8f6
WIP
CyonAlexRDX May 31, 2023
7bc5d5e
Merge branch 'main' into enum_factor_source
CyonAlexRDX May 31, 2023
6b0f332
Merge branch 'main' into enum_factor_source
CyonAlexRDX May 31, 2023
5ca6463
WIP
CyonAlexRDX May 31, 2023
da9285a
Merge branch 'main' into enum_factor_source
CyonAlexRDX May 31, 2023
d9ee380
merge
CyonAlexRDX Jun 1, 2023
d3897ae
merge
CyonAlexRDX Jun 1, 2023
8f33fff
WIP
CyonAlexRDX Jun 1, 2023
f74409a
WIP
CyonAlexRDX Jun 1, 2023
866032f
WIP
CyonAlexRDX Jun 1, 2023
a6245f9
WIP
CyonAlexRDX Jun 1, 2023
af7106b
WIP
CyonAlexRDX Jun 1, 2023
377633a
reset soft retry fix archive failing
CyonAlexRDX Jun 2, 2023
128d315
WIP
CyonAlexRDX Jun 2, 2023
8437364
WIP
CyonAlexRDX Jun 2, 2023
0c3ab53
WIP
CyonAlexRDX Jun 2, 2023
452515f
Merge branch 'main' into enum_factor_source
CyonAlexRDX Jun 2, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ struct ImportMnemonicPreviewApp: App {
ImportMnemonic.View(
store: Store(
initialState: ImportMnemonic.State(
saveInProfileKind: .offDevice
persistAsMnemonicKind: .offDevice,
offDeviceMnemonicInfoPrompt: .init(mnemonicWithPassphrase: .testValue)
),
reducer: ImportMnemonic()
._printChanges()
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,6 @@ package.addModules([
"ROLAClient", // calc expected hashed message for signAuth for validation
"RadixConnectClient",
"FactorSourcesClient", // FIXME: move models to lower level package
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"), // actually just CasePaths
],
tests: .no
),
Expand Down Expand Up @@ -860,6 +859,7 @@ package.addModules([
"EngineToolkit", // address derivation, blake hash
"RadixConnectModels",
"Resources",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"), // actually just CasePaths
],
tests: .yes(
dependencies: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,21 @@ struct DiscrepancyUnsupportedCurve: Swift.Error {}

// MARK: - PublicKeysFromOnDeviceHDRequest
public struct PublicKeysFromOnDeviceHDRequest: Sendable, Hashable {
public let hdOnDeviceFactorSource: HDOnDeviceFactorSource
public let deviceFactorSource: DeviceFactorSource
public let derivationPaths: OrderedSet<DerivationPath>
public let loadMnemonicPurpose: SecureStorageClient.LoadMnemonicPurpose

public init(
hdOnDeviceFactorSource: HDOnDeviceFactorSource,
deviceFactorSource: DeviceFactorSource,
derivationPaths: OrderedSet<DerivationPath>,
loadMnemonicPurpose: SecureStorageClient.LoadMnemonicPurpose
) throws {
for derivationPath in derivationPaths {
guard hdOnDeviceFactorSource.parameters.supportedCurves.contains(derivationPath.curveForScheme) else {
guard deviceFactorSource.cryptoParameters.supportedCurves.contains(derivationPath.curveForScheme) else {
throw DiscrepancyUnsupportedCurve()
}
}
self.hdOnDeviceFactorSource = hdOnDeviceFactorSource
self.deviceFactorSource = deviceFactorSource
self.derivationPaths = derivationPaths
self.loadMnemonicPurpose = loadMnemonicPurpose
}
Expand Down Expand Up @@ -121,7 +121,7 @@ extension DeviceFactorSourceClient {
}

public func signUsingDeviceFactorSource(
deviceFactorSource: HDOnDeviceFactorSource,
deviceFactorSource: DeviceFactorSource,
signerEntities: Set<EntityPotentiallyVirtual>,
unhashedDataToSign: some DataProtocol,
purpose: SigningPurpose
Expand Down Expand Up @@ -162,7 +162,7 @@ extension DeviceFactorSourceClient {
}
let curve = factorInstance.publicKey.curve

loggerGlobal.debug("🔏 Signing data with device, with entity=\(entity.displayName), curve=\(curve), factor source label=\(deviceFactorSource.label), description=\(deviceFactorSource.description)")
loggerGlobal.debug("🔏 Signing data with device, with entity=\(entity.displayName), curve=\(curve), factor source hint.name=\(deviceFactorSource.hint.name), hint.model=\(deviceFactorSource.hint.model)")

let signatureWithPublicKey = try await self.signatureFromOnDeviceHD(.init(
hdRoot: hdRoot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extension DeviceFactorSourceClient: DependencyKey {

return Self(
publicKeysFromOnDeviceHD: { request in
let factorSourceID = request.hdOnDeviceFactorSource.id
let factorSourceID = request.deviceFactorSource.id

guard
let mnemonicWithPassphrase = try await secureStorageClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ extension FactorSourcesClient {

// MARK: - AddPrivateHDFactorSourceRequest
public struct AddPrivateHDFactorSourceRequest: Sendable, Hashable {
public let privateFactorSource: PrivateHDFactorSource
public let factorSource: FactorSource
public let mnemonicWithPasshprase: MnemonicWithPassphrase
/// E.g. import babylon factor sources should only be saved keychain, not profile (already there).
public let saveIntoProfile: Bool
public init(privateFactorSource: PrivateHDFactorSource, saveIntoProfile: Bool) {
self.privateFactorSource = privateFactorSource
public init(factorSource: FactorSource, mnemonicWithPasshprase: MnemonicWithPassphrase, saveIntoProfile: Bool) {
self.factorSource = factorSource
self.mnemonicWithPasshprase = mnemonicWithPasshprase
self.saveIntoProfile = saveIntoProfile
}
}
Expand Down Expand Up @@ -86,18 +88,18 @@ extension FactorSourcesClient {

public func getDeviceFactorSource(
of hdFactorInstance: HierarchicalDeterministicFactorInstance
) async throws -> HDOnDeviceFactorSource? {
) async throws -> DeviceFactorSource? {
guard let factorSource = try await getFactorSource(of: hdFactorInstance.factorInstance) else {
return nil
}
return try HDOnDeviceFactorSource(factorSource: factorSource)
return try factorSource.extract(as: DeviceFactorSource.self)
}

public func getFactorSource(
public func getFactorSource<Source: FactorSourceProtocol>(
id: FactorSourceID,
ensureKind kind: FactorSourceKind
) async throws -> FactorSource? {
try await getFactorSource(id: id) { $0.kind == kind }
as _: Source.Type
) async throws -> Source? {
try await getFactorSource(id: id)?.extract(Source.self)
}

public func getFactorSource(
Expand All @@ -112,10 +114,10 @@ extension FactorSourcesClient {
try await IdentifiedArrayOf(uniqueElements: getFactorSources().filter(filter))
}

public func getFactorSources(
ofKind kind: FactorSourceKind
) async throws -> IdentifiedArrayOf<FactorSource> {
try await getFactorSources(matching: { $0.kind == kind })
public func getFactorSources<Source: FactorSourceProtocol>(
type _: Source.Type
) async throws -> IdentifiedArrayOf<Source> {
try await IdentifiedArrayOf(uniqueElements: getFactorSources().compactMap { $0.extract(Source.self) })
}
}

Expand Down Expand Up @@ -151,15 +153,16 @@ public struct SigningFactor: Sendable, Hashable, Identifiable {
public typealias ID = FactorSource.ID
public var id: ID { factorSource.id }
public let factorSource: FactorSource
public var signers: NonEmpty<IdentifiedArrayOf<Signer>>
public typealias Signers = NonEmpty<IdentifiedArrayOf<Signer>>
public var signers: Signers

public var expectedSignatureCount: Int {
signers.map(\.factorInstancesRequiredToSign.count).reduce(0, +)
}

public init(
factorSource: FactorSource,
signers: NonEmpty<IdentifiedArrayOf<Signer>>
signers: Signers
) {
self.factorSource = factorSource
self.signers = signers
Expand All @@ -179,16 +182,16 @@ public struct SigningFactor: Sendable, Hashable, Identifiable {
extension FactorSourcesClient {
public func addOffDeviceFactorSource(
mnemonicWithPassphrase: MnemonicWithPassphrase,
label: FactorSource.Label,
description: FactorSource.Description
label: OffDeviceMnemonicFactorSource.Hint.Label
) async throws -> FactorSourceID {
let privateFactorSource = try FactorSource.offDeviceMnemonic(
withPassphrase: mnemonicWithPassphrase,
label: label,
description: description
let factorSource = try OffDeviceMnemonicFactorSource.from(
mnemonicWithPassphrase: mnemonicWithPassphrase,
label: label
)

return try await addPrivateHDFactorSource(.init(
privateFactorSource: privateFactorSource,
factorSource: factorSource.embed(),
mnemonicWithPasshprase: mnemonicWithPassphrase,
saveIntoProfile: true
))
}
Expand All @@ -197,18 +200,16 @@ extension FactorSourcesClient {
onDeviceMnemonicKind: MnemonicBasedFactorSourceKind.OnDeviceMnemonicKind,
mnemonicWithPassphrase: MnemonicWithPassphrase
) async throws -> FactorSourceID {
let isOlympia = onDeviceMnemonicKind == .olympia
let hdOnDeviceFactorSource: HDOnDeviceFactorSource = isOlympia ? try FactorSource.olympia(
mnemonicWithPassphrase: mnemonicWithPassphrase
) : try FactorSource.babylon(mnemonicWithPassphrase: mnemonicWithPassphrase).hdOnDeviceFactorSource
let isOlympiaCompatible = onDeviceMnemonicKind == .olympia

let factorSource: DeviceFactorSource = try isOlympiaCompatible
? .olympia(mnemonicWithPassphrase: mnemonicWithPassphrase)
: .babylon(mnemonicWithPassphrase: mnemonicWithPassphrase)

let privateFactorSource = try PrivateHDFactorSource(
mnemonicWithPassphrase: mnemonicWithPassphrase,
factorSource: hdOnDeviceFactorSource.factorSource
)
return try await addPrivateHDFactorSource(.init(
privateFactorSource: privateFactorSource,
saveIntoProfile: isOlympia
factorSource: factorSource.embed(),
mnemonicWithPasshprase: mnemonicWithPassphrase,
saveIntoProfile: isOlympiaCompatible
))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,23 @@ extension FactorSourcesClient: DependencyKey {
await getProfileStore().factorSourcesValues()
},
addPrivateHDFactorSource: { request in
let privateFactorSource = request.privateFactorSource
if privateFactorSource.kind == .device {
try await secureStorageClient.saveMnemonicForFactorSource(privateFactorSource)
let factorSource = request.factorSource

switch factorSource {
case let .device(deviceFactorSource):
try await secureStorageClient.saveMnemonicForFactorSource(.init(mnemonicWithPassphrase: request.mnemonicWithPasshprase, factorSource: deviceFactorSource))
default: break
}
let factorSourceID = privateFactorSource.id
let factorSourceID = factorSource.id

/// We only need to save olympia mnemonics into Profile, the Babylon ones
/// already exist in profile, and this function is used only to save the
/// imported mnemonic into keychain (done above).
if request.saveIntoProfile {
do {
try await saveFactorSource(privateFactorSource.factorSource)
try await saveFactorSource(factorSource)
} catch {
if privateFactorSource.kind == .device {
if factorSource.kind == .device {
// We were unlucky, failed to update Profile, thus best to undo the saving of
// the mnemonic in keychain (if we can).
try? await secureStorageClient.deleteMnemonicByFactorSourceID(factorSourceID)
Expand Down Expand Up @@ -102,8 +105,7 @@ extension FactorSourcesClient: DependencyKey {
guard var factorSource = factorSources[id: id] else {
throw FactorSourceNotFound()
}

factorSource.lastUsedOn = request.lastUsedOn
factorSource.common.lastUsedOn = request.lastUsedOn
factorSources[id: id] = factorSource
}
profile.factorSources = .init(rawValue: factorSources)!
Expand All @@ -120,7 +122,7 @@ internal func signingFactors(
from allFactorSources: IdentifiedArrayOf<FactorSource>,
signingPurpose: SigningPurpose
) throws -> SigningFactors {
var signingFactorsNotNonEmpty: [FactorSourceKind: IdentifiedArrayOf<SigningFactor>] = [:]
CyonAlexRDX marked this conversation as resolved.
Show resolved Hide resolved
var signingFactors: [FactorSourceKind: IdentifiedArrayOf<SigningFactor>] = [:]

for entity in entities {
switch entity.securityState {
Expand All @@ -143,7 +145,7 @@ internal func signingFactors(
let signer = try Signer(factorInstanceRequiredToSign: factorInstance, entity: entity)
let sigingFactor = SigningFactor(factorSource: factorSource, signer: signer)

if var existingArray: IdentifiedArrayOf<SigningFactor> = signingFactorsNotNonEmpty[factorSource.kind] {
if var existingArray: IdentifiedArrayOf<SigningFactor> = signingFactors[factorSource.kind] {
if var existingSigningFactor = existingArray[id: factorSource.id] {
var signers = existingSigningFactor.signers.rawValue
signers[id: signer.id] = signer // update copy of `signers`
Expand All @@ -152,16 +154,16 @@ internal func signingFactors(
} else {
existingArray[id: factorSource.id] = sigingFactor // write back to IdentifiedArray
}
signingFactorsNotNonEmpty[factorSource.kind] = existingArray // write back to Dictionary
signingFactors[factorSource.kind] = existingArray // write back to Dictionary
} else {
// trivial case,
signingFactorsNotNonEmpty[factorSource.kind] = .init(uniqueElements: [sigingFactor])
CyonAlexRDX marked this conversation as resolved.
Show resolved Hide resolved
signingFactors[factorSource.kind] = .init(uniqueElements: [sigingFactor])
}
}
}

return SigningFactors(
uniqueKeysWithValues: signingFactorsNotNonEmpty.map { keyValuePair -> (key: FactorSourceKind, value: NonEmpty<Set<SigningFactor>>) in
uniqueKeysWithValues: signingFactors.map { keyValuePair -> (key: FactorSourceKind, value: NonEmpty<Set<SigningFactor>>) in
assert(!keyValuePair.value.isEmpty, "Incorrect implementation, IdentifiedArrayOf<SigningFactor> should never be empty.")
let value: NonEmpty<Set<SigningFactor>> = .init(rawValue: Set(keyValuePair.value))!
return (key: keyValuePair.key, value: value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ public struct LedgerHardwareWalletClient: Sendable {
extension LedgerHardwareWalletClient {
public typealias IsConnectedToAnyConnectorExtension = @Sendable () async -> AnyAsyncSequence<Bool>
public typealias GetDeviceInfo = @Sendable () async throws -> P2P.ConnectorExtension.Response.LedgerHardwareWallet.Success.GetDeviceInfo
public typealias DerivePublicKeys = @Sendable (OrderedSet<P2P.LedgerHardwareWallet.KeyParameters>, LedgerFactorSource) async throws -> OrderedSet<HierarchicalDeterministicPublicKey>
public typealias DerivePublicKeys = @Sendable (OrderedSet<P2P.LedgerHardwareWallet.KeyParameters>, LedgerHardwareWalletFactorSource) async throws -> OrderedSet<HierarchicalDeterministicPublicKey>
public typealias SignTransaction = @Sendable (SignTransactionWithLedgerRequest) async throws -> Set<SignatureOfEntity>
public typealias SignAuthChallenge = @Sendable (SignAuthChallengeWithLedgerRequest) async throws -> Set<SignatureOfEntity>
}

// MARK: - SignTransactionWithLedgerRequest
public struct SignTransactionWithLedgerRequest: Sendable, Hashable {
public let signers: NonEmpty<IdentifiedArrayOf<Signer>>
public let ledger: LedgerFactorSource
public let ledger: LedgerHardwareWalletFactorSource
public let unhashedDataToSign: Data
public let ledgerTXDisplayMode: P2P.ConnectorExtension.Request.LedgerHardwareWallet.Request.SignTransaction.Mode
public let displayHashOnLedgerDisplay: Bool

public init(
ledger: LedgerFactorSource,
ledger: LedgerHardwareWalletFactorSource,
signers: NonEmpty<IdentifiedArrayOf<Signer>>,
unhashedDataToSign: Data,
ledgerTXDisplayMode: P2P.ConnectorExtension.Request.LedgerHardwareWallet.Request.SignTransaction.Mode,
Expand All @@ -46,13 +46,13 @@ public struct SignTransactionWithLedgerRequest: Sendable, Hashable {
// MARK: - SignAuthChallengeWithLedgerRequest
public struct SignAuthChallengeWithLedgerRequest: Sendable, Hashable {
public let signers: NonEmpty<IdentifiedArrayOf<Signer>>
public let ledger: LedgerFactorSource
public let ledger: LedgerHardwareWalletFactorSource
public let challenge: P2P.Dapp.Request.AuthChallengeNonce
public let origin: P2P.Dapp.Request.Metadata.Origin
public let dAppDefinitionAddress: AccountAddress

public init(
ledger: LedgerFactorSource,
ledger: LedgerHardwareWalletFactorSource,
signers: NonEmpty<IdentifiedArrayOf<Signer>>,
challenge: P2P.Dapp.Request.AuthChallengeNonce,
origin: P2P.Dapp.Request.Metadata.Origin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,22 +170,30 @@ extension LedgerHardwareWalletClient: DependencyKey {
}()
}

extension LedgerFactorSource {
extension LedgerHardwareWalletFactorSource {
func device() throws -> P2P.LedgerHardwareWallet.LedgerDevice {
guard let model = P2P.LedgerHardwareWallet.Model(rawValue: model.rawValue) else {
throw UnrecognizedLedgerModel(model: model.rawValue)
guard let model = P2P.LedgerHardwareWallet.Model(rawValue: hint.model.rawValue) else {
throw UnrecognizedLedgerModel(model: hint.model.rawValue)
}
return P2P.LedgerHardwareWallet.LedgerDevice(
name: NonEmptyString(maybeString: self.name),
id: factorSource.id.hex(),
name: NonEmptyString(maybeString: self.hint.name.rawValue),
id: Data(id.hexCodable.data.dropFirst()).hex, // Connector Extension only cares about the hash not the factor source ID prefix.
model: model
)
}
}

// MARK: - UnrecognizedLedgerModel
public struct UnrecognizedLedgerModel: Error {
public let model: String
public init(model: String) {
self.model = model
}
}

extension P2P.LedgerHardwareWallet.LedgerDevice {
public init(factorSource: FactorSource) throws {
self = try LedgerFactorSource(factorSource: factorSource).device()
self = try factorSource.extract(as: LedgerHardwareWalletFactorSource.self).device()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ extension DependencyValues {
}
}

extension FactorSource.ID {
static let mocked = try! Self(hexCodable: .init(hex: String(repeating: "deadbeef", count: 8)))
}

// MARK: - LedgerHardwareWalletClient + TestDependencyKey
extension LedgerHardwareWalletClient: TestDependencyKey {
public static let previewValue: Self = .noop
Expand All @@ -20,7 +16,7 @@ extension LedgerHardwareWalletClient: TestDependencyKey {
isConnectedToAnyConnectorExtension: { AsyncLazySequence([]).eraseToAnyAsyncSequence() },
getDeviceInfo: {
.init(
id: .mocked,
id: try! .init(hex: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
model: .nanoS
)
},
Expand Down
Loading