Cleanup and refactor

lightweight wallet wrapper

getting rig of initialiser, import Security

SDK integrated, wallet's environment prepared

PR comments fixes

PR comments resolved

wallet storage as a dependency

PR comments resolved

Update secant/Util/WalletStorageInteractor.swift

agreed

Update secant/Util/WalletStorageInteractor.swift

order of parameters fixed

build fixed

tab resolved

buildable again

unit tests TODO added

Co-Authored-By: Adam <adam@olemae.com>
This commit is contained in:
Lukas Korba 2022-03-28 13:02:17 +02:00
parent fd7109d1f0
commit 549a5c0806
8 changed files with 484 additions and 254 deletions

View File

@ -15,7 +15,6 @@
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */; };
0D2ACE8026C2C67100D62E3C /* Zboto.otf in Resources */ = {isa = PBXBuildFile; fileRef = 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */; };
0D354A0926D5A9D000315F45 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0626D5A9D000315F45 /* Services.swift */; };
0D354A0A26D5A9D000315F45 /* KeyStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0726D5A9D000315F45 /* KeyStoring.swift */; };
0D35CC46277A36E00074316A /* ScrollableWhenScaled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */; };
0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D3D04072728B3440032ABC1 /* RecoveryPhraseDisplayView.swift */; };
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */; };
@ -80,7 +79,7 @@
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; };
9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9E2AC0FE27D8EC120042AA47 /* MnemonicSwift */; };
9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */; };
9E2AC10327DA28200042AA47 /* RecoveryPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10227DA28200042AA47 /* RecoveryPhraseStorage.swift */; };
9E2AC10327DA28200042AA47 /* WalletStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10227DA28200042AA47 /* WalletStorage.swift */; };
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99827CF704D00649636 /* ImportWalletStore.swift */; };
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */; };
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99B27CF704D00649636 /* ImportWalletView.swift */; };
@ -89,10 +88,12 @@
9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */; };
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */; };
9EBEF87A27CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */; };
9EF8135C27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift */; };
9EF8135C27ECC25E0075AF48 /* WalletStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */; };
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */; };
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135F27F043CC0075AF48 /* AppDelegate.swift */; };
9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8139B27F47AED0075AF48 /* InitializationState.swift */; };
9EF8139127F191BF0075AF48 /* WalletStorageInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8139027F191BF0075AF48 /* WalletStorageInteractor.swift */; };
9EF8139827F1FAEC0075AF48 /* ZcashLightClientKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9EF8139727F1FAEC0075AF48 /* ZcashLightClientKit */; };
F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9322DBF273B555C00C105B5 /* NavigationLinks.swift */; };
F93673D62742CB840099C6AF /* Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93673D52742CB840099C6AF /* Previews.swift */; };
F93874F0273C4DE200F0E875 /* HomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874ED273C4DE200F0E875 /* HomeStore.swift */; };
@ -148,7 +149,6 @@
0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayReducerTests.swift; sourceTree = "<group>"; };
0D2ACE7F26C2C67100D62E3C /* Zboto.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Zboto.otf; sourceTree = "<group>"; };
0D354A0626D5A9D000315F45 /* Services.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
0D354A0726D5A9D000315F45 /* KeyStoring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyStoring.swift; sourceTree = "<group>"; };
0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableWhenScaled.swift; sourceTree = "<group>"; };
0D3D04072728B3440032ABC1 /* RecoveryPhraseDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayView.swift; sourceTree = "<group>"; };
0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayStore.swift; sourceTree = "<group>"; };
@ -217,7 +217,7 @@
66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = "<group>"; };
66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardButtonStyle.swift; sourceTree = "<group>"; };
9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicSeedPhraseProvider.swift; sourceTree = "<group>"; };
9E2AC10227DA28200042AA47 /* RecoveryPhraseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseStorage.swift; sourceTree = "<group>"; };
9E2AC10227DA28200042AA47 /* WalletStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletStorage.swift; sourceTree = "<group>"; };
9E2AC10527DA34610042AA47 /* RecoveryPhraseStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseStorageTests.swift; sourceTree = "<group>"; };
9E2DF99827CF704D00649636 /* ImportWalletStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletStore.swift; sourceTree = "<group>"; };
9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportSeedEditor.swift; sourceTree = "<group>"; };
@ -227,10 +227,11 @@
9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantButtonStyles.swift; sourceTree = "<group>"; };
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorage.swift; sourceTree = "<group>"; };
9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = "<group>"; };
9EF8135A27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseStorageTests.swift; sourceTree = "<group>"; };
9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletStorageTests.swift; sourceTree = "<group>"; };
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = "<group>"; };
9EF8135F27F043CC0075AF48 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
9EF8139B27F47AED0075AF48 /* InitializationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializationState.swift; sourceTree = "<group>"; };
9EF8139027F191BF0075AF48 /* WalletStorageInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletStorageInteractor.swift; sourceTree = "<group>"; };
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinks.swift; sourceTree = "<group>"; };
F93673D52742CB840099C6AF /* Previews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Previews.swift; sourceTree = "<group>"; };
F93874ED273C4DE200F0E875 /* HomeStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStore.swift; sourceTree = "<group>"; };
@ -265,6 +266,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9EF8139827F1FAEC0075AF48 /* ZcashLightClientKit in Frameworks */,
9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */,
6654C73A2715A38000901167 /* ComposableArchitecture in Frameworks */,
);
@ -306,7 +308,6 @@
0D170A7426BC9B7500EB6A46 /* MockedDependencies */ = {
isa = PBXGroup;
children = (
0D354A0726D5A9D000315F45 /* KeyStoring.swift */,
0D354A0626D5A9D000315F45 /* Services.swift */,
);
path = MockedDependencies;
@ -510,9 +511,10 @@
F93673D52742CB840099C6AF /* Previews.swift */,
0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */,
9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */,
9E2AC10227DA28200042AA47 /* RecoveryPhraseStorage.swift */,
9E2AC10227DA28200042AA47 /* WalletStorage.swift */,
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */,
9EF8139B27F47AED0075AF48 /* InitializationState.swift */,
9EF8139027F191BF0075AF48 /* WalletStorageInteractor.swift */,
);
path = Util;
sourceTree = "<group>";
@ -703,7 +705,7 @@
9EF8135927ECC25E0075AF48 /* Util */ = {
isa = PBXGroup;
children = (
9EF8135A27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift */,
9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */,
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */,
);
path = Util;
@ -888,6 +890,7 @@
packageProductDependencies = (
6654C7392715A38000901167 /* ComposableArchitecture */,
9E2AC0FE27D8EC120042AA47 /* MnemonicSwift */,
9EF8139727F1FAEC0075AF48 /* ZcashLightClientKit */,
);
productName = secant;
productReference = 0D4E7A0526B364170058B01E /* secant-testnet.app */;
@ -963,6 +966,7 @@
packageReferences = (
6654C7382715A38000901167 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */,
9E2AC0FD27D8EC120042AA47 /* XCRemoteSwiftPackageReference "MnemonicSwift" */,
9EF8139627F1FAEC0075AF48 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */,
);
productRefGroup = 0D4E7A0626B364170058B01E /* Products */;
projectDirPath = "";
@ -1126,7 +1130,7 @@
663FAB9C271D874D00E495F8 /* ActiveButton.swift in Sources */,
F9C165C02740403600592F76 /* ApproveView.swift in Sources */,
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
9E2AC10327DA28200042AA47 /* RecoveryPhraseStorage.swift in Sources */,
9E2AC10327DA28200042AA47 /* WalletStorage.swift in Sources */,
F9971A6B27680E1000A2DB75 /* WalletInfo.swift in Sources */,
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */,
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */,
@ -1139,9 +1143,9 @@
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */,
F9971A6627680DFE00A2DB75 /* SettingsView.swift in Sources */,
F96B41EB273B50520021B49A /* Strings.swift in Sources */,
0D354A0A26D5A9D000315F45 /* KeyStoring.swift in Sources */,
F9971A5427680DD000A2DB75 /* ProfileView.swift in Sources */,
F9971A6027680DF600A2DB75 /* Scan.swift in Sources */,
9EF8139127F191BF0075AF48 /* WalletStorageInteractor.swift in Sources */,
0DFE93E1272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift in Sources */,
F9C165CB2741AB5D00592F76 /* SendView.swift in Sources */,
0D0781C4278750E30083ACD7 /* WelcomeView.swift in Sources */,
@ -1163,7 +1167,7 @@
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */,
0DFE93E6272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift in Sources */,
9EF8135C27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift in Sources */,
9EF8135C27ECC25E0075AF48 /* WalletStorageTests.swift in Sources */,
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1494,6 +1498,14 @@
minimumVersion = 2.0.0;
};
};
9EF8139627F1FAEC0075AF48 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/zcash/ZcashLightClientKit";
requirement = {
kind = revision;
revision = f3150072f5cafd53fa064b7cfc80ef3a84460fb2;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -1507,6 +1519,11 @@
package = 9E2AC0FD27D8EC120042AA47 /* XCRemoteSwiftPackageReference "MnemonicSwift" */;
productName = MnemonicSwift;
};
9EF8139727F1FAEC0075AF48 /* ZcashLightClientKit */ = {
isa = XCSwiftPackageProductDependency;
package = 9EF8139627F1FAEC0075AF48 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */;
productName = ZcashLightClientKit;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 0D4E79FD26B364170058B01E /* Project object */;

View File

@ -10,6 +10,15 @@
"version": "0.5.3"
}
},
{
"package": "grpc-swift",
"repositoryURL": "https://github.com/grpc/grpc-swift.git",
"state": {
"branch": null,
"revision": "593fe0fe931f7e838969243cd137be48e8055b1d",
"version": "1.7.3"
}
},
{
"package": "MnemonicSwift",
"repositoryURL": "https://github.com/zcash-hackworks/MnemonicSwift",
@ -19,6 +28,15 @@
"version": "2.2.3"
}
},
{
"package": "SQLite.swift",
"repositoryURL": "https://github.com/stephencelis/SQLite.swift.git",
"state": {
"branch": null,
"revision": "4d543d811ee644fa4cc4bfa0be996b4dd6ba0f54",
"version": "0.13.3"
}
},
{
"package": "swift-case-paths",
"repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
@ -73,6 +91,69 @@
"version": "0.3.1"
}
},
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version": "1.4.2"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "d6e3762e0a5f7ede652559f53623baf11006e17c",
"version": "2.39.0"
}
},
{
"package": "swift-nio-extras",
"repositoryURL": "https://github.com/apple/swift-nio-extras.git",
"state": {
"branch": null,
"revision": "f73ca5ee9c6806800243f1ac415fcf82de9a4c91",
"version": "1.10.2"
}
},
{
"package": "swift-nio-http2",
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
"state": {
"branch": null,
"revision": "50c25c132b140e62b45e90b5a76f13ded02c8a46",
"version": "1.20.1"
}
},
{
"package": "swift-nio-ssl",
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
"state": {
"branch": null,
"revision": "b5260a31c2a72a89fa684f5efb3054d8725a2316",
"version": "2.18.0"
}
},
{
"package": "swift-nio-transport-services",
"repositoryURL": "https://github.com/apple/swift-nio-transport-services.git",
"state": {
"branch": null,
"revision": "8ab824b140d0ebcd87e9149266ddc353e3705a3e",
"version": "1.11.4"
}
},
{
"package": "SwiftProtobuf",
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version": "1.19.0"
}
},
{
"package": "xctest-dynamic-overlay",
"repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
@ -81,6 +162,24 @@
"revision": "50a70a9d3583fe228ce672e8923010c8df2deddd",
"version": "0.2.1"
}
},
{
"package": "libzcashlc",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi.git",
"state": {
"branch": "main",
"revision": "8d4cff1ac9afccd7d7b6c4317dfe5e30c5c5bb42",
"version": null
}
},
{
"package": "ZcashLightClientKit",
"repositoryURL": "https://github.com/zcash/ZcashLightClientKit",
"state": {
"branch": null,
"revision": "f3150072f5cafd53fa064b7cfc80ef3a84460fb2",
"version": null
}
}
]
},

