// Copyright © 2021 Encrypted Ink. All rights reserved. // Rewrite of KeyStore.swift from Trust Wallet Core. import Foundation import WalletCore final class WalletsManager { enum Error: Swift.Error { case keychainAccessFailure case invalidInput } enum InputValidationResult { case valid, invalid, requiresPassword } static let shared = WalletsManager() private let keychain = Keychain.shared private(set) var wallets = [InkWallet]() private init() {} func start() { try? load() try? migrateFromLegacyIfNeeded() } func validateWalletInput(_ input: String) -> InputValidationResult { if Mnemonic.isValid(mnemonic: input) { return .valid } else if let data = Data(hexString: input) { return PrivateKey.isValid(data: data, curve: CoinType.ethereum.curve) ? .valid : .invalid } else { return input.maybeJSON ? .requiresPassword : .invalid } } func createWallet() throws -> InkWallet { guard let password = keychain.password else { throw Error.keychainAccessFailure } return try createWallet(name: defaultWalletName, password: password, coin: .ethereum) } func getWallet(id: String) -> InkWallet? { return wallets.first(where: { $0.id == id }) } func addWallet(input: String, inputPassword: String?) throws -> InkWallet { guard let password = keychain.password else { throw Error.keychainAccessFailure } let name = defaultWalletName let coin = CoinType.ethereum if Mnemonic.isValid(mnemonic: input) { return try importMnemonic(input, name: name, encryptPassword: password, coin: coin) } else if let data = Data(hexString: input), PrivateKey.isValid(data: data, curve: coin.curve), let privateKey = PrivateKey(data: data) { return try importPrivateKey(privateKey, name: name, password: password, coin: coin) } else if input.maybeJSON, let inputPassword = inputPassword, let json = input.data(using: .utf8) { return try importJSON(json, name: name, password: inputPassword, newPassword: password, coin: coin) } else { throw Error.invalidInput } } private func createWallet(name: String, password: String, coin: CoinType) throws -> InkWallet { let key = StoredKey(name: name, password: Data(password.utf8)) let id = makeNewWalletId() let wallet = InkWallet(id: id, key: key) _ = try wallet.getAccount(password: password, coin: coin) wallets.append(wallet) try save(wallet: wallet) return wallet } private func importJSON(_ json: Data, name: String, password: String, newPassword: String, coin: CoinType) throws -> InkWallet { guard let key = StoredKey.importJSON(json: json) else { throw KeyStore.Error.invalidKey } guard let data = key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } if let mnemonic = checkMnemonic(data) { return try self.importMnemonic(mnemonic, name: name, encryptPassword: newPassword, coin: coin) } guard let privateKey = PrivateKey(data: data) else { throw KeyStore.Error.invalidKey } return try self.importPrivateKey(privateKey, name: name, password: newPassword, coin: coin) } private func checkMnemonic(_ data: Data) -> String? { guard let mnemonic = String(data: data, encoding: .ascii), Mnemonic.isValid(mnemonic: mnemonic) else { return nil } return mnemonic } private func importPrivateKey(_ privateKey: PrivateKey, name: String, password: String, coin: CoinType) throws -> InkWallet { guard let newKey = StoredKey.importPrivateKey(privateKey: privateKey.data, name: name, password: Data(password.utf8), coin: coin) else { throw KeyStore.Error.invalidKey } let id = makeNewWalletId() let wallet = InkWallet(id: id, key: newKey) _ = try wallet.getAccount(password: password, coin: coin) wallets.append(wallet) try save(wallet: wallet) return wallet } private func importMnemonic(_ mnemonic: String, name: String, encryptPassword: String, coin: CoinType) throws -> InkWallet { guard let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: name, password: Data(encryptPassword.utf8), coin: coin) else { throw KeyStore.Error.invalidMnemonic } let id = makeNewWalletId() let wallet = InkWallet(id: id, key: key) _ = try wallet.getAccount(password: encryptPassword, coin: coin) wallets.append(wallet) try save(wallet: wallet) return wallet } func exportPrivateKey(wallet: InkWallet) throws -> Data { guard let password = keychain.password else { throw Error.keychainAccessFailure } guard let key = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } return key } func exportMnemonic(wallet: InkWallet) throws -> String { guard let password = keychain.password else { throw Error.keychainAccessFailure } guard let mnemonic = wallet.key.decryptMnemonic(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } return mnemonic } func update(wallet: InkWallet, password: String, newPassword: String) throws { try update(wallet: wallet, password: password, newPassword: newPassword, newName: wallet.key.name) } func update(wallet: InkWallet, password: String, newName: String) throws { try update(wallet: wallet, password: password, newPassword: password, newName: newName) } func delete(wallet: InkWallet) throws { guard let password = keychain.password else { throw Error.keychainAccessFailure } guard let index = wallets.firstIndex(of: wallet) else { throw KeyStore.Error.accountNotFound } guard var privateKey = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidKey } defer { privateKey.resetBytes(in: 0.. String { let uuid = UUID().uuidString let date = Date().timeIntervalSince1970 let walletId = "\(uuid)-\(date)" return walletId } }