Skip to content

Commit

Permalink
[ABW-1268] CAP33 parse imported payloads (#438)
Browse files Browse the repository at this point in the history
  • Loading branch information
CyonAlexRDX committed Apr 11, 2023
1 parent 00912c3 commit f335c43
Show file tree
Hide file tree
Showing 45 changed files with 5,679 additions and 258 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ImportLegacyWalletClientTests"
BuildableName = "ImportLegacyWalletClientTests"
BlueprintName = "ImportLegacyWalletClientTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,6 @@
"version" : "1.0.2"
}
},
{
"identity" : "swift-parsing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-parsing",
"state" : {
"revision" : "c6e2241daa46e5c6e5027a93b161bca6ba692bcc",
"version" : "0.12.0"
}
},
{
"identity" : "swift-tagged",
"kind" : "remoteSourceControl",
Expand Down
10 changes: 6 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -436,11 +436,13 @@ package.addModules([
dependencies: [
"AccountsClient",
"EngineToolkitClient",
.product(name: "Parsing", package: "swift-parsing") {
.package(url: "https://github.com/pointfreeco/swift-parsing", from: "0.12.0")
},
"Profile", // Olympia models
],
tests: .yes()
tests: .yes(
resources: [
.process("TestVectors/"),
]
)
),
.client(
name: "LocalAuthenticationClient",
Expand Down
12 changes: 11 additions & 1 deletion Sources/Clients/AccountsClient/AccountsClient+Interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,19 @@ extension AccountsClient {
public typealias AccountsOnCurrentNetwork = @Sendable () async -> AnyAsyncSequence<Profile.Network.Accounts>

public typealias CreateUnsavedVirtualAccount = @Sendable (CreateVirtualEntityRequest) async throws -> Profile.Network.Account
public typealias SaveVirtualAccount = @Sendable (Profile.Network.Account, _ shouldUpdateFactorSourceNextDerivationIndex: Bool) async throws -> Void
public typealias SaveVirtualAccount = @Sendable (SaveAccountRequest) async throws -> Void

public typealias GetAccountByAddress = @Sendable (AccountAddress) async throws -> Profile.Network.Account

public typealias HasAccountOnNetwork = @Sendable (NetworkID) async throws -> Bool
}

// MARK: - SaveAccountRequest
public struct SaveAccountRequest: Sendable, Hashable {
public let account: Profile.Network.Account
public let shouldUpdateFactorSourceNextDerivationIndex: Bool
public init(account: Profile.Network.Account, shouldUpdateFactorSourceNextDerivationIndex: Bool) {
self.account = account
self.shouldUpdateFactorSourceNextDerivationIndex = shouldUpdateFactorSourceNextDerivationIndex
}
}
2 changes: 1 addition & 1 deletion Sources/Clients/AccountsClient/AccountsClient+Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension AccountsClient: TestDependencyKey {
accountsOnCurrentNetwork: { AsyncLazySequence([]).eraseToAnyAsyncSequence() },
getAccountsOnNetwork: { _ in throw NoopError() },
createUnsavedVirtualAccount: { _ in throw NoopError() },
saveVirtualAccount: { _, _ in },
saveVirtualAccount: { _ in },
getAccountByAddress: { _ in throw NoopError() },
hasAccountOnNetwork: { _ in false }
)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Clients/AccountsClientLive/AccountsClient+Live.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ extension AccountsClient: DependencyKey {
public static func live(
profileStore getProfileStore: @escaping @Sendable () async -> ProfileStore = { await .shared }
) -> Self {
let saveVirtualAccount: SaveVirtualAccount = { account, shouldUpdateFactorSourceNextDerivationIndex in
let saveVirtualAccount: SaveVirtualAccount = { request in
try await getProfileStore().updating {
try $0.addAccount(account, shouldUpdateFactorSourceNextDerivationIndex: shouldUpdateFactorSourceNextDerivationIndex)
try $0.addAccount(request.account, shouldUpdateFactorSourceNextDerivationIndex: request.shouldUpdateFactorSourceNextDerivationIndex)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ public struct FactorSourcesClient: Sendable {
public var getFactorSources: GetFactorSources
public var factorSourcesAsyncSequence: FactorSourcesAsyncSequence
public var addPrivateHDFactorSource: AddPrivateHDFactorSource
public var checkIfHasOlympiaFactorSourceForAccounts: CheckIfHasOlympiaFactorSourceForAccounts

public init(
getFactorSources: @escaping GetFactorSources,
factorSourcesAsyncSequence: @escaping FactorSourcesAsyncSequence,
addPrivateHDFactorSource: @escaping AddPrivateHDFactorSource
addPrivateHDFactorSource: @escaping AddPrivateHDFactorSource,
checkIfHasOlympiaFactorSourceForAccounts: @escaping CheckIfHasOlympiaFactorSourceForAccounts
) {
self.getFactorSources = getFactorSources
self.factorSourcesAsyncSequence = factorSourcesAsyncSequence
self.addPrivateHDFactorSource = addPrivateHDFactorSource
self.checkIfHasOlympiaFactorSourceForAccounts = checkIfHasOlympiaFactorSourceForAccounts
}
}

Expand All @@ -23,6 +26,7 @@ extension FactorSourcesClient {
public typealias GetFactorSources = @Sendable () async throws -> FactorSources
public typealias FactorSourcesAsyncSequence = @Sendable () async -> AnyAsyncSequence<FactorSources>
public typealias AddPrivateHDFactorSource = @Sendable (PrivateHDFactorSource) async throws -> FactorSourceID
public typealias CheckIfHasOlympiaFactorSourceForAccounts = @Sendable (NonEmpty<OrderedSet<OlympiaAccountToMigrate>>) async -> FactorSourceID?
}

extension FactorSourcesClient {
Expand All @@ -39,3 +43,34 @@ extension FactorSourcesClient {
return try await self.addPrivateHDFactorSource(privateFactorSource)
}
}

// Move elsewhere?
import Cryptography
extension MnemonicWithPassphrase {
@discardableResult
public func validatePublicKeysOf(
softwareAccounts: NonEmpty<OrderedSet<OlympiaAccountToMigrate>>
) throws -> Bool {
let hdRoot = try self.hdRoot()

for olympiaAccount in softwareAccounts {
let path = olympiaAccount.path.fullPath

let derivedPublicKey = try hdRoot.derivePrivateKey(
path: path,
curve: SECP256K1.self
).publicKey

guard derivedPublicKey == olympiaAccount.publicKey else {
throw ValidateOlympiaAccountsFailure.publicKeyMismatch
}
}
// PublicKeys matches
return true
}
}

// MARK: - ValidateOlympiaAccountsFailure
enum ValidateOlympiaAccountsFailure: LocalizedError {
case publicKeyMismatch
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ extension FactorSourcesClient: TestDependencyKey {
public static let testValue = Self(
getFactorSources: unimplemented("\(Self.self).getFactorSources"),
factorSourcesAsyncSequence: unimplemented("\(Self.self).factorSourcesAsyncSequence"),
addPrivateHDFactorSource: unimplemented("\(Self.self).addPrivateHDFactorSource")
addPrivateHDFactorSource: unimplemented("\(Self.self).addPrivateHDFactorSource"),
checkIfHasOlympiaFactorSourceForAccounts: unimplemented("\(Self.self).checkIfHasOlympiaFactorSourceForAccounts")
)

public static let noop = Self(
getFactorSources: { throw NoopError() },
factorSourcesAsyncSequence: { AsyncLazySequence([]).eraseToAnyAsyncSequence() },
addPrivateHDFactorSource: { _ in throw NoopError() }
addPrivateHDFactorSource: { _ in throw NoopError() },
checkIfHasOlympiaFactorSourceForAccounts: { _ in nil }
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ extension FactorSourcesClient: DependencyKey {
profileStore getProfileStore: @escaping @Sendable () async -> ProfileStore = { await .shared }
) -> Self {
@Dependency(\.secureStorageClient) var secureStorageClient

let getFactorSources: GetFactorSources = {
await getProfileStore().profile.factorSources
}

return Self(
getFactorSources: {
await getProfileStore().profile.factorSources
},
getFactorSources: getFactorSources,
factorSourcesAsyncSequence: {
await getProfileStore().factorSourcesValues()
},
Expand All @@ -36,6 +39,34 @@ extension FactorSourcesClient: DependencyKey {
}

return factorSourceID
},
checkIfHasOlympiaFactorSourceForAccounts: { softwareAccounts in
guard softwareAccounts.allSatisfy({ $0.accountType == .software }) else {
assertionFailure("Unexpectedly received hardware account, unable to verify.")
return nil
}
do {
let factorSourceIDs = try await getFactorSources()
.filter { $0.kind == .device && $0.supportsOlympia }
.map(\.id)

for factorSourceID in factorSourceIDs {
guard let mnemonic = try await secureStorageClient.loadMnemonicByFactorSourceID(factorSourceID, .importOlympiaAccounts) else {
continue
}
guard try mnemonic.validatePublicKeysOf(softwareAccounts: softwareAccounts) else {
continue
}
// YES Managed to validate all software accounts against existing factor source
loggerGlobal.debug("Existing factor source found for selected Olympia software accounts.")
return factorSourceID
}

return nil // failed to find any factor source
} catch {
loggerGlobal.warning("Failed to check if olympia factor source exists, error: \(error)")
return nil // failure
}
}
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import AccountsClient
import ClientPrelude
import Cryptography
import EngineToolkitClient
import Profile

// MARK: - ImportLegacyWalletClient
public struct ImportLegacyWalletClient: Sendable {
Expand All @@ -10,14 +8,18 @@ public struct ImportLegacyWalletClient: Sendable {

public var migrateOlympiaSoftwareAccountsToBabylon: MigrateOlympiaSoftwareAccountsToBabylon
public var migrateOlympiaHardwareAccountsToBabylon: MigrateOlympiaHardwareAccountsToBabylon

public var findAlreadyImportedIfAny: FindAlreadyImportedIfAny
}

extension ImportLegacyWalletClient {
public typealias ParseHeaderFromQRCode = @Sendable (String) throws -> OlympiaExportHeader
public typealias ParseHeaderFromQRCode = @Sendable (NonEmptyString) throws -> Olympia.Export.Payload.Header

public typealias ParseLegacyWalletFromQRCodes = @Sendable (_ qrCodes: OrderedSet<String>) throws -> ScannedParsedOlympiaWalletToMigrate
public typealias ParseLegacyWalletFromQRCodes = @Sendable (_ qrCodes: NonEmpty<OrderedSet<NonEmptyString>>) throws -> ScannedParsedOlympiaWalletToMigrate

public typealias MigrateOlympiaSoftwareAccountsToBabylon = @Sendable (MigrateOlympiaSoftwareAccountsToBabylonRequest) async throws -> MigratedSoftwareAccounts

public typealias MigrateOlympiaHardwareAccountsToBabylon = @Sendable (MigrateOlympiaHardwareAccountsToBabylonRequest) async throws -> MigratedHardwareAccounts

public typealias FindAlreadyImportedIfAny = @Sendable (NonEmpty<OrderedSet<OlympiaAccountToMigrate>>) async -> Set<OlympiaAccountToMigrate.ID>
}
Loading

0 comments on commit f335c43

Please sign in to comment.