Merge pull request #204 from LukasKorba/157_KeyStoring_protocol
Implementation of KeyStoring protocol
This commit is contained in:
commit
7bcb9c5596
|
@ -12,7 +12,6 @@
|
||||||
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D185818272723FF0046B928 /* ColoredChip.swift */; };
|
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D185818272723FF0046B928 /* ColoredChip.swift */; };
|
||||||
0D18581B272728D60046B928 /* PhraseChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D18581A272728D60046B928 /* PhraseChip.swift */; };
|
0D18581B272728D60046B928 /* PhraseChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D18581A272728D60046B928 /* PhraseChip.swift */; };
|
||||||
0D1922F226BDE29300052649 /* ZcashSDKStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */; };
|
0D1922F226BDE29300052649 /* ZcashSDKStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */; };
|
||||||
0D1922F826BDEB3500052649 /* MockServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1922F726BDEB3500052649 /* MockServices.swift */; };
|
|
||||||
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */; };
|
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */; };
|
||||||
0D2ACE8026C2C67100D62E3C /* Zboto.otf in Resources */ = {isa = PBXBuildFile; fileRef = 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */; };
|
0D2ACE8026C2C67100D62E3C /* Zboto.otf in Resources */ = {isa = PBXBuildFile; fileRef = 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */; };
|
||||||
0D354A0926D5A9D000315F45 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0626D5A9D000315F45 /* Services.swift */; };
|
0D354A0926D5A9D000315F45 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0626D5A9D000315F45 /* Services.swift */; };
|
||||||
|
@ -82,6 +81,8 @@
|
||||||
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; };
|
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; };
|
||||||
9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9E2AC0FE27D8EC120042AA47 /* MnemonicSwift */; };
|
9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9E2AC0FE27D8EC120042AA47 /* MnemonicSwift */; };
|
||||||
9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */; };
|
9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */; };
|
||||||
|
9E2AC10327DA28200042AA47 /* RecoveryPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10227DA28200042AA47 /* RecoveryPhraseStorage.swift */; };
|
||||||
|
9E2AC10627DA34610042AA47 /* RecoveryPhraseStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10527DA34610042AA47 /* RecoveryPhraseStorageTests.swift */; };
|
||||||
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99827CF704D00649636 /* ImportWalletStore.swift */; };
|
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99827CF704D00649636 /* ImportWalletStore.swift */; };
|
||||||
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */; };
|
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */; };
|
||||||
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99B27CF704D00649636 /* ImportWalletView.swift */; };
|
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99B27CF704D00649636 /* ImportWalletView.swift */; };
|
||||||
|
@ -141,7 +142,6 @@
|
||||||
0D185818272723FF0046B928 /* ColoredChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredChip.swift; sourceTree = "<group>"; };
|
0D185818272723FF0046B928 /* ColoredChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredChip.swift; sourceTree = "<group>"; };
|
||||||
0D18581A272728D60046B928 /* PhraseChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhraseChip.swift; sourceTree = "<group>"; };
|
0D18581A272728D60046B928 /* PhraseChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhraseChip.swift; sourceTree = "<group>"; };
|
||||||
0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZcashSDKStubs.swift; sourceTree = "<group>"; };
|
0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZcashSDKStubs.swift; sourceTree = "<group>"; };
|
||||||
0D1922F726BDEB3500052649 /* MockServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServices.swift; sourceTree = "<group>"; };
|
|
||||||
0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayReducerTests.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
0D354A0626D5A9D000315F45 /* Services.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
|
||||||
|
@ -215,6 +215,8 @@
|
||||||
66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
||||||
|
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>"; };
|
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>"; };
|
9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportSeedEditor.swift; sourceTree = "<group>"; };
|
||||||
9E2DF99B27CF704D00649636 /* ImportWalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletView.swift; sourceTree = "<group>"; };
|
9E2DF99B27CF704D00649636 /* ImportWalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -315,7 +317,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */,
|
0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */,
|
||||||
0D1922F726BDEB3500052649 /* MockServices.swift */,
|
|
||||||
);
|
);
|
||||||
path = Stubs;
|
path = Stubs;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -410,6 +411,7 @@
|
||||||
0D4E7A1926B364180058B01E /* secantTests */ = {
|
0D4E7A1926B364180058B01E /* secantTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
9E2AC10427DA34450042AA47 /* Util */,
|
||||||
0DFE93E4272CB6D0000FCCA5 /* RecoveryPhraseValidationTests */,
|
0DFE93E4272CB6D0000FCCA5 /* RecoveryPhraseValidationTests */,
|
||||||
0DFE93DD272C6D4B000FCCA5 /* BackupFlowTests */,
|
0DFE93DD272C6D4B000FCCA5 /* BackupFlowTests */,
|
||||||
6654C7422715A48E00901167 /* OnboardingTests */,
|
6654C7422715A48E00901167 /* OnboardingTests */,
|
||||||
|
@ -501,6 +503,7 @@
|
||||||
F93673D52742CB840099C6AF /* Previews.swift */,
|
F93673D52742CB840099C6AF /* Previews.swift */,
|
||||||
0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */,
|
0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */,
|
||||||
9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */,
|
9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */,
|
||||||
|
9E2AC10227DA28200042AA47 /* RecoveryPhraseStorage.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -654,6 +657,14 @@
|
||||||
path = CircularFrame;
|
path = CircularFrame;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
9E2AC10427DA34450042AA47 /* Util */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9E2AC10527DA34610042AA47 /* RecoveryPhraseStorageTests.swift */,
|
||||||
|
);
|
||||||
|
path = Util;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9E2DF99727CF704D00649636 /* ImportWallet */ = {
|
9E2DF99727CF704D00649636 /* ImportWallet */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1079,7 +1090,6 @@
|
||||||
0DA13CA526C1963000E3B610 /* Balance.swift in Sources */,
|
0DA13CA526C1963000E3B610 /* Balance.swift in Sources */,
|
||||||
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */,
|
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */,
|
||||||
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */,
|
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */,
|
||||||
0D1922F826BDEB3500052649 /* MockServices.swift in Sources */,
|
|
||||||
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */,
|
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */,
|
||||||
9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */,
|
9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */,
|
||||||
0D4E7A0B26B364170058B01E /* ContentView.swift in Sources */,
|
0D4E7A0B26B364170058B01E /* ContentView.swift in Sources */,
|
||||||
|
@ -1096,6 +1106,7 @@
|
||||||
663FAB9C271D874D00E495F8 /* ActiveButton.swift in Sources */,
|
663FAB9C271D874D00E495F8 /* ActiveButton.swift in Sources */,
|
||||||
F9C165C02740403600592F76 /* ApproveView.swift in Sources */,
|
F9C165C02740403600592F76 /* ApproveView.swift in Sources */,
|
||||||
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
|
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
|
||||||
|
9E2AC10327DA28200042AA47 /* RecoveryPhraseStorage.swift in Sources */,
|
||||||
F9971A6B27680E1000A2DB75 /* WalletInfo.swift in Sources */,
|
F9971A6B27680E1000A2DB75 /* WalletInfo.swift in Sources */,
|
||||||
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */,
|
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */,
|
||||||
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */,
|
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */,
|
||||||
|
@ -1129,6 +1140,7 @@
|
||||||
0DFE93DF272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift in Sources */,
|
0DFE93DF272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift in Sources */,
|
||||||
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
|
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
|
||||||
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
||||||
|
9E2AC10627DA34610042AA47 /* RecoveryPhraseStorageTests.swift in Sources */,
|
||||||
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */,
|
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */,
|
||||||
0DFE93E6272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift in Sources */,
|
0DFE93E6272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
"repositoryURL": "https://github.com/zcash-hackworks/MnemonicSwift",
|
"repositoryURL": "https://github.com/zcash-hackworks/MnemonicSwift",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "27711179a75a1172d6f04ceb5d86419cf0cba401",
|
"revision": "b10b0b8ee1f297e33ea5b1bc041ced49943b6582",
|
||||||
"version": "2.1.0"
|
"version": "2.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,33 +6,49 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
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 {
|
protocol KeyStoring {
|
||||||
func importBirthday(_ height: BlockHeight) throws
|
/**
|
||||||
func exportBirthday() throws -> BlockHeight
|
Store recovery phrase and optionally even birthday to the secured and persistent storage.
|
||||||
func importPhrase(bip39 phrase: String) throws
|
This function creates an instance of `StoredWallet` and automatically handles versioning of the stored data.
|
||||||
func exportPhrase() throws -> String
|
*/
|
||||||
|
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() throws -> Bool
|
func areKeysPresent() throws -> Bool
|
||||||
/**
|
|
||||||
Use carefully: Deletes the seed phrase from the keychain
|
|
||||||
*/
|
|
||||||
func nukePhrase()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Use carefully: deletes the wallet birthday from the keychain
|
Update the birthday in the securely stored wallet.
|
||||||
*/
|
*/
|
||||||
func nukeBirthday()
|
func updateBirthday(_ height: BlockHeight) throws
|
||||||
|
|
||||||
/**
|
/**
|
||||||
There's no fate but what we make for ourselves - Sarah Connor
|
Use carefully: deletes the stored wallet.
|
||||||
|
There's no fate but what we make for ourselves - Sarah Connor.
|
||||||
*/
|
*/
|
||||||
func nukeWallet()
|
func nukeWallet()
|
||||||
|
|
||||||
var keysPresent: Bool { get }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum KeyStoringError: Error {
|
enum KeyStoringError: Error {
|
||||||
case alreadyImported
|
case alreadyImported
|
||||||
case uninitializedWallet
|
case uninitializedWallet
|
||||||
case storageError(Error)
|
case storageError(Error)
|
||||||
|
case unsupportedVersion(Int)
|
||||||
|
case unsupportedLanguage(MnemonicLanguageType)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,254 @@
|
||||||
|
//
|
||||||
|
// RecoveryPhraseStorage.swift
|
||||||
|
// secant-testnet
|
||||||
|
//
|
||||||
|
// Created by Lukáš Korba on 03/10/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MnemonicSwift
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
enum Constants {
|
||||||
|
static let zcashStoredWallet = "zcashStoredWallet"
|
||||||
|
/// Versioning of the stored data
|
||||||
|
static let zcashKeychainVersion = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum KeychainError: Error, Equatable {
|
||||||
|
case decoding
|
||||||
|
case duplicate
|
||||||
|
case encoding
|
||||||
|
case noDataFound
|
||||||
|
case unknown(OSStatus)
|
||||||
|
case unsupportedVersion(Int)
|
||||||
|
case unsupportedLanguage(MnemonicLanguageType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Codable helpers
|
||||||
|
|
||||||
|
static 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)
|
||||||
|
return data
|
||||||
|
} catch {
|
||||||
|
throw KeychainError.decoding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func encode<T: Codable>(object: T) throws -> Data? {
|
||||||
|
do {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = .prettyPrinted
|
||||||
|
return try encoder.encode(object)
|
||||||
|
} catch {
|
||||||
|
throw KeychainError.encoding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Query Helpers
|
||||||
|
|
||||||
|
static 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,
|
||||||
|
kSecAttrAccount as String: account as AnyObject,
|
||||||
|
kSecClass as String: kSecClassGenericPassword,
|
||||||
|
/// The data in the keychain item can be accessed only while the device is unlocked by the user.
|
||||||
|
/// This is recommended for items that need to be accessible only while the application is in the foreground.
|
||||||
|
/// Items with this attribute do not migrate to a new device.
|
||||||
|
/// Thus, after restoring from a backup of a different device, these items will not be present.
|
||||||
|
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
||||||
|
]
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
static 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
|
||||||
|
query[kSecReturnRef as String] = kCFBooleanFalse
|
||||||
|
query[kSecReturnPersistentRef as String] = kCFBooleanFalse
|
||||||
|
query[kSecReturnAttributes as String] = kCFBooleanFalse
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Recovery Phrase 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore data for key
|
||||||
|
func data(
|
||||||
|
forKey: String,
|
||||||
|
account: String = ""
|
||||||
|
) -> Data? {
|
||||||
|
let query = RecoveryPhraseStorage.restoreQuery(forAccount: account, andKey: forKey)
|
||||||
|
|
||||||
|
var result: AnyObject?
|
||||||
|
_ = SecItemCopyMatching(query as CFDictionary, &result)
|
||||||
|
|
||||||
|
return result as? Data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use carefully: Deletes data for key
|
||||||
|
func deleteData(
|
||||||
|
forKey: String,
|
||||||
|
account: String = ""
|
||||||
|
) -> Bool {
|
||||||
|
let query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
|
||||||
|
|
||||||
|
let status = SecItemDelete(query as CFDictionary)
|
||||||
|
|
||||||
|
return status == noErr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store data for key
|
||||||
|
func setData(
|
||||||
|
_ data: Data,
|
||||||
|
forKey: String,
|
||||||
|
account: String = ""
|
||||||
|
) throws {
|
||||||
|
var query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
|
||||||
|
query[kSecValueData as String] = data as AnyObject
|
||||||
|
|
||||||
|
let status = SecItemAdd(query as CFDictionary, nil)
|
||||||
|
|
||||||
|
guard status != errSecDuplicateItem else {
|
||||||
|
throw KeychainError.duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
guard status == errSecSuccess else {
|
||||||
|
throw KeychainError.unknown(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use carefully: Update data for key
|
||||||
|
func updateData(
|
||||||
|
_ data: Data,
|
||||||
|
forKey: String,
|
||||||
|
account: String = ""
|
||||||
|
) throws {
|
||||||
|
let query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
|
||||||
|
|
||||||
|
let attributes:[ String: AnyObject ] = [
|
||||||
|
kSecValueData as String: data as AnyObject
|
||||||
|
]
|
||||||
|
|
||||||
|
let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
||||||
|
|
||||||
|
guard status != errSecItemNotFound else {
|
||||||
|
throw KeychainError.noDataFound
|
||||||
|
}
|
||||||
|
|
||||||
|
guard status == errSecSuccess else {
|
||||||
|
throw KeychainError.unknown(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() throws -> 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,295 @@
|
||||||
|
//
|
||||||
|
// RecoveryPhraseStorageTests.swift
|
||||||
|
// secantTests
|
||||||
|
//
|
||||||
|
// Created by Lukáš Korba on 10.03.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
import MnemonicSwift
|
||||||
|
@testable import secant_testnet
|
||||||
|
|
||||||
|
extension KeyStoringError {
|
||||||
|
var debugValue: String {
|
||||||
|
switch self {
|
||||||
|
case .alreadyImported: return "alreadyImported"
|
||||||
|
case .uninitializedWallet: return "uninitializedWallet"
|
||||||
|
case .storageError: return "storageError"
|
||||||
|
case .unsupportedVersion: return "unsupportedVersion"
|
||||||
|
case .unsupportedLanguage: return "unsupportedLanguage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoveryPhraseStorageTests: XCTestCase {
|
||||||
|
let birthday = BlockHeight(12345678)
|
||||||
|
let seedPhrase = "one two three"
|
||||||
|
let language = MnemonicLanguageType.english
|
||||||
|
var storage: RecoveryPhraseStorage?
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
storage = RecoveryPhraseStorage()
|
||||||
|
deleteData(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
super.tearDown()
|
||||||
|
storage = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWalletStoredSucessfuly() throws {
|
||||||
|
do {
|
||||||
|
try storage?.importRecoveryPhrase(bip39: seedPhrase, birthday: birthday)
|
||||||
|
guard let data = data(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet) else {
|
||||||
|
return XCTFail("Keychain: no data found for key: `zcashStoredWallet`.")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let walletReceived = try RecoveryPhraseStorage.decode(json: data, as: StoredWallet.self) else {
|
||||||
|
return XCTFail("Keychain: `walletReceived` can't be decoded.")
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(birthday, walletReceived.birthday, "Keychain: stored birthday and retrieved one must be the same.")
|
||||||
|
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 `testWalletStoredSucessfuly` but received. \(err)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWalletDuplicate() throws {
|
||||||
|
do {
|
||||||
|
try storage?.importRecoveryPhrase(bip39: seedPhrase, birthday: birthday)
|
||||||
|
try storage?.importRecoveryPhrase(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).")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
error.debugValue,
|
||||||
|
KeyStoringError.alreadyImported.debugValue,
|
||||||
|
"Keychain: error must be .alreadyImported but it's \(error)."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUninitializedWallet() throws {
|
||||||
|
do {
|
||||||
|
_ = 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).")
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(error.debugValue, KeyStoringError.uninitializedWallet.debugValue, "Keychain: error must be .uninitializedWallet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDeleteWallet() throws {
|
||||||
|
do {
|
||||||
|
let wallet = StoredWallet(
|
||||||
|
birthday: birthday,
|
||||||
|
language: language,
|
||||||
|
seedPhrase: seedPhrase,
|
||||||
|
version: RecoveryPhraseStorage.Constants.zcashKeychainVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let walletData = try RecoveryPhraseStorage.encode(object: wallet) else {
|
||||||
|
return XCTFail("`testDeleteWallet` encoding `walletData` failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try setData(walletData, forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
|
||||||
|
} catch {
|
||||||
|
XCTFail("`testDeleteWallet` storing `walletData` failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage?.nukeWallet()
|
||||||
|
|
||||||
|
let data = data(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
|
||||||
|
|
||||||
|
XCTAssertEqual(data, nil, "Keychain: keychain is expected to not find anything for key `zcashStoredWallet` but received some data.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateBirthdayOverNil() throws {
|
||||||
|
let wallet = StoredWallet(
|
||||||
|
birthday: nil,
|
||||||
|
language: language,
|
||||||
|
seedPhrase: seedPhrase,
|
||||||
|
version: RecoveryPhraseStorage.Constants.zcashKeychainVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let walletData = try RecoveryPhraseStorage.encode(object: wallet) else {
|
||||||
|
return XCTFail("`testUpdateBirthdayOverNil` encoding `walletData` failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try setData(walletData, forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
|
||||||
|
} catch {
|
||||||
|
XCTFail("`testUpdateBirthdayOverNil` storing `walletData` failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try storage?.updateBirthday(birthday)
|
||||||
|
guard let data = data(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet) else {
|
||||||
|
return XCTFail("Keychain: no data found for key: `zcashStoredWallet`.")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let walletReceived = try RecoveryPhraseStorage.decode(json: data, as: StoredWallet.self) else {
|
||||||
|
return XCTFail("Keychain: `walletReceived` can't be decoded.")
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(birthday, walletReceived.birthday, "Keychain: stored birthday and retrieved one must be the same.")
|
||||||
|
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 `testUpdateBirthdayOverNil` but received. \(err)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateBirthdayOverSomeBirthday() throws {
|
||||||
|
let wallet = StoredWallet(
|
||||||
|
birthday: birthday,
|
||||||
|
language: language,
|
||||||
|
seedPhrase: seedPhrase,
|
||||||
|
version: RecoveryPhraseStorage.Constants.zcashKeychainVersion
|
||||||
|
)
|
||||||
|
let newBirthday = BlockHeight(87654321)
|
||||||
|
|
||||||
|
guard let walletData = try RecoveryPhraseStorage.encode(object: wallet) else {
|
||||||
|
return XCTFail("`testUpdateBirthdayOverSomeBirthday` encoding `walletData` failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try setData(walletData, forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
|
||||||
|
} catch {
|
||||||
|
XCTFail("`testUpdateBirthdayOverSomeBirthday` storing `walletData` failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try storage?.updateBirthday(newBirthday)
|
||||||
|
guard let data = data(forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet) else {
|
||||||
|
return XCTFail("Keychain: no data found for key: `zcashStoredWallet`.")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let walletReceived = try RecoveryPhraseStorage.decode(json: data, as: StoredWallet.self) else {
|
||||||
|
return XCTFail("Keychain: `walletReceived` can't be decoded.")
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(newBirthday, walletReceived.birthday, "Keychain: stored birthday and retrieved one must be the same.")
|
||||||
|
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 `testUpdateBirthdayOverNil` but received. \(err)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUnsupportedVersion() throws {
|
||||||
|
let wallet = StoredWallet(
|
||||||
|
birthday: birthday,
|
||||||
|
language: language,
|
||||||
|
seedPhrase: seedPhrase,
|
||||||
|
/// older version
|
||||||
|
version: RecoveryPhraseStorage.Constants.zcashKeychainVersion - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let walletData = try RecoveryPhraseStorage.encode(object: wallet) else {
|
||||||
|
return XCTFail("`testUnsupportedVersion` encoding `walletData` failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try setData(walletData, forKey: RecoveryPhraseStorage.Constants.zcashStoredWallet)
|
||||||
|
} catch {
|
||||||
|
XCTFail("`testUnsupportedVersion` storing `walletData` failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
_ = try storage?.exportWallet()
|
||||||
|
|
||||||
|
XCTFail("Keychain: `testUnsupportedVersion` should fail but received some wallet with correct version.")
|
||||||
|
} catch KeyStoringError.unsupportedVersion(let version) {
|
||||||
|
XCTAssertEqual(
|
||||||
|
version + 1,
|
||||||
|
RecoveryPhraseStorage.Constants.zcashKeychainVersion,
|
||||||
|
"Keychain: version should be \(RecoveryPhraseStorage.Constants.zcashKeychainVersion) but stored version is \(version)"
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
XCTFail("Keychain: `testUnsupportedVersion` should fail with `unsupportedVersion` error but threw \(error).")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUnsupportedLanguage() throws {
|
||||||
|
do {
|
||||||
|
try storage?.importRecoveryPhrase(bip39: seedPhrase, birthday: birthday, language: .chinese)
|
||||||
|
|
||||||
|
XCTFail("Keychain: `testUnsupportedLanguage` should fail but imported chinese language.")
|
||||||
|
} catch KeyStoringError.unsupportedLanguage(let languageToStore) {
|
||||||
|
XCTAssertEqual(
|
||||||
|
MnemonicLanguageType.chinese,
|
||||||
|
languageToStore,
|
||||||
|
"Keychain: language should be english but received \(languageToStore)"
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
XCTFail("Keychain: `testUnsupportedLanguage` should fail with `unsupportedLanguage` error but threw \(error).")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
private func setData(
|
||||||
|
account: String = "",
|
||||||
|
_ data: Data,
|
||||||
|
forKey: String
|
||||||
|
) throws {
|
||||||
|
var query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
|
||||||
|
query[kSecValueData as String] = data as AnyObject
|
||||||
|
|
||||||
|
SecItemAdd(query as CFDictionary, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func data(
|
||||||
|
forKey: String,
|
||||||
|
account: String = ""
|
||||||
|
) -> Data? {
|
||||||
|
let query = RecoveryPhraseStorage.restoreQuery(forAccount: account, andKey: forKey)
|
||||||
|
|
||||||
|
var result: AnyObject?
|
||||||
|
_ = SecItemCopyMatching(query as CFDictionary, &result)
|
||||||
|
|
||||||
|
return result as? Data
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
private func deleteData(
|
||||||
|
forKey: String,
|
||||||
|
account: String = ""
|
||||||
|
) -> Bool {
|
||||||
|
let query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
|
||||||
|
|
||||||
|
let status = SecItemDelete(query as CFDictionary)
|
||||||
|
|
||||||
|
return status == noErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateData(
|
||||||
|
_ data: Data,
|
||||||
|
forKey: String,
|
||||||
|
account: String = ""
|
||||||
|
) throws {
|
||||||
|
let query = RecoveryPhraseStorage.baseQuery(forAccount: account, andKey: forKey)
|
||||||
|
|
||||||
|
let attributes:[ String: AnyObject ] = [
|
||||||
|
kSecValueData as String: data as AnyObject
|
||||||
|
]
|
||||||
|
|
||||||
|
_ = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue