diff --git a/Sources/LibAuk/Constant.swift b/Sources/LibAuk/Constant.swift index 0da4af2..2778713 100644 --- a/Sources/LibAuk/Constant.swift +++ b/Sources/LibAuk/Constant.swift @@ -20,5 +20,6 @@ struct Constant { static let seed = "seed" static let ethInfoKey = "ethInfo" + static let seedPublicData = "seedPublicData" } } diff --git a/Sources/LibAuk/LibAukError.swift b/Sources/LibAuk/LibAukError.swift index 1132415..f3fd98e 100644 --- a/Sources/LibAuk/LibAukError.swift +++ b/Sources/LibAuk/LibAukError.swift @@ -14,6 +14,7 @@ public enum LibAukError: Error { case emptyKey case keyCreationExistingError(key: String) case keyDerivationError + case generateSeedPublicDataError case other(reason: String) } @@ -44,6 +45,8 @@ extension LibAukError: LocalizedError { return "create key error: key exists" case .keyDerivationError: return "key derivation error" + case .generateSeedPublicDataError: + return "generate seed public data error" case .other(let reason): return reason } diff --git a/Sources/LibAuk/Model/KeyInfo.swift b/Sources/LibAuk/Model/KeyInfo.swift index b122ce2..dbf23a7 100644 --- a/Sources/LibAuk/Model/KeyInfo.swift +++ b/Sources/LibAuk/Model/KeyInfo.swift @@ -7,7 +7,65 @@ import Foundation -public struct KeyInfo: Codable { +public struct KeyInfo { let fingerprint, ethAddress: String let creationDate: Date } + +public struct SeedPublicData: Codable { + let ethAddress: String + let creationDate: Date + let name: String? + let did: String + let preGenerateEthAddress: [Int: String] + let tezosPublicKeys: [Int: String] + var _encryptionPrivateKeyBase64: String? = nil + var _accountDIDPrivateKeyBase64: String? = nil + + var encryptionPrivateKey: Secp256k1.Signing.PrivateKey? { + get { + guard let base64String = _encryptionPrivateKeyBase64, + let data = Data(base64Encoded: base64String) else { + fatalError("Invalid base64 string for private key") + } + do { + return try Secp256k1.Signing.PrivateKey(rawRepresentation: data) + } catch { + fatalError("Failed to initialize private key: \(error)") + } + } + + set { + if let newValue = newValue { + let privateKeyData = newValue.rawRepresentation + _encryptionPrivateKeyBase64 = privateKeyData.base64EncodedString() + } else { + _encryptionPrivateKeyBase64 = nil + } + } + } + + var accountDIDPrivateKey: Secp256k1.Signing.PrivateKey? { + get { + guard let base64String = _accountDIDPrivateKeyBase64, + let data = Data(base64Encoded: base64String) else { + fatalError("Invalid base64 string for private key") + } + do { + return try Secp256k1.Signing.PrivateKey(rawRepresentation: data) + } catch { + fatalError("Failed to initialize private key: \(error)") + } + } + + set { + if let newValue = newValue { + let privateKeyData = newValue.rawRepresentation + _accountDIDPrivateKeyBase64 = privateKeyData.base64EncodedString() + } else { + _accountDIDPrivateKeyBase64 = nil + } + } + } + +} diff --git a/Sources/LibAuk/Model/Seed.swift b/Sources/LibAuk/Model/Seed.swift index b507336..e95c49e 100644 --- a/Sources/LibAuk/Model/Seed.swift +++ b/Sources/LibAuk/Model/Seed.swift @@ -49,26 +49,26 @@ public class Seed: Codable { UREncoder.encode(ur) } - convenience init(urString: String) throws { + public convenience init(urString: String) throws { let ur = try URDecoder.decode(urString) try self.init(ur: ur) } - convenience init(ur: UR) throws { + public convenience init(ur: UR) throws { guard ur.type == "crypto-seed" else { throw LibAukError.other(reason: "Unexpected UR type.") } try self.init(cborData: ur.cbor) } - convenience init(cborData: Data) throws { + public convenience init(cborData: Data) throws { guard let cbor = try? CBOR(cborData) else { throw LibAukError.other(reason: "ur:crypto-seed: Invalid CBOR.") } try self.init(cbor: cbor) } - convenience init(cbor: CBOR) throws { + public convenience init(cbor: CBOR) throws { guard case let CBOR.orderedMap(orderedMap) = cbor else { throw LibAukError.other(reason: "ur:crypto-seed: CBOR doesn't contain a map.") } diff --git a/Sources/LibAuk/Storage/AccessControl.swift b/Sources/LibAuk/Storage/AccessControl.swift new file mode 100644 index 0000000..b34fc71 --- /dev/null +++ b/Sources/LibAuk/Storage/AccessControl.swift @@ -0,0 +1,32 @@ +// +// AccessControl.swift +// +// +// Created by Nguyen Phuoc Sang on 27/03/2024. +// + +import Foundation +import LocalAuthentication + + +class AccessControl { + + // MARK: - Singleton + public static let shared = AccessControl() + + // Policy + private var policy: LAPolicy = .deviceOwnerAuthentication + + var accessible: CFString = kSecAttrAccessibleWhenUnlocked + + // Reason + var reason: String = NSLocalizedString("Access your password on the keychain", comment: "") + + // Context + lazy var context: LAContext = { + let mainContext = LAContext() + mainContext.touchIDAuthenticationAllowableReuseDuration = Double(5) + mainContext.localizedReason = reason + return mainContext + }() +} diff --git a/Sources/LibAuk/Storage/Keychain.swift b/Sources/LibAuk/Storage/Keychain.swift index ba5033b..7d67252 100644 --- a/Sources/LibAuk/Storage/Keychain.swift +++ b/Sources/LibAuk/Storage/Keychain.swift @@ -7,10 +7,11 @@ // import Foundation +import LocalAuthentication protocol KeychainProtocol { @discardableResult - func set(_ data: Data, forKey: String, isSync: Bool) -> Bool + func set(_ data: Data, forKey: String, isSync: Bool, isPrivate: Bool) -> Bool func getData(_ key: String, isSync: Bool) -> Data? @discardableResult func remove(key: String, isSync: Bool) -> Bool @@ -25,16 +26,31 @@ class Keychain: KeychainProtocol { } @discardableResult - func set(_ data: Data, forKey: String, isSync: Bool = true) -> Bool { + func set(_ data: Data, forKey: String, isSync: Bool = true, isPrivate: Bool) -> Bool { let syncAttr = isSync ? kCFBooleanTrue : kCFBooleanFalse - let query = [ + var error: Unmanaged? + var accessControl: SecAccessControl? + if (isPrivate) { + accessControl = SecAccessControlCreateWithFlags( + nil, // Use the default allocator. + AccessControl.shared.accessible, + [.biometryCurrentSet, .or, .devicePasscode], + &error) + } + + var query = [ kSecClass as String: kSecClassGenericPassword as String, - kSecAttrSynchronizable as String: syncAttr!, - kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock, kSecAttrAccessGroup as String: LibAuk.shared.keyChainGroup, kSecAttrAccount as String: buildKeyAttr(prefix: prefix, key: forKey), - kSecValueData as String: data + kSecAttrSynchronizable as String: syncAttr, + kSecValueData as String: data, ] as [String: Any] + + if let access = accessControl { + query[kSecAttrAccessControl as String] = access + } else { + query[kSecAttrAccessible as String] = AccessControl.shared.accessible + } SecItemDelete(query as CFDictionary) @@ -49,14 +65,15 @@ class Keychain: KeychainProtocol { func getData(_ key: String, isSync: Bool = true) -> Data? { let syncAttr = isSync ? kCFBooleanTrue : kCFBooleanFalse + let context = AccessControl.shared.context let query = [ kSecClass as String: kSecClassGenericPassword, kSecAttrSynchronizable as String: syncAttr!, kSecAttrAccount as String: buildKeyAttr(prefix: prefix, key: key), kSecReturnData as String: kCFBooleanTrue!, - kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock, kSecAttrAccessGroup as String: LibAuk.shared.keyChainGroup, - kSecMatchLimit as String: kSecMatchLimitOne + kSecAttrAccessible as String: AccessControl.shared.accessible, + kSecMatchLimit as String: kSecMatchLimitOne, ] as [String: Any] var dataTypeRef: AnyObject? @@ -76,9 +93,9 @@ class Keychain: KeychainProtocol { let query = [ kSecClass as String: kSecClassGenericPassword as String, kSecAttrSynchronizable as String: syncAttr!, - kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock, kSecAttrAccessGroup as String: LibAuk.shared.keyChainGroup, - kSecAttrAccount as String: buildKeyAttr(prefix: prefix, key: key) + kSecAttrAccount as String: buildKeyAttr(prefix: prefix, key: key), + kSecAttrAccessible as String: AccessControl.shared.accessible, ] as [String: Any] // Delete any existing items diff --git a/Sources/LibAuk/Storage/SecureStorage.swift b/Sources/LibAuk/Storage/SecureStorage.swift index 5b8500e..e8e46a6 100644 --- a/Sources/LibAuk/Storage/SecureStorage.swift +++ b/Sources/LibAuk/Storage/SecureStorage.swift @@ -15,11 +15,10 @@ import CryptoKit import Sodium public protocol SecureStorageProtocol { - func createKey(passphrase: String?, name: String) -> AnyPublisher - func importKey(words: [String], passphrase: String?, name: String, creationDate: Date?) -> AnyPublisher + func createKey(passphrase: String?, name: String, isPrivate: Bool) -> AnyPublisher + func importKey(words: [String], passphrase: String?, name: String, creationDate: Date?, isPrivate: Bool) -> AnyPublisher func isWalletCreated() -> AnyPublisher func getName() -> String? - func updateName(name: String) -> AnyPublisher func getAccountDID() -> AnyPublisher func getAccountDIDSignature(message: String) -> AnyPublisher func getETHAddress() -> String? @@ -30,7 +29,6 @@ public protocol SecureStorageProtocol { func ethSignTransactionWithIndex(transaction: EthereumTransaction, chainId: EthereumQuantity, index: Int) -> AnyPublisher func encryptFile(inputPath: String, outputPath: String) -> AnyPublisher func decryptFile(inputPath: String, outputPath: String, usingLegacy: Bool) -> AnyPublisher - func getTezosPublicKey() -> AnyPublisher func tezosSign(message: Data) -> AnyPublisher<[UInt8], Error> func tezosSignTransaction(forgedHex: String) -> AnyPublisher<[UInt8], Error> func getTezosPublicKeyWithIndex(index: Int) -> AnyPublisher @@ -40,19 +38,128 @@ public protocol SecureStorageProtocol { func exportSeed() -> AnyPublisher func exportMnemonicWords() -> AnyPublisher<[String], Error> func removeKeys() -> AnyPublisher + func setSeed(seed: Seed, isPrivate: Bool) -> AnyPublisher + func removeSeed() -> AnyPublisher + func generateSeedPublicData(seed: Seed) -> AnyPublisher + func setData(_ data: Data, forKey: String, isSync: Bool, isPrivate: Bool) -> Bool } class SecureStorage: SecureStorageProtocol { - + private let keychain: KeychainProtocol + private let preGenerateAddressLimit: Int = 100 + init(keychain: KeychainProtocol = Keychain()) { self.keychain = keychain } - func createKey(passphrase: String? = "", name: String) -> AnyPublisher { + private func generateEthAddresses(mnemonic: BIP39Mnemonic, passphrase: String?, start: Int = 0, end: Int = 100) -> [Int: String] { + var ethAddresses: [Int: String] = [:] + + for index in start...end { + do { + // Generate Ethereum private key for the current index + let privateKey = try Keys.ethereumPrivateKeyWithIndex(mnemonic: mnemonic, passphrase: passphrase, index: index) + + // Derive Ethereum address from the private key + let address = privateKey.address.hex(eip55: true) + + // Store the address in the dictionary + ethAddresses[index] = address + } catch { + // Handle errors if private key generation fails + print("Error generating private key for index \(index): \(error)") + } + } + + return ethAddresses + } + + private func generateTezosPublicKeys(mnemonic: BIP39Mnemonic, passphrase: String?, start: Int, end: Int) -> [Int: String] { + var tezosPublicKeys: [Int: String] = [:] + + for index in start...end { + // Generate Tezos wallet for the current index + let tezosWallet = Keys.tezosWalletWithIndex(mnemonic: mnemonic, passphrase: passphrase, index: index) + + // Get the public key for the Tezos wallet + let publicKey = tezosWallet?.publicKeyBase58encoded() + + // Store the public key in the dictionary + tezosPublicKeys[index] = publicKey + } + + return tezosPublicKeys + } + + private func generateEthAddress(mnemonic: BIP39Mnemonic, passphrase: String?) throws -> String { + let ethPrivateKey = try Keys.ethereumPrivateKey(mnemonic: mnemonic, passphrase: passphrase) + let ethAddress = ethPrivateKey.address.hex(eip55: true) + return ethAddress + } + + func generateSeedPublicData(seed: Seed) -> AnyPublisher { + Future { promise in + do { + /* seedName */ + let name = seed.name + + /* accountDidKey */ + let mnemonic = Keys.mnemonic(seed.data)! + let passphrase = seed.passphrase + let privateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic) + + // Multicodec encoded with prefix 0xe7 + var bytes: [UInt8] = [231, 1] + bytes.append(contentsOf: privateKey.publicKey.rawRepresentation.bytes) + let did = "did:key:z\(Base58.base58Encode(bytes))" + + /* pre-generate 100 eth addresses */ + let ethAddresses = self.generateEthAddresses(mnemonic: mnemonic, passphrase: passphrase, start: 0, end: self.preGenerateAddressLimit) + + /* encryptionPrivateKey */ + let encryptionPrivateKey = try Keys.encryptionPrivateKey(mnemonic: mnemonic, passphrase: passphrase) + + /* accountDIDPrivateKey */ + let accountDIDPrivateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic, passphrase: passphrase) + + /* tezos public key */ + let tezosPublicKeys = self.generateTezosPublicKeys(mnemonic: mnemonic, passphrase: passphrase, start: 0, end: self.preGenerateAddressLimit) + + let ethAddress = try self.generateEthAddress(mnemonic: mnemonic, passphrase: passphrase) + + var seedPublicData = SeedPublicData(ethAddress: ethAddress, + creationDate: Date(), + name: name, + did: did, + preGenerateEthAddress: ethAddresses, + tezosPublicKeys: tezosPublicKeys) + + seedPublicData.encryptionPrivateKey = encryptionPrivateKey + seedPublicData.accountDIDPrivateKey = accountDIDPrivateKey + promise(.success(seedPublicData)) + } + catch { + promise(.failure(LibAukError.generateSeedPublicDataError)) + } + }.eraseToAnyPublisher() + } + + + + internal func getSeedPublicData() -> SeedPublicData? { + guard let seedPublicDataRaw = self.keychain.getData(Constant.KeychainKey.seedPublicData, isSync: true), + let seedPublicData = try? JSONDecoder().decode(SeedPublicData.self, from: seedPublicDataRaw) + else { + return nil + } + return seedPublicData + } + + func createKey(passphrase: String? = "", name: String, isPrivate: Bool) -> AnyPublisher { Future { promise in - guard self.keychain.getData(Constant.KeychainKey.ethInfoKey, isSync: true) == nil else { + guard self.getSeedPublicData() == nil else { promise(.failure(LibAukError.keyCreationExistingError(key: "createETHKey"))) return } @@ -64,23 +171,24 @@ class SecureStorage: SecureStorageProtocol { let seed = Seed(data: entropy, name: name, creationDate: Date(), passphrase: passphrase) let seedData = seed.urString.utf8 - - self.keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + self.keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: isPrivate) promise(.success(seed)) } - .compactMap { seed in - Keys.mnemonic(seed.data) - } - .tryMap { [unowned self] in - try self.saveKeyInfo(mnemonic: $0, passphrase: passphrase ?? "") - } - .eraseToAnyPublisher() + .flatMap { seed in + do { + return try self.saveSeedPublicData(seed: seed) + .map { _ in () } // Map the output to Void + .eraseToAnyPublisher() + } catch { + return Fail(error: error).eraseToAnyPublisher() + } + }.eraseToAnyPublisher() } - func importKey(words: [String], passphrase: String? = "", name: String, creationDate: Date?) -> AnyPublisher { + func importKey(words: [String], passphrase: String? = "", name: String, creationDate: Date?, isPrivate: Bool) -> AnyPublisher { Future { promise in - guard self.keychain.getData(Constant.KeychainKey.ethInfoKey, isSync: true) == nil else { + guard self.getSeedPublicData() == nil else { promise(.failure(LibAukError.keyCreationExistingError(key: "createETHKey"))) return } @@ -89,25 +197,52 @@ class SecureStorage: SecureStorageProtocol { let seed = Seed(data: entropy, name: name, creationDate: creationDate ?? Date(), passphrase: passphrase) let seedData = seed.urString.utf8 - self.keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + self.keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: isPrivate) promise(.success(seed)) } else { promise(.failure(LibAukError.invalidMnemonicError)) } } - .compactMap { seed in - Keys.mnemonic(seed.data) - } - .tryMap { [unowned self] in - try self.saveKeyInfo(mnemonic: $0, passphrase: passphrase ?? "") + .flatMap { seed in + do { + return try self.saveSeedPublicData(seed: seed) + .map { _ in () } // Map the output to Void + .eraseToAnyPublisher() + } catch { + return Fail(error: error).eraseToAnyPublisher() + } } .eraseToAnyPublisher() } + func setSeed(seed: Seed, isPrivate: Bool) -> AnyPublisher { + Future { promise in + guard self.keychain.set(seed.urString.utf8, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: isPrivate) else { + promise(.success(false)) + return + } + promise(.success(true)) + }.eraseToAnyPublisher() + } + + func setData(_ data: Data, forKey: String, isSync: Bool = true, isPrivate: Bool) -> Bool { + return self.keychain.set(data, forKey: forKey, isSync: isSync, isPrivate: isPrivate) + } + + func removeSeed() -> AnyPublisher { + Future { promise in + guard self.keychain.remove(key: Constant.KeychainKey.seed, isSync: true) else { + promise(.success(false)) + return + } + promise(.success(true)) + }.eraseToAnyPublisher() + } + func isWalletCreated() -> AnyPublisher { Future { promise in - guard let infoData = self.keychain.getData(Constant.KeychainKey.ethInfoKey, isSync: true), - (try? JSONDecoder().decode(KeyInfo.self, from: infoData)) != nil else { + guard let seedPublicDataRaw = self.keychain.getData(Constant.KeychainKey.seedPublicData, isSync: true), + (try? JSONDecoder().decode(SeedPublicData.self, from: seedPublicDataRaw)) != nil else { promise(.success(false)) return } @@ -118,120 +253,100 @@ class SecureStorage: SecureStorageProtocol { } func getName() -> String? { - guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), - let seed = try? Seed(urString: seedUR.utf8) else { + guard let seedPublicData = self.getSeedPublicData() else { return "" } - return seed.name - } - - func updateName(name: String) -> AnyPublisher { - Future { promise in - guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), - let seed = try? Seed(urString: seedUR.utf8) else { - promise(.failure(LibAukError.emptyKey)) - return - } - - promise(.success(seed)) - } - .map { - Seed(data: $0.data, name: name, creationDate: $0.creationDate, passphrase: $0.passphrase) - } - .map { seed in - let seedData = seed.urString.utf8 - - self.keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - return () - } - .eraseToAnyPublisher() + return seedPublicData.name } func getAccountDID() -> AnyPublisher { - Future { promise in - guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), - let seed = try? Seed(urString: seedUR.utf8) else { + Future { promise in + guard let seedPublicData = self.getSeedPublicData() else { promise(.failure(LibAukError.emptyKey)) return } - promise(.success(seed)) + promise(.success(seedPublicData)) } - .compactMap { seed in - guard let mnemonic = Keys.mnemonic(seed.data) else { - return nil - } - return (mnemonic, seed.passphrase) - } - .tryMap { (mnemonic: BIP39Mnemonic, passphrase: String?) in - let privateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic, passphrase: passphrase) - // Multicodec encoded with prefix 0xe7 - - var bytes: [UInt8] = [231, 1] - bytes.append(contentsOf: privateKey.publicKey.rawRepresentation.bytes) - let did = "did:key:z\(Base58.base58Encode(bytes))" - - return did + .tryMap { (seedPublicData) in + return seedPublicData.did } .eraseToAnyPublisher() } func getAccountDIDSignature(message: String) -> AnyPublisher { - Future { promise in - guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), - let seed = try? Seed(urString: seedUR.utf8) else { + Future { promise in + guard let seedPublicData = self.getSeedPublicData(), + let privateKey = seedPublicData.accountDIDPrivateKey + else { promise(.failure(LibAukError.emptyKey)) return } - promise(.success(seed)) + promise(.success(privateKey)) } - .compactMap { seed in - guard let mnemonic = Keys.mnemonic(seed.data) else { - return nil - } - return (mnemonic, seed.passphrase) - } - .tryMap { (mnemonic: BIP39Mnemonic, passphrase: String?) in - let privateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic, passphrase: passphrase) - + .tryMap { (privateKey) in return try privateKey.signature(for: message.utf8).derRepresentation.hexString } .eraseToAnyPublisher() } func getETHAddress() -> String? { - guard let infoData = self.keychain.getData(Constant.KeychainKey.ethInfoKey, isSync: true), - let keyInfo = try? JSONDecoder().decode(KeyInfo.self, from: infoData) else { + guard let seedPublicData = self.getSeedPublicData(), + let ethAddress = seedPublicData.ethAddress as String? else { return nil } - return keyInfo.ethAddress + return ethAddress } func getETHAddressWithIndex(index: Int) -> AnyPublisher { - Future { promise in - guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), - let seed = try? Seed(urString: seedUR.utf8) else { + Future { promise in + guard let seedPublicData = self.getSeedPublicData(), + let address = seedPublicData.preGenerateEthAddress[index] else { promise(.failure(LibAukError.emptyKey)) return } - - promise(.success(seed)) + promise(.success(address)) } - .compactMap { seed in - guard let mnemonic = Keys.mnemonic(seed.data) else { - return nil + .catch({ error in + Future { promise in + guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), + let seed = try? Seed(urString: seedUR.utf8) else { + promise(.failure(LibAukError.emptyKey)) + return + } + + promise(.success(seed)) } - return (mnemonic, seed.passphrase) - } - .tryMap { (mnemonic: BIP39Mnemonic, passphrase: String?) in - let ethPrivateKey = try Keys.ethereumPrivateKeyWithIndex(mnemonic: mnemonic, passphrase: passphrase, index: index) - return ethPrivateKey.address.hex(eip55: true) - } + .compactMap { seed in + guard let mnemonic = Keys.mnemonic(seed.data) else { + return nil + } + return (mnemonic, seed.passphrase) + } + .tryMap { (mnemonic: BIP39Mnemonic, passphrase: String?) in + let ethPrivateKey = try Keys.ethereumPrivateKeyWithIndex(mnemonic: mnemonic, passphrase: passphrase, index: index) + return ethPrivateKey.address.hex(eip55: true) + } + .eraseToAnyPublisher() + }) .eraseToAnyPublisher() } + private func getSeed() -> AnyPublisher { + Future { promise in + guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), + let seed = try? Seed(urString: seedUR.utf8) else { + promise(.failure(LibAukError.emptyKey)) + return + } + + promise(.success(seed)) + }.eraseToAnyPublisher() + } + + func ethSign(message: Bytes) -> AnyPublisher<(v: UInt, r: Bytes, s: Bytes), Error> { Future { promise in guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), @@ -327,22 +442,15 @@ class SecureStorage: SecureStorageProtocol { } private func getEncryptKey(usingLegacy: Bool = false) -> AnyPublisher { - return Future { promise in - guard let seedUR = self.keychain.getData(Constant.KeychainKey.seed, isSync: true), - let seed = try? Seed(urString: seedUR.utf8) else { + return Future { promise in + guard let seedPublicData = self.getSeedPublicData() else { promise(.failure(LibAukError.emptyKey)) return } - promise(.success(seed)) - } - .compactMap { seed in - guard let mnemonic = Keys.mnemonic(seed.data) else { - return nil - } - return (mnemonic, seed.passphrase) + promise(.success(seedPublicData)) } - .tryMap({ (mnemonic: BIP39Mnemonic, passphrase: String?) in - let privateKey = try Keys.encryptionPrivateKey(mnemonic: mnemonic, passphrase: passphrase) + .tryMap({ (seedPublicData) in + let privateKey = seedPublicData.encryptionPrivateKey! if (usingLegacy) { return SymmetricKey(data: privateKey.rawRepresentation) } else { @@ -375,21 +483,27 @@ class SecureStorage: SecureStorageProtocol { }) .eraseToAnyPublisher() } - - func getTezosPublicKey() -> AnyPublisher { - getTezosWallet() - .compactMap { - $0.publicKeyBase58encoded() - } - .eraseToAnyPublisher() - } - + func getTezosPublicKeyWithIndex(index: Int) -> AnyPublisher { - getTezosWalletWithIndex(index: index) - .compactMap { - $0.publicKeyBase58encoded() + return Future { promise in + guard let seedPublicData = self.getSeedPublicData(), + let address = seedPublicData.tezosPublicKeys[index] else { + promise(.failure(LibAukError.emptyKey)) + return } - .eraseToAnyPublisher() + promise(.success(address)) + + } + .catch({ error in + return self.getTezosWalletWithIndex(index: index) + .compactMap { + $0.publicKeyBase58encoded() + } + .eraseToAnyPublisher() + }) + .eraseToAnyPublisher() + + } func tezosSign(message: Data) -> AnyPublisher<[UInt8], Error> { @@ -447,7 +561,7 @@ class SecureStorage: SecureStorageProtocol { } .eraseToAnyPublisher() } - + func exportMnemonicPassphrase() -> AnyPublisher { self.exportSeed() .map { $0.passphrase ?? "" } @@ -465,14 +579,13 @@ class SecureStorage: SecureStorageProtocol { func removeKeys() -> AnyPublisher { Future { promise in - guard self.keychain.getData(Constant.KeychainKey.seed, isSync: true) != nil, - self.keychain.getData(Constant.KeychainKey.ethInfoKey, isSync: true) != nil else { + guard self.keychain.getData(Constant.KeychainKey.seedPublicData, isSync: true) != nil else { promise(.failure(LibAukError.emptyKey)) return } self.keychain.remove(key: Constant.KeychainKey.seed, isSync: true) - self.keychain.remove(key: Constant.KeychainKey.ethInfoKey, isSync: true) + self.keychain.remove(key: Constant.KeychainKey.seedPublicData, isSync: true) promise(.success(())) } @@ -480,15 +593,11 @@ class SecureStorage: SecureStorageProtocol { } - func saveKeyInfo(mnemonic: BIP39Mnemonic, passphrase: String) throws { - let ethPrivateKey = try Keys.ethereumPrivateKey(mnemonic: mnemonic, passphrase: passphrase) - - let keyInfo = KeyInfo(fingerprint: Keys.fingerprint(mnemonic: mnemonic, passphrase: passphrase) ?? "", - ethAddress: ethPrivateKey.address.hex(eip55: true), - creationDate: Date()) - - let keyInfoData = try JSONEncoder().encode(keyInfo) - keychain.set(keyInfoData, forKey: Constant.KeychainKey.ethInfoKey, isSync: true) + func saveSeedPublicData(seed: Seed) -> AnyPublisher { + return self.generateSeedPublicData(seed: seed).tryMap { seedPublicData in + let seedPublicDataRaw = try JSONEncoder().encode(seedPublicData) + self.keychain.set(seedPublicDataRaw, forKey: Constant.KeychainKey.seedPublicData, isSync: true, isPrivate: false) + }.eraseToAnyPublisher() } internal func getTezosWallet() -> AnyPublisher { diff --git a/Tests/LibAukTests/LibAuk_Tests.swift b/Tests/LibAukTests/LibAuk_Tests.swift index d5750aa..39320b5 100644 --- a/Tests/LibAukTests/LibAuk_Tests.swift +++ b/Tests/LibAukTests/LibAuk_Tests.swift @@ -1,43 +1,43 @@ -import XCTest -import Combine -@testable import LibAuk - -final class LibAuk_Tests: XCTestCase { - - private var cancelBag: Set! - - override func setUpWithError() throws { - cancelBag = [] - LibAuk.create(keyChainGroup: "com.bitmark.autonomy") - } - - override func tearDownWithError() throws { - cancelBag.removeAll() - } - - func testCreateAutonomyAccountVault() throws { - XCTAssertEqual(LibAuk.shared.keyChainGroup, "com.bitmark.autonomy") - } - - func testCalculateEthFirstAddressSuccessfully() throws { - let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let passphrase = "feralfile" - let receivedExpectation = expectation(description: "all values received") - LibAuk.shared.calculateEthFirstAddress(words: words.components(separatedBy: " "), passphrase: passphrase) - .sink(receiveCompletion: { completion in - switch completion { - case .finished: - receivedExpectation.fulfill() - case .failure(let error): - XCTFail("calculateEthFirstAddress failed \(error)") - } - - }, receiveValue: { ethAddress in - XCTAssertEqual(ethAddress, "0x459389605dF56EA4BBB0F11F1b6D68928C73384A") - }) - .store(in: &cancelBag) - - waitForExpectations(timeout: 1, handler: nil) - } - -} + import XCTest + import Combine + @testable import LibAuk + + final class LibAuk_Tests: XCTestCase { + + private var cancelBag: Set! + + override func setUpWithError() throws { + cancelBag = [] + LibAuk.create(keyChainGroup: "com.bitmark.autonomy") + } + + override func tearDownWithError() throws { + cancelBag.removeAll() + } + + func testCreateAutonomyAccountVault() throws { + XCTAssertEqual(LibAuk.shared.keyChainGroup, "com.bitmark.autonomy") + } + + func testCalculateEthFirstAddressSuccessfully() throws { + let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" + let passphrase = "feralfile" + let receivedExpectation = expectation(description: "all values received") + LibAuk.shared.calculateEthFirstAddress(words: words.components(separatedBy: " "), passphrase: passphrase) + .sink(receiveCompletion: { completion in + switch completion { + case .finished: + receivedExpectation.fulfill() + case .failure(let error): + XCTFail("calculateEthFirstAddress failed \(error)") + } + + }, receiveValue: { ethAddress in + XCTAssertEqual(ethAddress, "0x459389605dF56EA4BBB0F11F1b6D68928C73384A") + }) + .store(in: &cancelBag) + + waitForExpectations(timeout: 1, handler: nil) + } + + } diff --git a/Tests/LibAukTests/Storage/KeychainMock.swift b/Tests/LibAukTests/Storage/KeychainMock.swift index 69dd2dc..8329802 100644 --- a/Tests/LibAukTests/Storage/KeychainMock.swift +++ b/Tests/LibAukTests/Storage/KeychainMock.swift @@ -10,11 +10,11 @@ import Foundation @testable import LibAuk class KeychainMock: KeychainProtocol { - + var values = [String: (Data, Bool)]() @discardableResult - func set(_ data: Data, forKey: String, isSync: Bool) -> Bool { + func set(_ data: Data, forKey: String, isSync: Bool, isPrivate: Bool) -> Bool { values[forKey] = (data, isSync) return true } diff --git a/Tests/LibAukTests/Storage/SecureStorage_Tests.swift b/Tests/LibAukTests/Storage/SecureStorage_Tests.swift index 0aec4fb..ac0709a 100644 --- a/Tests/LibAukTests/Storage/SecureStorage_Tests.swift +++ b/Tests/LibAukTests/Storage/SecureStorage_Tests.swift @@ -35,14 +35,14 @@ class SecureStorage_Tests: XCTestCase { func testCreateKeySuccessfully() throws { let receivedExpectation = expectation(description: "all values received") - storage.createKey(passphrase: "", name: "account1") + storage.createKey(passphrase: "", name: "account1", isPrivate: true) .sink(receiveCompletion: { completion in switch completion { case .finished: XCTAssertNotNil(self.keychain.getData(Constant.KeychainKey.seed)) XCTAssertTrue(self.keychain.getSync(Constant.KeychainKey.seed)!) - XCTAssertNotNil(self.keychain.getData(Constant.KeychainKey.ethInfoKey)) - XCTAssertTrue(self.keychain.getSync(Constant.KeychainKey.ethInfoKey)!) + XCTAssertNotNil(self.keychain.getData(Constant.KeychainKey.seedPublicData)) + XCTAssertTrue(self.keychain.getSync(Constant.KeychainKey.seedPublicData)!) receivedExpectation.fulfill() case .failure(let error): @@ -59,14 +59,14 @@ class SecureStorage_Tests: XCTestCase { let words: [String] = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak".components(separatedBy: " ") let receivedExpectation = expectation(description: "all values received") - storage.importKey(words: words, passphrase: "", name: "account1", creationDate: Date(timeIntervalSince1970: 1628656699)) + storage.importKey(words: words, passphrase: "", name: "account1", creationDate: Date(timeIntervalSince1970: 1628656699), isPrivate: true) .sink(receiveCompletion: { completion in switch completion { case .finished: XCTAssertNotNil(self.keychain.getData(Constant.KeychainKey.seed)) XCTAssertTrue(self.keychain.getSync(Constant.KeychainKey.seed)!) - XCTAssertNotNil(self.keychain.getData(Constant.KeychainKey.ethInfoKey)) - XCTAssertTrue(self.keychain.getSync(Constant.KeychainKey.ethInfoKey)!) + XCTAssertNotNil(self.keychain.getData(Constant.KeychainKey.seedPublicData)) + XCTAssertTrue(self.keychain.getSync(Constant.KeychainKey.seedPublicData)!) receivedExpectation.fulfill() case .failure(let error): @@ -80,11 +80,19 @@ class SecureStorage_Tests: XCTestCase { } func testIsWalletCreatedSuccessfully() throws { - let mnemomic = try BIP39Mnemonic(words: "daring mix cradle palm crowd sea observe whisper rubber either uncle oak") - try storage.saveKeyInfo(mnemonic: mnemomic, passphrase: "") - + let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" + let seed = Seed(data: Keys.entropy(words)!, name: "", creationDate: Date(), passphrase: "") + let mnemonic = Keys.mnemonic(seed.data)! + + var seedPublicData = SeedPublicData(ethAddress: "0xA00cbE6a45102135A210F231901faA6c05D51465", creationDate: Date(), name: "", did: "did:key:zQ3shUnBWE7Dkskaozsnzsb78kVcgQFbtXf7zdCCDN3qepBGL", preGenerateEthAddress: [:], tezosPublicKeys: [:]) + let accountDIDPrivateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic, passphrase: "") + let encryptionPrivateKey = try Keys.encryptionPrivateKey(mnemonic: mnemonic, passphrase: "") + seedPublicData.encryptionPrivateKey = encryptionPrivateKey + seedPublicData.accountDIDPrivateKey = accountDIDPrivateKey + let seedPublicDataRaw = try JSONEncoder().encode(seedPublicData) + keychain.set(seedPublicDataRaw, forKey: Constant.KeychainKey.seedPublicData, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") - + storage.isWalletCreated() .sink(receiveCompletion: { completion in switch completion { @@ -102,71 +110,34 @@ class SecureStorage_Tests: XCTestCase { waitForExpectations(timeout: 1, handler: nil) } - func testUpdateNameSuccessfully() throws { - let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date(), passphrase: "passphrase") - let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - - let receivedExpectation = expectation(description: "all values received") - - storage.updateName(name: "account2") - .sink(receiveCompletion: { completion in - switch completion { - case .finished: - let seed = try! Seed(urString: self.keychain.getData(Constant.KeychainKey.seed)!.utf8) - XCTAssertEqual(seed.name, "account2") - receivedExpectation.fulfill() - case .failure(let error): - XCTFail("update name failed \(error)") - } - - }, receiveValue: { _ in }) - .store(in: &cancelBag) - - waitForExpectations(timeout: 1, handler: nil) - } - - func testGetNameSuccessfully() throws { - let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date()) - let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - - XCTAssertEqual(storage.getName(), "account1") - } - - func testGetNameWithOptionalCreationDateSuccessfully() throws { - let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: nil) - let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - - XCTAssertEqual(storage.getName(), "account1") - } - - func testGetNameWithEmptySuccessfully() throws { - let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let seed = Seed(data: Keys.entropy(words)!, name: "", creationDate: Date()) - let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - - XCTAssertEqual(storage.getName(), "") - } - func testGetETHAddressSuccessfully() throws { - let mnemomic = try BIP39Mnemonic(words: "daring mix cradle palm crowd sea observe whisper rubber either uncle oak") - try storage.saveKeyInfo(mnemonic: mnemomic, passphrase: "") + let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" + let seed = Seed(data: Keys.entropy(words)!, name: "", creationDate: Date(), passphrase: "") + let mnemonic = Keys.mnemonic(seed.data)! + + var seedPublicData = SeedPublicData(ethAddress: "0xA00cbE6a45102135A210F231901faA6c05D51465", creationDate: Date(), name: "", did: "did:key:zQ3shUnBWE7Dkskaozsnzsb78kVcgQFbtXf7zdCCDN3qepBGL", preGenerateEthAddress: [:], tezosPublicKeys: [:]) + let accountDIDPrivateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic, passphrase: "") + let encryptionPrivateKey = try Keys.encryptionPrivateKey(mnemonic: mnemonic, passphrase: "") + seedPublicData.encryptionPrivateKey = encryptionPrivateKey + seedPublicData.accountDIDPrivateKey = accountDIDPrivateKey + let seedPublicDataRaw = try JSONEncoder().encode(seedPublicData) + keychain.set(seedPublicDataRaw, forKey: Constant.KeychainKey.seedPublicData, isSync: true, isPrivate: true) XCTAssertEqual(storage.getETHAddress(), "0xA00cbE6a45102135A210F231901faA6c05D51465") } func testGetAccountDIDSuccessfully() throws { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date()) - let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - + let seed = Seed(data: Keys.entropy(words)!, name: "", creationDate: Date(), passphrase: "") + let mnemonic = Keys.mnemonic(seed.data)! + + var seedPublicData = SeedPublicData(ethAddress: "0xA00cbE6a45102135A210F231901faA6c05D51465", creationDate: Date(), name: "", did: "did:key:zQ3shUnBWE7Dkskaozsnzsb78kVcgQFbtXf7zdCCDN3qepBGL", preGenerateEthAddress: [:], tezosPublicKeys: [:]) + let accountDIDPrivateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic, passphrase: "") + let encryptionPrivateKey = try Keys.encryptionPrivateKey(mnemonic: mnemonic, passphrase: "") + seedPublicData.encryptionPrivateKey = encryptionPrivateKey + seedPublicData.accountDIDPrivateKey = accountDIDPrivateKey + let seedPublicDataRaw = try JSONEncoder().encode(seedPublicData) + keychain.set(seedPublicDataRaw, forKey: Constant.KeychainKey.seedPublicData, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") storage.getAccountDID() @@ -188,10 +159,16 @@ class SecureStorage_Tests: XCTestCase { func testGetAccountDIDSignatureSuccessfully() throws { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date()) - let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - + let seed = Seed(data: Keys.entropy(words)!, name: "", creationDate: Date(), passphrase: "") + let mnemonic = Keys.mnemonic(seed.data)! + + var seedPublicData = SeedPublicData(ethAddress: "0xA00cbE6a45102135A210F231901faA6c05D51465", creationDate: Date(), name: "", did: "did:key:zQ3shUnBWE7Dkskaozsnzsb78kVcgQFbtXf7zdCCDN3qepBGL", preGenerateEthAddress: [:], tezosPublicKeys: [:]) + let accountDIDPrivateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic, passphrase: "") + let encryptionPrivateKey = try Keys.encryptionPrivateKey(mnemonic: mnemonic, passphrase: "") + seedPublicData.encryptionPrivateKey = encryptionPrivateKey + seedPublicData.accountDIDPrivateKey = accountDIDPrivateKey + let seedPublicDataRaw = try JSONEncoder().encode(seedPublicData) + keychain.set(seedPublicDataRaw, forKey: Constant.KeychainKey.seedPublicData, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") storage.getAccountDIDSignature(message: "hello") @@ -215,7 +192,7 @@ class SecureStorage_Tests: XCTestCase { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date()) let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: true) let message = "hello" let receivedExpectation = expectation(description: "all values received") @@ -243,7 +220,7 @@ class SecureStorage_Tests: XCTestCase { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date()) let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: true) let tx = EthereumTransaction( nonce: 1, @@ -280,7 +257,7 @@ class SecureStorage_Tests: XCTestCase { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date(timeIntervalSince1970: 1628656699)) let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") @@ -301,36 +278,11 @@ class SecureStorage_Tests: XCTestCase { waitForExpectations(timeout: 1, handler: nil) } - func testGetTezosPublickeySuccessfully() throws { - let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date(timeIntervalSince1970: 1628656699)) - let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - - let receivedExpectation = expectation(description: "all values received") - - storage.getTezosPublicKey() - .sink(receiveCompletion: { completion in - switch completion { - case .finished: - receivedExpectation.fulfill() - case .failure(let error): - XCTFail("exportSeed failed \(error)") - } - - }, receiveValue: { publickKey in - XCTAssertEqual(publickKey, "edpkuJKSkDoBpDs1aDtQWfBothZxpu6KxWG8gkB77TLxjJ344adoaP") - }) - .store(in: &cancelBag) - - waitForExpectations(timeout: 1, handler: nil) - } - func testTezosSignMessageSuccessfully() throws { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date(timeIntervalSince1970: 1628656699)) let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") @@ -355,7 +307,7 @@ class SecureStorage_Tests: XCTestCase { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date(timeIntervalSince1970: 1628656699)) let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") @@ -380,12 +332,8 @@ class SecureStorage_Tests: XCTestCase { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date(timeIntervalSince1970: 1628656699)) let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - - let keyInfo = KeyInfo(fingerprint: "0a3df912", ethAddress: "0xA00cbE6a45102135A210F231901faA6c05D51465", creationDate: Date(timeIntervalSince1970: 1628656699)) - let keyInfoData = try JSONEncoder().encode(keyInfo) - keychain.set(keyInfoData, forKey: Constant.KeychainKey.ethInfoKey, isSync: true) - + keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: true) + let receivedExpectation = expectation(description: "all values received") storage.exportSeed() @@ -412,7 +360,7 @@ class SecureStorage_Tests: XCTestCase { let passphrase = "passphrase1" let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date(), passphrase: passphrase) let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") @@ -437,7 +385,7 @@ class SecureStorage_Tests: XCTestCase { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date()) let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) + keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") @@ -460,14 +408,16 @@ class SecureStorage_Tests: XCTestCase { func testRemoveKeysSuccessfully() throws { let words = "daring mix cradle palm crowd sea observe whisper rubber either uncle oak" - let seed = Seed(data: Keys.entropy(words)!, name: "account1", creationDate: Date(timeIntervalSince1970: 1628656699)) - let seedData = seed.urString.utf8 - keychain.set(seedData, forKey: Constant.KeychainKey.seed, isSync: true) - - let keyInfo = KeyInfo(fingerprint: "0a3df912", ethAddress: "0xA00cbE6a45102135A210F231901faA6c05D51465", creationDate: Date(timeIntervalSince1970: 1628656699)) - let keyInfoData = try JSONEncoder().encode(keyInfo) - keychain.set(keyInfoData, forKey: Constant.KeychainKey.ethInfoKey, isSync: true) - + let seed = Seed(data: Keys.entropy(words)!, name: "", creationDate: Date(), passphrase: "") + let mnemonic = Keys.mnemonic(seed.data)! + + var seedPublicData = SeedPublicData(ethAddress: "0xA00cbE6a45102135A210F231901faA6c05D51465", creationDate: Date(), name: "", did: "did:key:zQ3shUnBWE7Dkskaozsnzsb78kVcgQFbtXf7zdCCDN3qepBGL", preGenerateEthAddress: [:], tezosPublicKeys: [:]) + let accountDIDPrivateKey = try Keys.accountDIDPrivateKey(mnemonic: mnemonic, passphrase: "") + let encryptionPrivateKey = try Keys.encryptionPrivateKey(mnemonic: mnemonic, passphrase: "") + seedPublicData.encryptionPrivateKey = encryptionPrivateKey + seedPublicData.accountDIDPrivateKey = accountDIDPrivateKey + let seedPublicDataRaw = try JSONEncoder().encode(seedPublicData) + keychain.set(seedPublicDataRaw, forKey: Constant.KeychainKey.seedPublicData, isSync: true, isPrivate: true) let receivedExpectation = expectation(description: "all values received") storage.removeKeys() @@ -475,7 +425,7 @@ class SecureStorage_Tests: XCTestCase { switch completion { case .finished: XCTAssertNil(self.keychain.getData(Constant.KeychainKey.seed)) - XCTAssertNil(self.keychain.getData(Constant.KeychainKey.ethInfoKey)) + XCTAssertNil(self.keychain.getData(Constant.KeychainKey.seedPublicData)) receivedExpectation.fulfill() case .failure(let error):