View File

@ -34,20 +34,20 @@ enum AppAction: Equatable {
struct AppEnvironment {
let scheduler: AnySchedulerOf<DispatchQueue>
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let walletStorage: RecoveryPhraseStorage
let walletStorage: WalletStorageInteractor
}
extension AppEnvironment {
static let live = AppEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .live,
walletStorage: RecoveryPhraseStorage()
walletStorage: .live(walletStorage: WalletStorage())
)
static let mock = AppEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .mock,
walletStorage: RecoveryPhraseStorage()
walletStorage: .live(walletStorage: WalletStorage())
)
}
@ -78,7 +78,7 @@ extension AppReducer {
// TODO: - Get birthday from the integrated SDK, issue 228 (https://github.com/zcash/secant-ios-wallet/issues/228)
let birthday = BlockHeight(12345678)
try environment.walletStorage.importRecoveryPhrase(bip39: randomPhrase, birthday: birthday)
try environment.walletStorage.importWallet(randomPhrase, birthday, .english, false)
} catch {
// TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States
return .none
@ -94,8 +94,6 @@ extension AppReducer {
case .checkWalletInitialization:
// TODO: Create a dependency to handle database files for the SDK, issue #220 (https://github.com/zcash/secant-ios-wallet/issues/220)
let fileManager = FileManager()
// TODO: use updated dependency from PR #217 (https://github.com/zcash/secant-ios-wallet/pull/217)
let keysPresent = environment.walletStorage.areKeysPresent()
do {
// TODO: use database URL from the same issue #220
@ -103,12 +101,11 @@ extension AppReducer {
let dataDatabaseURL = documentsURL.appendingPathComponent("ZcashSDK.defaultDataDbName", isDirectory: false)
let attributes = try fileManager.attributesOfItem(atPath: dataDatabaseURL.path)
let databaseFilesPresent = attributes.isEmpty
let keysPresent = try environment.walletStorage.areKeysPresent()
switch (keysPresent, databaseFilesPresent) {
case (false, false):
return Effect(value: .updateRoute(.onboarding))
.delay(for: 3, scheduler: environment.scheduler)
.eraseToEffect()
state.appInitializationState = .uninitialized
case (false, true):
state.appInitializationState = .keysMissing
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
@ -116,11 +113,18 @@ extension AppReducer {
return Effect(value: .initializeApp)
}
} catch CocoaError.fileNoSuchFile, CocoaError.fileReadNoSuchFile {
state.appInitializationState = keysPresent ? .filesMissing : .uninitialized
state.appInitializationState = .filesMissing
} catch {
state.appInitializationState = .failed
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
}
if state.appInitializationState == .uninitialized || state.appInitializationState == .filesMissing {
return Effect(value: .updateRoute(.onboarding))
.delay(for: 3, scheduler: environment.scheduler)
.eraseToEffect()
}
return .none
/// Stored wallet is present, database files may or may not be present, trying to initialize app state variables and environments.

View File

@ -1,54 +0,0 @@
//
// KeyStoring.swift
// secant
//
// Created by Francisco Gindre on 8/6/21.
//
import Foundation
import MnemonicSwift
/// Representation of the wallet stored in the persistent storage (typically keychain, handled by `RecoveryPhraseStorage`).
struct StoredWallet: Codable, Equatable {
var birthday: BlockHeight?
let language: MnemonicLanguageType
let seedPhrase: String
let version: Int
}
protocol KeyStoring {
/**
Store recovery phrase and optionally even birthday to the secured and persistent storage.
This function creates an instance of `StoredWallet` and automatically handles versioning of the stored data.
*/
func importRecoveryPhrase(bip39 phrase: String, birthday: BlockHeight?, language: MnemonicLanguageType) throws
/**
Load the representation of the wallet from the persistent and secured storage.
*/
func exportWallet() throws -> StoredWallet
/**
Check if the wallet representation `StoredWallet` is present in the persistent storage.
*/
func areKeysPresent() -> Bool
/**
Update the birthday in the securely stored wallet.
*/
func updateBirthday(_ height: BlockHeight) throws
/**
Use carefully: deletes the stored wallet.
There's no fate but what we make for ourselves - Sarah Connor.
*/
func nukeWallet()
}
enum KeyStoringError: Error {
case alreadyImported
case uninitializedWallet
case storageError(Error)
case unsupportedVersion(Int)
case unsupportedLanguage(MnemonicLanguageType)
}

View File

@ -10,7 +10,7 @@ import Foundation
protocol Services {
var networkProvider: ZcashNetworkProvider { get }
var seedHandler: MnemonicSeedPhraseProvider { get }
var keyStorage: KeyStoring { get }
var walletStorage: WalletStorageInteractor { get }
}
protocol ZcashNetworkProvider {

View File

@ -1,5 +1,5 @@
//
// RecoveryPhraseStorage.swift
// WalletStorage.swift
// secant-testnet
//
// Created by Lukáš Korba on 03/10/2022.
@ -7,12 +7,12 @@
import Foundation
import MnemonicSwift
import Security
/// Zcash implementation of the keychain that is not universal but designed to deliver functionality needed by the wallet itself.
/// All the APIs should be thread safe according to official doc:
/// https://developer.apple.com/documentation/security/certificate_key_and_trust_services/working_with_concurrency?language=objc
// swiftlint:disable convenience_type
final class RecoveryPhraseStorage {
struct WalletStorage {
enum Constants {
static let zcashStoredWallet = "zcashStoredWallet"
/// Versioning of the stored data
@ -25,13 +25,112 @@ final class RecoveryPhraseStorage {
case encoding
case noDataFound
case unknown(OSStatus)
}
enum WalletStorageError: Error {
case alreadyImported
case uninitializedWallet
case storageError(Error)
case unsupportedVersion(Int)
case unsupportedLanguage(MnemonicLanguageType)
}
func importWallet(
bip39 phrase: String,
birthday: BlockHeight?,
language: MnemonicLanguageType = .english,
hasUserPassedPhraseBackupTest: Bool = false
) throws {
// Future-proof of the bundle to potentialy avoid migration. We enforce english mnemonic.
guard language == .english else {
throw WalletStorageError.unsupportedLanguage(language)
}
let wallet = StoredWallet(
language: language,
seedPhrase: phrase,
version: Constants.zcashKeychainVersion,
birthday: birthday,
hasUserPassedPhraseBackupTest: hasUserPassedPhraseBackupTest
)
do {
guard let data = try encode(object: wallet) else {
throw KeychainError.encoding
}
try setData(data, forKey: Constants.zcashStoredWallet)
} catch KeychainError.duplicate {
throw WalletStorageError.alreadyImported
} catch {
throw WalletStorageError.storageError(error)
}
}
// MARK: - Codable helpers
func exportWallet() throws -> StoredWallet {
guard let data = data(forKey: Constants.zcashStoredWallet) else {
throw WalletStorageError.uninitializedWallet
}
guard let wallet = try decode(json: data, as: StoredWallet.self) else {
throw WalletStorageError.uninitializedWallet
}
guard wallet.version == Constants.zcashKeychainVersion else {
throw WalletStorageError.unsupportedVersion(wallet.version)
}
return wallet
}
static func decode<T: Decodable>(json: Data, as clazz: T.Type) throws -> T? {
func areKeysPresent() throws -> Bool {
do {
_ = try exportWallet()
} catch {
// TODO: - report & log error.localizedDescription [Issue #219, https://github.com/zcash/secant-ios-wallet/issues/219]
throw error
}
return true
}
func updateBirthday(_ height: BlockHeight) throws {
do {
var wallet = try exportWallet()
wallet.birthday = height
guard let data = try encode(object: wallet) else {
throw KeychainError.encoding
}
try updateData(data, forKey: Constants.zcashStoredWallet)
} catch {
throw error
}
}
func markUserPassedPhraseBackupTest() throws {
do {
var wallet = try exportWallet()
wallet.hasUserPassedPhraseBackupTest = true
guard let data = try encode(object: wallet) else {
throw KeychainError.encoding
}
try updateData(data, forKey: WalletStorage.Constants.zcashStoredWallet)
} catch {
throw error
}
}
func nukeWallet() {
deleteData(forKey: Constants.zcashStoredWallet)
}
// MARK: - Wallet Storage Codable & Query helpers
func decode<T: Decodable>(json: Data, as clazz: T.Type) throws -> T? {
do {
let decoder = JSONDecoder()
let data = try decoder.decode(T.self, from: json)
@ -41,7 +140,7 @@ final class RecoveryPhraseStorage {
}
}
static func encode<T: Codable>(object: T) throws -> Data? {
func encode<T: Codable>(object: T) throws -> Data? {
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
@ -51,9 +150,7 @@ final class RecoveryPhraseStorage {
}
}
// MARK: - Query Helpers
static func baseQuery(forAccount account: String = "", andKey forKey: String) -> [String: Any] {
func baseQuery(forAccount account: String = "", andKey forKey: String) -> [String: Any] {
let query:[ String: AnyObject ] = [
/// Uniquely identify this keychain accessor
kSecAttrService as String: forKey as AnyObject,
@ -69,7 +166,7 @@ final class RecoveryPhraseStorage {
return query
}
static func restoreQuery(forAccount account: String = "", andKey forKey: String) -> [String: Any] {
func restoreQuery(forAccount account: String = "", andKey forKey: String) -> [String: Any] {
var query = baseQuery(forAccount: account, andKey: forKey)
query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecReturnData as String] = kCFBooleanTrue
@ -81,45 +178,15 @@ final class RecoveryPhraseStorage {
}
}
// MARK: - Recovery Phrase Helper Functions
// MARK: - Wallet Storage Helper Functions
private extension RecoveryPhraseStorage {
func setWallet(_ wallet: StoredWallet) throws {
guard let data = try RecoveryPhraseStorage.encode(object: wallet) else {
throw KeychainError.encoding
}
try setData(data, forKey: Constants.zcashStoredWallet)
}
func wallet() throws -> StoredWallet {
guard let data = data(forKey: Constants.zcashStoredWallet) else {
throw KeychainError.noDataFound
}
guard let wallet = try RecoveryPhraseStorage.decode(json: data, as: StoredWallet.self) else {
throw KeychainError.decoding
}
guard wallet.version == Constants.zcashKeychainVersion else {
throw KeychainError.unsupportedVersion(wallet.version)
}
return wallet
}
/// Use carefully: Deletes seed phrase from the keychain!!!
@discardableResult
func deleteWallet() -> Bool {
deleteData(forKey: Constants.zcashStoredWallet)
}
private extension WalletStorage {
/// Restore data for key
func data(
forKey: String,
account: String = ""
) -> Data? {
let query = RecoveryPhraseStorage.restoreQuery(forAccount: account, andKey: forKey)
let query = restoreQuery(forAccount: account, andKey: forKey)
var result: AnyObject?
_ = SecItemCopyMatching(query as CFDictionary, &result)
@ -128,11 +195,12 @@ private extension RecoveryPhraseStorage {
}
/// Use carefully: Deletes data for key
@discardableResult
func deleteData(
forKey: String,
account: String = ""
) -> Bool {
let query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
let query = baseQuery(forAccount: account, andKey: forKey)
let status = SecItemDelete(query as CFDictionary)
@ -145,7 +213,7 @@ private extension RecoveryPhraseStorage {
forKey: String,
account: String = ""
) throws {
var query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
var query = baseQuery(forAccount: account, andKey: forKey)
query[kSecValueData as String] = data as AnyObject
let status = SecItemAdd(query as CFDictionary, nil)
@ -165,7 +233,7 @@ private extension RecoveryPhraseStorage {
forKey: String,
account: String = ""
) throws {
let query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
let query = baseQuery(forAccount: account, andKey: forKey)
let attributes:[ String: AnyObject ] = [
kSecValueData as String: data as AnyObject
@ -182,73 +250,3 @@ private extension RecoveryPhraseStorage {
}
}
}
// MARK: - KeyStoring
extension RecoveryPhraseStorage: KeyStoring {
func importRecoveryPhrase(bip39 phrase: String, birthday: BlockHeight?, language: MnemonicLanguageType = .english) throws {
// Future-proof of the bundle to potentialy avoid migration. We enforce english mnemonic.
guard language == .english else {
throw KeyStoringError.unsupportedLanguage(language)
}
do {
let wallet = StoredWallet(
birthday: birthday,
language: language,
seedPhrase: phrase,
version: Constants.zcashKeychainVersion
)
try setWallet(wallet)
} catch KeychainError.duplicate {
throw KeyStoringError.alreadyImported
} catch {
throw KeyStoringError.storageError(error)
}
}
func exportWallet() throws -> StoredWallet {
do {
return try wallet()
} catch KeychainError.noDataFound, KeychainError.decoding {
throw KeyStoringError.uninitializedWallet
} catch KeychainError.unsupportedVersion(let version) {
throw KeyStoringError.unsupportedVersion(version)
} catch {
throw KeyStoringError.storageError(error)
}
}
func areKeysPresent() -> Bool {
do {
_ = try exportWallet()
} catch KeyStoringError.uninitializedWallet {
return false
} catch {
// TODO: - report & log error.localizedDescription
return false
}
return true
}
func updateBirthday(_ height: BlockHeight) throws {
do {
var wallet = try exportWallet()
wallet.birthday = height
guard let data = try RecoveryPhraseStorage.encode(object: wallet) else {
throw KeychainError.encoding
}
try updateData(data, forKey: Constants.zcashStoredWallet)
} catch {
throw error
}
}
func nukeWallet() {
deleteWallet()
}
}

View File

@ -0,0 +1,125 @@
//
// WalletStorageInteractor.swift
// secant-testnet
//
// Created by Lukáš Korba on 03/25/2022.
//
import Foundation
import MnemonicSwift
/// Representation of the wallet stored in the persistent storage (typically keychain, handled by `WalletStorage`).
struct StoredWallet: Codable, Equatable {
let language: MnemonicLanguageType
let seedPhrase: String
let version: Int
var birthday: BlockHeight?
var hasUserPassedPhraseBackupTest: Bool
}
/// The `WalletStorageInteractor` is a wrapper around the `WalletStorage` type that allows for easy testing.
/// The `WalletStorage` Type is comprised of static functions that take and produce data. In order
/// to easily produce test data, all of these static functions have been wrapped in function
/// properties that live on the `WalletStorageInteractor` type. Because of this, you can instantiate
/// the `WalletStorageInteractor` with your own implementation of these functions for testing purposes,
/// or you can use one of the built in static versions of the `WalletStorageInteractor`.
struct WalletStorageInteractor {
/// Store recovery phrase and optionally even birthday to the secured and persistent storage.
/// This function creates an instance of `StoredWallet` and automatically handles versioning of the stored data.
///
/// - Parameters:
/// - bip39: Mnemonic/Seed phrase from `MnemonicSwift`
/// - birthday: BlockHeight from SDK
/// - language: Mnemonic's language
/// - Throws:
/// - `WalletStorageError.unsupportedLanguage`: when mnemonic's language is anything other than English
/// - `WalletStorageError.alreadyImported` when valid wallet is already in the storage
/// - `WalletStorageError.storageError` when some unrecognized error occured
let importWallet: (String, BlockHeight?, MnemonicLanguageType, Bool) throws -> Void
/// Load the representation of the wallet from the persistent and secured storage.
///
/// - Returns: the representation of the wallet from the persistent and secured storage.
/// - Throws:
/// - `WalletStorageError.uninitializedWallet`: when no wallet's data is found in the keychain.
/// - `WalletStorageError.storageError` when some unrecognized error occured.
/// - `WalletStorageError.unsupportedVersion` when wallet's version stored in the keychain is outdated.
let exportWallet: () throws -> StoredWallet
/// Check if the wallet representation `StoredWallet` is present in the persistent storage.
///
/// - Returns: the information wheather some wallet is stored or is not available
let areKeysPresent: () throws -> Bool
/// Update the birthday in the securely stored wallet.
///
/// - Parameters:
/// - birthday: BlockHeight from SDK
/// - Throws:
/// - `WalletStorage.KeychainError.encoding`: when encoding the wallet's data failed.
/// - `WalletStorageError.storageError` when some unrecognized error occured.
let updateBirthday: (BlockHeight) throws -> Void
/// Update the information that user has passed the recovery phrase backup test.
/// The fuction doesn't take any parameters, default value is the user hasn't passed the test
/// and this fucntion only sets the true = fact user passed.
///
/// - Throws:
/// - `WalletStorage.KeychainError.encoding`: when encoding the wallet's data failed.
/// - `WalletStorageError.storageError` when some unrecognized error occured.
let markUserPassedPhraseBackupTest: () throws -> Void
/// Use carefully: deletes the stored wallet.
/// There's no fate but what we make for ourselves - Sarah Connor.
let nukeWallet: () -> Void
}
extension WalletStorageInteractor {
public static func live(walletStorage: WalletStorage = WalletStorage()) -> Self {
Self(
importWallet: { bip39, birthday, language, hasUserPassedPhraseBackupTest in
try walletStorage.importWallet(
bip39: bip39,
birthday: birthday,
language: language,
hasUserPassedPhraseBackupTest: hasUserPassedPhraseBackupTest
)
},
exportWallet: {
try walletStorage.exportWallet()
},
areKeysPresent: {
try walletStorage.areKeysPresent()
},
updateBirthday: { birthday in
try walletStorage.updateBirthday(birthday)
},
markUserPassedPhraseBackupTest: {
try walletStorage.markUserPassedPhraseBackupTest()
},
nukeWallet: {
walletStorage.nukeWallet()
}
)
}
public static let throwing = WalletStorageInteractor(
importWallet: { _, _, _, _ in
throw WalletStorage.WalletStorageError.alreadyImported
},
exportWallet: {
throw WalletStorage.WalletStorageError.uninitializedWallet
},
areKeysPresent: {
throw WalletStorage.WalletStorageError.uninitializedWallet
},
updateBirthday: { _ in
throw WalletStorage.KeychainError.encoding
},
markUserPassedPhraseBackupTest: {
throw WalletStorage.KeychainError.encoding
},
nukeWallet: { }
)
}

View File

@ -1,5 +1,5 @@
//
// RecoveryPhraseStorageTests.swift
// WalletStorageTests.swift
// secantTests
//
// Created by Lukáš Korba on 10.03.2022.
@ -9,7 +9,7 @@ import XCTest
import MnemonicSwift
@testable import secant_testnet
extension KeyStoringError {
extension WalletStorage.WalletStorageError {
var debugValue: String {
switch self {
case .alreadyImported: return "alreadyImported"
@ -21,31 +21,25 @@ extension KeyStoringError {
}
}
class RecoveryPhraseStorageTests: XCTestCase {
class WalletStorageTests: XCTestCase {
let birthday = BlockHeight(12345678)
let seedPhrase = "one two three"
let language = MnemonicLanguageType.english
var storage: RecoveryPhraseStorage?
var storage = WalletStorage()
override func setUp() {
super.setUp()
storage = RecoveryPhraseStorage()
deleteData(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
deleteData(forKey: WalletStorage.Constants.zcashStoredWallet)
}
override func tearDown() {
super.tearDown()
storage = nil
}
func testWalletStoredSucessfuly() throws {
func testWalletStoredSuccessfuly() throws {
do {
try storage?.importRecoveryPhrase(bip39: seedPhrase, birthday: birthday)
guard let data = data(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet) else {
try storage.importWallet(bip39: seedPhrase, birthday: birthday)
guard let data = data(forKey: WalletStorage.Constants.zcashStoredWallet) else {
return XCTFail("Keychain: no data found for key: `zcashStoredWallet`.")
}
guard let walletReceived = try RecoveryPhraseStorage.decode(json: data, as: StoredWallet.self) else {
guard let walletReceived = try storage.decode(json: data, as: StoredWallet.self) else {
return XCTFail("Keychain: `walletReceived` can't be decoded.")
}
@ -58,20 +52,20 @@ class RecoveryPhraseStorageTests: XCTestCase {
func testWalletDuplicate() throws {
do {
try storage?.importRecoveryPhrase(bip39: seedPhrase, birthday: birthday)
try storage?.importRecoveryPhrase(bip39: seedPhrase, birthday: birthday)
try storage.importWallet(bip39: seedPhrase, birthday: birthday)
try storage.importWallet(bip39: seedPhrase, birthday: birthday)
XCTFail("Keychain: `testRecoveryPhraseDuplicate` is expected to throw a `duplicate` error but passed instead.")
} catch {
guard let error = error as? KeyStoringError else {
XCTFail("Keychain: the error is expected to be KeyStoringError but it's \(error).")
guard let error = error as? WalletStorage.WalletStorageError else {
XCTFail("Keychain: the error is expected to be WalletStorageError but it's \(error).")
return
}
XCTAssertEqual(
error.debugValue,
KeyStoringError.alreadyImported.debugValue,
WalletStorage.WalletStorageError.alreadyImported.debugValue,
"Keychain: error must be .alreadyImported but it's \(error)."
)
}
@ -79,40 +73,45 @@ class RecoveryPhraseStorageTests: XCTestCase {
func testUninitializedWallet() throws {
do {
_ = try storage?.exportWallet()
_ = try storage.exportWallet()
XCTFail("Keychain: `testUninitializedWallet` should fail but received some wallet.")
} catch {
guard let error = error as? KeyStoringError else {
return XCTFail("Keychain: the error is expected to be KeyStoringError but it's \(error).")
guard let error = error as? WalletStorage.WalletStorageError else {
return XCTFail("Keychain: the error is expected to be WalletStorageError but it's \(error).")
}
XCTAssertEqual(error.debugValue, KeyStoringError.uninitializedWallet.debugValue, "Keychain: error must be .uninitializedWallet")
XCTAssertEqual(
error.debugValue,
WalletStorage.WalletStorageError.uninitializedWallet.debugValue,
"Keychain: error must be .uninitializedWallet"
)
}
}
func testDeleteWallet() throws {
do {
let wallet = StoredWallet(
birthday: birthday,
language: language,
seedPhrase: seedPhrase,
version: RecoveryPhraseStorage.Constants.zcashKeychainVersion
version: WalletStorage.Constants.zcashKeychainVersion,
birthday: birthday,
hasUserPassedPhraseBackupTest: false
)
guard let walletData = try RecoveryPhraseStorage.encode(object: wallet) else {
guard let walletData = try storage.encode(object: wallet) else {
return XCTFail("`testDeleteWallet` encoding `walletData` failed.")
}
do {
try setData(walletData, forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
try setData(walletData, forKey: WalletStorage.Constants.zcashStoredWallet)
} catch {
XCTFail("`testDeleteWallet` storing `walletData` failed.")
}
storage?.nukeWallet()
storage.nukeWallet()
let data = data(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
let data = data(forKey: WalletStorage.Constants.zcashStoredWallet)
XCTAssertEqual(data, nil, "Keychain: keychain is expected to not find anything for key `zcashStoredWallet` but received some data.")
}
@ -120,29 +119,30 @@ class RecoveryPhraseStorageTests: XCTestCase {
func testUpdateBirthdayOverNil() throws {
let wallet = StoredWallet(
birthday: nil,
language: language,
seedPhrase: seedPhrase,
version: RecoveryPhraseStorage.Constants.zcashKeychainVersion
version: WalletStorage.Constants.zcashKeychainVersion,
birthday: nil,
hasUserPassedPhraseBackupTest: false
)
guard let walletData = try RecoveryPhraseStorage.encode(object: wallet) else {
guard let walletData = try storage.encode(object: wallet) else {
return XCTFail("`testUpdateBirthdayOverNil` encoding `walletData` failed.")
}
do {
try setData(walletData, forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
try setData(walletData, forKey: WalletStorage.Constants.zcashStoredWallet)
} catch {
XCTFail("`testUpdateBirthdayOverNil` storing `walletData` failed.")
}
do {
try storage?.updateBirthday(birthday)
guard let data = data(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet) else {
try storage.updateBirthday(birthday)
guard let data = data(forKey: WalletStorage.Constants.zcashStoredWallet) else {
return XCTFail("Keychain: no data found for key: `zcashStoredWallet`.")
}
guard let walletReceived = try RecoveryPhraseStorage.decode(json: data, as: StoredWallet.self) else {
guard let walletReceived = try storage.decode(json: data, as: StoredWallet.self) else {
return XCTFail("Keychain: `walletReceived` can't be decoded.")
}
@ -155,30 +155,31 @@ class RecoveryPhraseStorageTests: XCTestCase {
func testUpdateBirthdayOverSomeBirthday() throws {
let wallet = StoredWallet(
birthday: birthday,
language: language,
seedPhrase: seedPhrase,
version: RecoveryPhraseStorage.Constants.zcashKeychainVersion
version: WalletStorage.Constants.zcashKeychainVersion,
birthday: birthday,
hasUserPassedPhraseBackupTest: false
)
let newBirthday = BlockHeight(87654321)
guard let walletData = try RecoveryPhraseStorage.encode(object: wallet) else {
guard let walletData = try storage.encode(object: wallet) else {
return XCTFail("`testUpdateBirthdayOverSomeBirthday` encoding `walletData` failed.")
}
do {
try setData(walletData, forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
try setData(walletData, forKey: WalletStorage.Constants.zcashStoredWallet)
} catch {
XCTFail("`testUpdateBirthdayOverSomeBirthday` storing `walletData` failed.")
}
do {
try storage?.updateBirthday(newBirthday)
guard let data = data(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet) else {
try storage.updateBirthday(newBirthday)
guard let data = data(forKey: WalletStorage.Constants.zcashStoredWallet) else {
return XCTFail("Keychain: no data found for key: `zcashStoredWallet`.")
}
guard let walletReceived = try RecoveryPhraseStorage.decode(json: data, as: StoredWallet.self) else {
guard let walletReceived = try storage.decode(json: data, as: StoredWallet.self) else {
return XCTFail("Keychain: `walletReceived` can't be decoded.")
}
@ -189,34 +190,70 @@ class RecoveryPhraseStorageTests: XCTestCase {
}
}
func testMarkUserPassedPhraseBackupTest() throws {
let wallet = StoredWallet(
language: language,
seedPhrase: seedPhrase,
version: WalletStorage.Constants.zcashKeychainVersion,
birthday: birthday,
hasUserPassedPhraseBackupTest: false
)
guard let walletData = try storage.encode(object: wallet) else {
return XCTFail("`testMarkUserPassedPhraseBackupTest` encoding `walletData` failed.")
}
do {
try setData(walletData, forKey: WalletStorage.Constants.zcashStoredWallet)
} catch {
XCTFail("`testMarkUserPassedPhraseBackupTest` storing `walletData` failed.")
}
do {
try storage.markUserPassedPhraseBackupTest()
guard let data = data(forKey: WalletStorage.Constants.zcashStoredWallet) else {
return XCTFail("Keychain: no data found for key: `zcashStoredWallet`.")
}
guard let walletReceived = try storage.decode(json: data, as: StoredWallet.self) else {
return XCTFail("Keychain: `walletReceived` can't be decoded.")
}
XCTAssertTrue(walletReceived.hasUserPassedPhraseBackupTest, "Keychain: `hasUserPassedPhraseBackupTest` must be set to true.")
XCTAssertEqual(seedPhrase, walletReceived.seedPhrase, "Keychain: stored seed phrase and retrieved one must be the same.")
} catch let err {
XCTFail("Keychain: no error is expected for `testMarkUserPassedPhraseBackupTest` but received. \(err)")
}
}
func testUnsupportedVersion() throws {
let wallet = StoredWallet(
birthday: birthday,
language: language,
seedPhrase: seedPhrase,
/// older version
version: RecoveryPhraseStorage.Constants.zcashKeychainVersion - 1
version: WalletStorage.Constants.zcashKeychainVersion - 1,
birthday: birthday,
hasUserPassedPhraseBackupTest: false
)
guard let walletData = try RecoveryPhraseStorage.encode(object: wallet) else {
guard let walletData = try storage.encode(object: wallet) else {
return XCTFail("`testUnsupportedVersion` encoding `walletData` failed.")
}
do {
try setData(walletData, forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
try setData(walletData, forKey: WalletStorage.Constants.zcashStoredWallet)
} catch {
XCTFail("`testUnsupportedVersion` storing `walletData` failed.")
}
do {
_ = try storage?.exportWallet()
_ = try storage.exportWallet()
XCTFail("Keychain: `testUnsupportedVersion` should fail but received some wallet with correct version.")
} catch KeyStoringError.unsupportedVersion(let version) {
} catch WalletStorage.WalletStorageError.unsupportedVersion(let version) {
XCTAssertEqual(
version + 1,
RecoveryPhraseStorage.Constants.zcashKeychainVersion,
"Keychain: version should be \(RecoveryPhraseStorage.Constants.zcashKeychainVersion) but stored version is \(version)"
WalletStorage.Constants.zcashKeychainVersion,
"Keychain: version should be \(WalletStorage.Constants.zcashKeychainVersion) but stored version is \(version)"
)
} catch {
XCTFail("Keychain: `testUnsupportedVersion` should fail with `unsupportedVersion` error but threw \(error).")
@ -225,10 +262,10 @@ class RecoveryPhraseStorageTests: XCTestCase {
func testUnsupportedLanguage() throws {
do {
try storage?.importRecoveryPhrase(bip39: seedPhrase, birthday: birthday, language: .chinese)
try storage.importWallet(bip39: seedPhrase, birthday: birthday, language: .chinese)
XCTFail("Keychain: `testUnsupportedLanguage` should fail but imported chinese language.")
} catch KeyStoringError.unsupportedLanguage(let languageToStore) {
} catch WalletStorage.WalletStorageError.unsupportedLanguage(let languageToStore) {
XCTAssertEqual(
MnemonicLanguageType.chinese,
languageToStore,
@ -242,16 +279,17 @@ class RecoveryPhraseStorageTests: XCTestCase {
// MARK: - Misc
/// The followings methods are here purposely to not rely on `RecoveryPhraseStorage` in order to test functionality of JUST ONE method at a time
private extension RecoveryPhraseStorageTests {
/// The followings methods are here purposely to not rely on `WalletStorage` in order to test functionality of JUST ONE method at a time
private extension WalletStorageTests {
private func setData(
account: String = "",
_ data: Data,
forKey: String
) throws {
var query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
var query = storage.baseQuery(forAccount: account, andKey: forKey)
query[kSecValueData as String] = data as AnyObject
// TODO: - Mock the Keychain and write unit tests, issue 231 (https://github.com/zcash/secant-ios-wallet/issues/231)
SecItemAdd(query as CFDictionary, nil)
}
@ -259,9 +297,10 @@ private extension RecoveryPhraseStorageTests {
forKey: String,
account: String = ""
) -> Data? {
let query = RecoveryPhraseStorage.restoreQuery(forAccount: account, andKey: forKey)
let query = storage.restoreQuery(forAccount: account, andKey: forKey)
var result: AnyObject?
// TODO: - Mock the Keychain and write unit tests, issue 231 (https://github.com/zcash/secant-ios-wallet/issues/231)
_ = SecItemCopyMatching(query as CFDictionary, &result)
return result as? Data
@ -272,8 +311,9 @@ private extension RecoveryPhraseStorageTests {
forKey: String,
account: String = ""
) -> Bool {
let query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
let query = storage.baseQuery(forAccount: account, andKey: forKey)
// TODO: - Mock the Keychain and write unit tests, issue 231 (https://github.com/zcash/secant-ios-wallet/issues/231)
let status = SecItemDelete(query as CFDictionary)
return status == noErr
@ -284,12 +324,13 @@ private extension RecoveryPhraseStorageTests {
forKey: String,
account: String = ""
) throws {
let query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
let query = storage.baseQuery(forAccount: account, andKey: forKey)
let attributes:[ String: AnyObject ] = [
kSecValueData as String: data as AnyObject
]
// TODO: - Mock the Keychain and write unit tests, issue 231 (https://github.com/zcash/secant-ios-wallet/issues/231)
_ = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
}
}