From 964b4d3ebb04d7e179ef7fc68a14bc22bb8b5cb3 Mon Sep 17 00:00:00 2001 From: Ivan Grachev Date: Sat, 31 Jul 2021 20:43:34 +0300 Subject: [PATCH 01/21] Rewrite KeyStore.swift and Wallet.swift from Trust Wallet Core to store keys JSONs in keychain --- Encrypted Ink.xcodeproj/project.pbxproj | 4 + Encrypted Ink/Wallets/InkWallet.swift | 45 +++++++ Encrypted Ink/Wallets/WalletsManager.swift | 132 +++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 Encrypted Ink/Wallets/InkWallet.swift create mode 100644 Encrypted Ink/Wallets/WalletsManager.swift diff --git a/Encrypted Ink.xcodeproj/project.pbxproj b/Encrypted Ink.xcodeproj/project.pbxproj index d860d1ac..d89a08ce 100644 --- a/Encrypted Ink.xcodeproj/project.pbxproj +++ b/Encrypted Ink.xcodeproj/project.pbxproj @@ -51,6 +51,8 @@ 2CC0CDBE2692027E0072922A /* PriceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC0CDBD2692027E0072922A /* PriceService.swift */; }; 2CC8946F269A2E8C00879245 /* SessionStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC8946E269A2E8C00879245 /* SessionStorage.swift */; }; 2CC89471269A334A00879245 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC89470269A334A00879245 /* UserDefaults.swift */; }; + 2CD0669126B5537B00728C20 /* InkWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0668B26B2142000728C20 /* InkWallet.swift */; }; + 2CD0669226B5537B00728C20 /* WalletsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0668926B213E500728C20 /* WalletsManager.swift */; }; 2CD0B3F526A0DAA900488D92 /* NSPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0B3F426A0DAA900488D92 /* NSPasteboard.swift */; }; 2CD0B3F726AC619900488D92 /* AddAccountOptionCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0B3F626AC619900488D92 /* AddAccountOptionCellView.swift */; }; 2CDAB3722675B3F0009F8B97 /* PasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDAB3712675B3F0009F8B97 /* PasswordViewController.swift */; }; @@ -438,6 +440,7 @@ 0DB7291D2674E2DB0011F7A1 /* EIP712Hashable.swift in Sources */, 0DB729162674E2DB0011F7A1 /* EIP712SimpleValue.swift in Sources */, 0DB729122674E2DB0011F7A1 /* EIP712ParameterEncoder.swift in Sources */, + 2CD0669126B5537B00728C20 /* InkWallet.swift in Sources */, 2C8A09D726751A0C00993638 /* WalletConnect.swift in Sources */, 2C03D1D2269B407900EF10EA /* NetworkMonitor.swift in Sources */, 2C8A09E326757FC000993638 /* AccountCellView.swift in Sources */, @@ -447,6 +450,7 @@ 2C78F8282683BDCC00C10670 /* Alert.swift in Sources */, 2C8A09EE2675965F00993638 /* WaitingViewController.swift in Sources */, 2C797E7E267BB88800F2CE2D /* WelcomeViewController.swift in Sources */, + 2CD0669226B5537B00728C20 /* WalletsManager.swift in Sources */, 0DB729192674E2DB0011F7A1 /* EIP712TypedData.swift in Sources */, 2CD0B3F726AC619900488D92 /* AddAccountOptionCellView.swift in Sources */, 2C8A09E82675960D00993638 /* ErrorViewController.swift in Sources */, diff --git a/Encrypted Ink/Wallets/InkWallet.swift b/Encrypted Ink/Wallets/InkWallet.swift new file mode 100644 index 00000000..141a23f5 --- /dev/null +++ b/Encrypted Ink/Wallets/InkWallet.swift @@ -0,0 +1,45 @@ +// Copyright © 2021 Encrypted Ink. All rights reserved. +// Rewrite of Wallet.swift from Trust Wallet Core. + +import Foundation +import WalletCore + +final class InkWallet: Hashable, Equatable { + + let id: String + var key: StoredKey + + var accounts: [Account] { + return (0.. Account { + let wallet = key.wallet(password: Data(password.utf8)) + guard let account = key.accountForCoin(coin: coin, wallet: wallet) else { throw KeyStore.Error.invalidPassword } + return account + } + + func getAccounts(password: String, coins: [CoinType]) throws -> [Account] { + guard let wallet = key.wallet(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } + return coins.compactMap({ key.accountForCoin(coin: $0, wallet: wallet) }) + } + + func privateKey(password: String, coin: CoinType) throws -> PrivateKey { + guard let privateKey = key.privateKey(coin: coin, password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } + return privateKey + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + + static func == (lhs: InkWallet, rhs: InkWallet) -> Bool { + return lhs.id == rhs.id + } + +} diff --git a/Encrypted Ink/Wallets/WalletsManager.swift b/Encrypted Ink/Wallets/WalletsManager.swift new file mode 100644 index 00000000..8d9de1fc --- /dev/null +++ b/Encrypted Ink/Wallets/WalletsManager.swift @@ -0,0 +1,132 @@ +// Copyright © 2021 Encrypted Ink. All rights reserved. +// Rewrite of KeyStore.swift from Trust Wallet Core. + +import Foundation +import WalletCore + +final class WalletsManager { + + private let keychain = Keychain.shared + private(set) var wallets = [InkWallet]() + + init() throws { + try load() + } + + private func load() throws { + let ids = keychain.getAllWalletsIds() + for id in ids { + guard let data = keychain.getWalletData(id: id), let key = StoredKey.importJSON(json: data) else { continue } + let wallet = InkWallet(id: id, key: key) + wallets.append(wallet) + } + } + + 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 + } + + 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) + } + + func checkMnemonic(_ data: Data) -> String? { + guard let mnemonic = String(data: data, encoding: .ascii), Mnemonic.isValid(mnemonic: mnemonic) else { return nil } + return mnemonic + } + + 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 + } + + 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, password: String) throws -> Data { + guard let key = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } + return key + } + + func exportMnemonic(wallet: InkWallet, password: String) throws -> String { + 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) + } + + private func update(wallet: InkWallet, password: String, newPassword: String, newName: String) throws { + guard let index = wallets.firstIndex(of: wallet) else { throw KeyStore.Error.accountNotFound } + guard var privateKeyData = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } + defer { privateKeyData.resetBytes(in: 0 ..< privateKeyData.count) } + let coins = wallet.accounts.map({ $0.coin }) + guard !coins.isEmpty else { throw KeyStore.Error.accountNotFound } + + if let mnemonic = checkMnemonic(privateKeyData), + let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { + wallets[index].key = key + } else if let key = StoredKey.importPrivateKey( + privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { + wallets[index].key = key + } else { + throw KeyStore.Error.invalidKey + } + + _ = try wallets[index].getAccounts(password: newPassword, coins: coins) + try save(wallet: wallets[index]) + } + + func delete(wallet: InkWallet, password: String) throws { + 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 + } + +} From d85329f4ee82abbed1f8248a34aff5185f01adb2 Mon Sep 17 00:00:00 2001 From: Ivan Grachev Date: Sun, 1 Aug 2021 01:22:07 +0300 Subject: [PATCH 02/21] Mark legacy in keychain --- Encrypted Ink/Services/Keychain.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Encrypted Ink/Services/Keychain.swift b/Encrypted Ink/Services/Keychain.swift index 129b0fa6..92205f92 100644 --- a/Encrypted Ink/Services/Keychain.swift +++ b/Encrypted Ink/Services/Keychain.swift @@ -28,6 +28,8 @@ struct Keychain { save(data: data, key: .password) } + // MARK: - Legacy + var accounts: [AccountWithKey] { if let data = get(key: .accounts), let accounts = try? JSONDecoder().decode([AccountWithKey].self, from: data) { return accounts From 5f9cb396a6cf41f10cade0671d4c0b59111331c2 Mon Sep 17 00:00:00 2001 From: Ivan Grachev Date: Sun, 1 Aug 2021 02:44:44 +0300 Subject: [PATCH 03/21] Actually store keystores in keychain --- Encrypted Ink/Services/Keychain.swift | 64 ++++++++++++++++++--------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/Encrypted Ink/Services/Keychain.swift b/Encrypted Ink/Services/Keychain.swift index 92205f92..92fd1fdf 100644 --- a/Encrypted Ink/Services/Keychain.swift +++ b/Encrypted Ink/Services/Keychain.swift @@ -4,15 +4,32 @@ import Foundation struct Keychain { - private let prefix = "ink.encrypted.macos." - private init() {} static let shared = Keychain() - private enum Key: String { - case accounts = "ethereum.keys" - case password = "password" + private enum ItemKey { + + static let commonPrefix = "ink.encrypted.macos." + static let walletPrefix = "wallet." + + case accounts + case password + case wallet(id: String) + + var stringValue: String { + let key: String + switch self { + case .accounts: + key = "ethereum.keys" + case .password: + key = "password" + case let .wallet(id: id): + key = ItemKey.walletPrefix + id + } + return ItemKey.commonPrefix + key + } + } var password: String? { @@ -51,39 +68,44 @@ struct Keychain { } func getWalletData(id: String) -> Data? { - // TODO: implement - return nil + return get(key: .wallet(id: id)) } func saveWallet(id: String, data: Data) throws { - // TODO: implement + save(data: data, key: .wallet(id: id)) } func removeWallet(id: String) throws { - // TODO: implement + removeData(forKey: .wallet(id: id)) } func removeAllWallets() throws { - // TODO: implement + for id in getAllWalletsIds() { + removeData(forKey: .wallet(id: id)) + } } // MARK: Private - private func save(data: Data, key: Key) { - let query = [kSecClass as String: kSecClassGenericPassword as String, - kSecAttrAccount as String: prefix + key.rawValue, - kSecValueData as String: data] as [String: Any] + private func save(data: Data, key: ItemKey) { + let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String, + kSecAttrAccount as String: key.stringValue, + kSecValueData as String: data] SecItemDelete(query as CFDictionary) SecItemAdd(query as CFDictionary, nil) } - private func get(key: Key) -> Data? { - guard let returnDataQueryValue = kCFBooleanTrue else { return nil } - let query = [kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: prefix + key.rawValue, - kSecReturnData as String: returnDataQueryValue, - kSecMatchLimit as String: kSecMatchLimitOne] as [String: Any] - + private func removeData(forKey key: ItemKey) { + let query = [kSecClass as String: kSecClassGenericPassword as String, + kSecAttrAccount as String: key.stringValue] + SecItemDelete(query as CFDictionary) + } + + private func get(key: ItemKey) -> Data? { + let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key.stringValue, + kSecReturnData as String: true, + kSecMatchLimit as String: kSecMatchLimitOne] var dataTypeRef: AnyObject? let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) if status == noErr, let data = dataTypeRef as? Data { From 83876a44879797ad1f5a0620f944f7dbb1e620ca Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 12:38:30 +0300 Subject: [PATCH 04/21] Refactor keychain access --- Encrypted Ink/Services/Keychain.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Encrypted Ink/Services/Keychain.swift b/Encrypted Ink/Services/Keychain.swift index 92fd1fdf..06662b15 100644 --- a/Encrypted Ink/Services/Keychain.swift +++ b/Encrypted Ink/Services/Keychain.swift @@ -88,15 +88,19 @@ struct Keychain { // MARK: Private private func save(data: Data, key: ItemKey) { - let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String, + let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key.stringValue, kSecValueData as String: data] SecItemDelete(query as CFDictionary) SecItemAdd(query as CFDictionary, nil) } + private func allStoredItemsKeys() -> [String] { + return [] + } + private func removeData(forKey key: ItemKey) { - let query = [kSecClass as String: kSecClassGenericPassword as String, + let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key.stringValue] SecItemDelete(query as CFDictionary) } @@ -106,9 +110,9 @@ struct Keychain { kSecAttrAccount as String: key.stringValue, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne] - var dataTypeRef: AnyObject? - let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) - if status == noErr, let data = dataTypeRef as? Data { + var item: CFTypeRef? + let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &item) + if status == noErr, let data = item as? Data { return data } else { return nil From 344e0102378bdf9013d1bf2f46a01da8053bfbcf Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 13:31:27 +0300 Subject: [PATCH 05/21] List all wallets ids from keychain --- Encrypted Ink/Services/Keychain.swift | 31 +++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Encrypted Ink/Services/Keychain.swift b/Encrypted Ink/Services/Keychain.swift index 06662b15..9ad6e92f 100644 --- a/Encrypted Ink/Services/Keychain.swift +++ b/Encrypted Ink/Services/Keychain.swift @@ -9,14 +9,15 @@ struct Keychain { static let shared = Keychain() private enum ItemKey { - - static let commonPrefix = "ink.encrypted.macos." - static let walletPrefix = "wallet." - case accounts case password case wallet(id: String) + private static let commonPrefix = "ink.encrypted.macos." + private static let walletPrefix = "wallet." + private static let fullWalletPrefix = commonPrefix + walletPrefix + private static let fullWalletPrefixCount = fullWalletPrefix.count + var stringValue: String { let key: String switch self { @@ -30,6 +31,11 @@ struct Keychain { return ItemKey.commonPrefix + key } + static func walletId(key: String) -> String? { + guard key.hasPrefix(fullWalletPrefix) else { return nil } + return String(key.dropFirst(fullWalletPrefixCount)) + } + } var password: String? { @@ -63,8 +69,9 @@ struct Keychain { // MARK: - WalletCore func getAllWalletsIds() -> [String] { - // TODO: implement - return [] + let allKeys = allStoredItemsKeys() + let ids = allKeys.compactMap { ItemKey.walletId(key: $0) } + return ids } func getWalletData(id: String) -> Data? { @@ -96,7 +103,17 @@ struct Keychain { } private func allStoredItemsKeys() -> [String] { - return [] + let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, + kSecReturnData as String: false, + kSecReturnAttributes as String: true, + kSecMatchLimit as String: kSecMatchLimitAll] + var items: CFTypeRef? + let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &items) + if status == noErr, let items = items as? [[String: Any]], !items.isEmpty { + return items.compactMap { $0[kSecAttrAccount as String] as? String } + } else { + return [] + } } private func removeData(forKey key: ItemKey) { From 815e82610c905b7d7a43c9aa179f9ac9b5e89dc4 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 13:53:13 +0300 Subject: [PATCH 06/21] Start WalletsManager on app launch --- Encrypted Ink/AppDelegate.swift | 2 ++ Encrypted Ink/Wallets/WalletsManager.swift | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Encrypted Ink/AppDelegate.swift b/Encrypted Ink/AppDelegate.swift index dec1818f..a2153220 100644 --- a/Encrypted Ink/AppDelegate.swift +++ b/Encrypted Ink/AppDelegate.swift @@ -9,6 +9,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { private let gasService = GasService.shared private let priceService = PriceService.shared private let networkMonitor = NetworkMonitor.shared + private let walletsManager = WalletsManager.shared private var didFinishLaunching = false private var initialInputLink: String? @@ -34,6 +35,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { gasService.start() priceService.start() networkMonitor.start() + walletsManager.start() didFinishLaunching = true if let link = initialInputLink { diff --git a/Encrypted Ink/Wallets/WalletsManager.swift b/Encrypted Ink/Wallets/WalletsManager.swift index 8d9de1fc..259e0056 100644 --- a/Encrypted Ink/Wallets/WalletsManager.swift +++ b/Encrypted Ink/Wallets/WalletsManager.swift @@ -6,13 +6,16 @@ import WalletCore final class WalletsManager { + static let shared = WalletsManager() private let keychain = Keychain.shared private(set) var wallets = [InkWallet]() - init() throws { - try load() - } + private init() {} + func start() { + try? load() + } + private func load() throws { let ids = keychain.getAllWalletsIds() for id in ids { From fff817ae8cb576884e3da26a30759c48ffb3ce6e Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 13:58:39 +0300 Subject: [PATCH 07/21] AccountWithKey -> LegacyAccountWithKey --- Encrypted Ink.xcodeproj/project.pbxproj | 8 ++++---- Encrypted Ink/Ethereum/Ethereum.swift | 10 +++++----- ...countWithKey.swift => LegacyAccountWithKey.swift} | 2 +- .../Screens/AccountsListViewController.swift | 6 +++--- Encrypted Ink/Screens/ImportViewController.swift | 2 +- Encrypted Ink/Services/AccountsService.swift | 12 ++++++------ 6 files changed, 20 insertions(+), 20 deletions(-) rename Encrypted Ink/Ethereum/{AccountWithKey.swift => LegacyAccountWithKey.swift} (76%) diff --git a/Encrypted Ink.xcodeproj/project.pbxproj b/Encrypted Ink.xcodeproj/project.pbxproj index d89a08ce..7c62623e 100644 --- a/Encrypted Ink.xcodeproj/project.pbxproj +++ b/Encrypted Ink.xcodeproj/project.pbxproj @@ -57,7 +57,7 @@ 2CD0B3F726AC619900488D92 /* AddAccountOptionCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0B3F626AC619900488D92 /* AddAccountOptionCellView.swift */; }; 2CDAB3722675B3F0009F8B97 /* PasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDAB3712675B3F0009F8B97 /* PasswordViewController.swift */; }; 2CE3D012267F73C00032A62E /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE3D011267F73C00032A62E /* Transaction.swift */; }; - 2CE3D015267F73E80032A62E /* AccountWithKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE3D014267F73E80032A62E /* AccountWithKey.swift */; }; + 2CE3D015267F73E80032A62E /* LegacyAccountWithKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE3D014267F73E80032A62E /* LegacyAccountWithKey.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -113,7 +113,7 @@ 2CD0B3F626AC619900488D92 /* AddAccountOptionCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountOptionCellView.swift; sourceTree = ""; }; 2CDAB3712675B3F0009F8B97 /* PasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordViewController.swift; sourceTree = ""; }; 2CE3D011267F73C00032A62E /* Transaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transaction.swift; sourceTree = ""; }; - 2CE3D014267F73E80032A62E /* AccountWithKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWithKey.swift; sourceTree = ""; }; + 2CE3D014267F73E80032A62E /* LegacyAccountWithKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyAccountWithKey.swift; sourceTree = ""; }; 35AD6E3AC630C8A9B4EC16D9 /* Pods-Encrypted Ink.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Encrypted Ink.release.xcconfig"; path = "Target Support Files/Pods-Encrypted Ink/Pods-Encrypted Ink.release.xcconfig"; sourceTree = ""; }; 3E2A642C960E4952955E6E82 /* Pods_Encrypted_Ink.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Encrypted_Ink.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A858B0F7D88913EAB1FA50B0 /* Pods-Encrypted Ink.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Encrypted Ink.debug.xcconfig"; path = "Target Support Files/Pods-Encrypted Ink/Pods-Encrypted Ink.debug.xcconfig"; sourceTree = ""; }; @@ -225,7 +225,7 @@ isa = PBXGroup; children = ( 2C1995552674D0F300A8E370 /* Ethereum.swift */, - 2CE3D014267F73E80032A62E /* AccountWithKey.swift */, + 2CE3D014267F73E80032A62E /* LegacyAccountWithKey.swift */, 2CE3D011267F73C00032A62E /* Transaction.swift */, 0DB729012674E2DB0011F7A1 /* EIP712 */, ); @@ -464,7 +464,7 @@ 2C901C4D268A033100D0926A /* GasService.swift in Sources */, 2C528A16267FA8EB00CA3ADD /* Defaults.swift in Sources */, 2CD0B3F526A0DAA900488D92 /* NSPasteboard.swift in Sources */, - 2CE3D015267F73E80032A62E /* AccountWithKey.swift in Sources */, + 2CE3D015267F73E80032A62E /* LegacyAccountWithKey.swift in Sources */, 2CE3D012267F73C00032A62E /* Transaction.swift in Sources */, 0DB7291B2674E2DB0011F7A1 /* EIP712Hash.swift in Sources */, 0DB729172674E2DB0011F7A1 /* EIP712StructType.swift in Sources */, diff --git a/Encrypted Ink/Ethereum/Ethereum.swift b/Encrypted Ink/Ethereum/Ethereum.swift index dc87f781..05a7e61c 100644 --- a/Encrypted Ink/Ethereum/Ethereum.swift +++ b/Encrypted Ink/Ethereum/Ethereum.swift @@ -21,7 +21,7 @@ struct Ethereum { apiKey: Secrets.alchemy ) - func sign(message: String, account: AccountWithKey) throws -> String { + func sign(message: String, account: LegacyAccountWithKey) throws -> String { let ethPrivateKey = EthPrivateKey(hex: account.privateKey) let signature = SECP256k1Signature( @@ -39,14 +39,14 @@ struct Ethereum { return data.toPrefixedHexString() } - func signPersonal(message: String, account: AccountWithKey) throws -> String { + func signPersonal(message: String, account: LegacyAccountWithKey) throws -> String { let ethPrivateKey = EthPrivateKey(hex: account.privateKey) let signed = SignedPersonalMessageBytes(message: message, signerKey: ethPrivateKey) let data = try signed.value().toPrefixedHexString() return data } - func sign(typedData: String, account: AccountWithKey) throws -> String { + func sign(typedData: String, account: LegacyAccountWithKey) throws -> String { let data = try EIP712TypedData(jsonString: typedData) let hash = EIP712Hash(domain: data.domain, typedData: data) let privateKey = EthPrivateKey(hex: account.privateKey) @@ -54,7 +54,7 @@ struct Ethereum { return try signer.signatureData(hash: hash).toPrefixedHexString() } - func send(transaction: Transaction, account: AccountWithKey) throws -> String { + func send(transaction: Transaction, account: LegacyAccountWithKey) throws -> String { let bytes = signedTransactionBytes(transaction: transaction, account: account) let response = try SendRawTransactionProcedure(network: network, transactionBytes: bytes).call() guard let hash = response["result"].string else { @@ -63,7 +63,7 @@ struct Ethereum { return hash } - private func signedTransactionBytes(transaction: Transaction, account: AccountWithKey) -> EthContractCallBytes { + private func signedTransactionBytes(transaction: Transaction, account: LegacyAccountWithKey) -> EthContractCallBytes { let senderKey = EthPrivateKey(hex: account.privateKey) let contractAddress = EthAddress(hex: transaction.to) let functionCall = BytesFromHexString(hex: transaction.data) diff --git a/Encrypted Ink/Ethereum/AccountWithKey.swift b/Encrypted Ink/Ethereum/LegacyAccountWithKey.swift similarity index 76% rename from Encrypted Ink/Ethereum/AccountWithKey.swift rename to Encrypted Ink/Ethereum/LegacyAccountWithKey.swift index 89449468..bb559c73 100644 --- a/Encrypted Ink/Ethereum/AccountWithKey.swift +++ b/Encrypted Ink/Ethereum/LegacyAccountWithKey.swift @@ -2,7 +2,7 @@ import Foundation -struct AccountWithKey: Codable { +struct LegacyAccountWithKey: Codable { let privateKey: String let address: String } diff --git a/Encrypted Ink/Screens/AccountsListViewController.swift b/Encrypted Ink/Screens/AccountsListViewController.swift index 29609883..72de30f3 100644 --- a/Encrypted Ink/Screens/AccountsListViewController.swift +++ b/Encrypted Ink/Screens/AccountsListViewController.swift @@ -6,13 +6,13 @@ class AccountsListViewController: NSViewController { private let agent = Agent.shared private let accountsService = AccountsService.shared - private var accounts = [AccountWithKey]() + private var accounts = [LegacyAccountWithKey]() private var cellModels = [CellModel]() - var onSelectedAccount: ((AccountWithKey) -> Void)? + var onSelectedAccount: ((LegacyAccountWithKey) -> Void)? enum CellModel { - case account(AccountWithKey) + case account(LegacyAccountWithKey) case addAccountOption(AddAccountOption) } diff --git a/Encrypted Ink/Screens/ImportViewController.swift b/Encrypted Ink/Screens/ImportViewController.swift index 842546de..60f1e3b4 100644 --- a/Encrypted Ink/Screens/ImportViewController.swift +++ b/Encrypted Ink/Screens/ImportViewController.swift @@ -5,7 +5,7 @@ import Cocoa class ImportViewController: NSViewController { private let accountsService = AccountsService.shared - var onSelectedAccount: ((AccountWithKey) -> Void)? + var onSelectedAccount: ((LegacyAccountWithKey) -> Void)? private var inputValidationResult = AccountsService.InputValidationResult.invalid @IBOutlet weak var textField: NSTextField! { diff --git a/Encrypted Ink/Services/AccountsService.swift b/Encrypted Ink/Services/AccountsService.swift index b3f32807..acde7e9c 100644 --- a/Encrypted Ink/Services/AccountsService.swift +++ b/Encrypted Ink/Services/AccountsService.swift @@ -31,7 +31,7 @@ struct AccountsService { _ = saveAccount(privateKey: privateKey) } - func addAccount(input: String, password: String?) -> AccountWithKey? { + func addAccount(input: String, password: String?) -> LegacyAccountWithKey? { let key: PrivateKey if Mnemonic.isValid(mnemonic: input) { key = HDWallet(mnemonic: input, passphrase: "").getKeyForCoin(coin: .ethereum) @@ -52,10 +52,10 @@ struct AccountsService { return account } - private func saveAccount(privateKey: PrivateKey) -> AccountWithKey? { + private func saveAccount(privateKey: PrivateKey) -> LegacyAccountWithKey? { let address = CoinType.ethereum.deriveAddress(privateKey: privateKey).lowercased() // TODO: use checksum address - let account = AccountWithKey(privateKey: privateKey.data.hexString, address: address) + let account = LegacyAccountWithKey(privateKey: privateKey.data.hexString, address: address) var accounts = getAccounts() guard !accounts.contains(where: { $0.address == address }) else { return nil } accounts.append(account) @@ -63,17 +63,17 @@ struct AccountsService { return account } - func removeAccount(_ account: AccountWithKey) { + func removeAccount(_ account: LegacyAccountWithKey) { var accounts = getAccounts() accounts.removeAll(where: {$0.address == account.address }) keychain.save(accounts: accounts) } - func getAccounts() -> [AccountWithKey] { + func getAccounts() -> [LegacyAccountWithKey] { return keychain.accounts } - func getAccountForAddress(_ address: String) -> AccountWithKey? { + func getAccountForAddress(_ address: String) -> LegacyAccountWithKey? { let allAccounts = getAccounts() return allAccounts.first(where: { $0.address == address.lowercased() }) } From 82a505c57343eac2158ea08de0610608b3d7303f Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 14:48:26 +0300 Subject: [PATCH 08/21] AccountWithKey -> LegacyAccountWithKey --- Encrypted Ink/Agent.swift | 6 +++--- Encrypted Ink/Services/Keychain.swift | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Encrypted Ink/Agent.swift b/Encrypted Ink/Agent.swift index 668db256..87196f55 100644 --- a/Encrypted Ink/Agent.swift +++ b/Encrypted Ink/Agent.swift @@ -120,7 +120,7 @@ class Agent: NSObject { showInitialScreen(wcSession: session) } - func getAccountSelectionCompletionIfShouldSelect() -> ((AccountWithKey) -> Void)? { + func getAccountSelectionCompletionIfShouldSelect() -> ((LegacyAccountWithKey) -> Void)? { let session = getSessionFromPasteboard() return onSelectedAccount(session: session) } @@ -223,7 +223,7 @@ class Agent: NSObject { } } - private func onSelectedAccount(session: WCSession?) -> ((AccountWithKey) -> Void)? { + private func onSelectedAccount(session: WCSession?) -> ((LegacyAccountWithKey) -> Void)? { guard let session = session else { return nil } return { [weak self] account in self?.connectWallet(session: session, account: account) @@ -285,7 +285,7 @@ class Agent: NSObject { } } - private func connectWallet(session: WCSession, account: AccountWithKey) { + private func connectWallet(session: WCSession, account: LegacyAccountWithKey) { let windowController = Window.showNew() let window = windowController.window windowController.contentViewController = WaitingViewController.withReason("Connecting") diff --git a/Encrypted Ink/Services/Keychain.swift b/Encrypted Ink/Services/Keychain.swift index 9ad6e92f..e3349dc2 100644 --- a/Encrypted Ink/Services/Keychain.swift +++ b/Encrypted Ink/Services/Keychain.swift @@ -53,15 +53,15 @@ struct Keychain { // MARK: - Legacy - var accounts: [AccountWithKey] { - if let data = get(key: .accounts), let accounts = try? JSONDecoder().decode([AccountWithKey].self, from: data) { + var accounts: [LegacyAccountWithKey] { + if let data = get(key: .accounts), let accounts = try? JSONDecoder().decode([LegacyAccountWithKey].self, from: data) { return accounts } else { return [] } } - func save(accounts: [AccountWithKey]) { + func save(accounts: [LegacyAccountWithKey]) { guard let data = try? JSONEncoder().encode(accounts) else { return } save(data: data, key: .accounts) } From cf3c87c55e7c2b3e565497c1c45d959492f12a3b Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 15:26:27 +0300 Subject: [PATCH 09/21] Migrate legacy keychain accounts --- Encrypted Ink/Services/AccountsService.swift | 4 ++-- Encrypted Ink/Services/Keychain.swift | 22 ++++++++++++++------ Encrypted Ink/Wallets/WalletsManager.swift | 12 +++++++++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Encrypted Ink/Services/AccountsService.swift b/Encrypted Ink/Services/AccountsService.swift index acde7e9c..226d2784 100644 --- a/Encrypted Ink/Services/AccountsService.swift +++ b/Encrypted Ink/Services/AccountsService.swift @@ -59,14 +59,14 @@ struct AccountsService { var accounts = getAccounts() guard !accounts.contains(where: { $0.address == address }) else { return nil } accounts.append(account) - keychain.save(accounts: accounts) + try? keychain.save(accounts: accounts) return account } func removeAccount(_ account: LegacyAccountWithKey) { var accounts = getAccounts() accounts.removeAll(where: {$0.address == account.address }) - keychain.save(accounts: accounts) + try? keychain.save(accounts: accounts) } func getAccounts() -> [LegacyAccountWithKey] { diff --git a/Encrypted Ink/Services/Keychain.swift b/Encrypted Ink/Services/Keychain.swift index e3349dc2..70460359 100644 --- a/Encrypted Ink/Services/Keychain.swift +++ b/Encrypted Ink/Services/Keychain.swift @@ -9,7 +9,7 @@ struct Keychain { static let shared = Keychain() private enum ItemKey { - case accounts + case legacyAccounts case password case wallet(id: String) @@ -21,7 +21,7 @@ struct Keychain { var stringValue: String { let key: String switch self { - case .accounts: + case .legacyAccounts: key = "ethereum.keys" case .password: key = "password" @@ -53,17 +53,27 @@ struct Keychain { // MARK: - Legacy - var accounts: [LegacyAccountWithKey] { - if let data = get(key: .accounts), let accounts = try? JSONDecoder().decode([LegacyAccountWithKey].self, from: data) { + func getLegacyAccounts() throws -> [LegacyAccountWithKey] { + if let data = get(key: .legacyAccounts), let accounts = try? JSONDecoder().decode([LegacyAccountWithKey].self, from: data) { return accounts } else { return [] } } - func save(accounts: [LegacyAccountWithKey]) { + func removeLegacyAccounts() throws { + removeData(forKey: .legacyAccounts) + } + + // TODO: remove + var accounts: [LegacyAccountWithKey] { + return (try? getLegacyAccounts()) ?? [] + } + + // TODO: remove + func save(accounts: [LegacyAccountWithKey]) throws { guard let data = try? JSONEncoder().encode(accounts) else { return } - save(data: data, key: .accounts) + save(data: data, key: .legacyAccounts) } // MARK: - WalletCore diff --git a/Encrypted Ink/Wallets/WalletsManager.swift b/Encrypted Ink/Wallets/WalletsManager.swift index 259e0056..0f62c293 100644 --- a/Encrypted Ink/Wallets/WalletsManager.swift +++ b/Encrypted Ink/Wallets/WalletsManager.swift @@ -14,6 +14,18 @@ final class WalletsManager { func start() { try? load() + try? migrateFromLegacyIfNeeded() + } + + private func migrateFromLegacyIfNeeded() throws { + let legacyAccountsWithKeys = try keychain.getLegacyAccounts() + guard !legacyAccountsWithKeys.isEmpty, let password = keychain.password else { return } + for legacyAccount in legacyAccountsWithKeys { + if let data = Data(hexString: legacyAccount.privateKey), let privateKey = PrivateKey(data: data) { + _ = try importPrivateKey(privateKey, name: legacyAccount.address, password: password, coin: .ethereum) + } + } + try keychain.removeLegacyAccounts() } private func load() throws { From 75eadd24416f02b651312abdac8928f7cbfef1c3 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 17:12:11 +0300 Subject: [PATCH 10/21] Remove AccountsService --- Encrypted Ink.xcodeproj/project.pbxproj | 8 +- Encrypted Ink/Extensions/String.swift | 11 +++ Encrypted Ink/Services/AccountsService.swift | 89 -------------------- 3 files changed, 15 insertions(+), 93 deletions(-) create mode 100644 Encrypted Ink/Extensions/String.swift delete mode 100644 Encrypted Ink/Services/AccountsService.swift diff --git a/Encrypted Ink.xcodeproj/project.pbxproj b/Encrypted Ink.xcodeproj/project.pbxproj index 7c62623e..6b1894cc 100644 --- a/Encrypted Ink.xcodeproj/project.pbxproj +++ b/Encrypted Ink.xcodeproj/project.pbxproj @@ -31,10 +31,10 @@ 2C1995562674D0F300A8E370 /* Ethereum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1995552674D0F300A8E370 /* Ethereum.swift */; }; 2C208A9F26813408005BA500 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C208A9E26813408005BA500 /* Secrets.swift */; }; 2C528A16267FA8EB00CA3ADD /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C528A15267FA8EB00CA3ADD /* Defaults.swift */; }; + 2C603D0226B6E13F00956955 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C603D0126B6E13F00956955 /* String.swift */; }; 2C6706A5267A6BFE006AAEF2 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6706A4267A6BFE006AAEF2 /* Bundle.swift */; }; 2C78F8282683BDCC00C10670 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78F8272683BDCC00C10670 /* Alert.swift */; }; 2C797E7E267BB88800F2CE2D /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C797E7D267BB88800F2CE2D /* WelcomeViewController.swift */; }; - 2C8A09B52675101300993638 /* AccountsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09B42675101300993638 /* AccountsService.swift */; }; 2C8A09C6267513FC00993638 /* Agent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09C5267513FC00993638 /* Agent.swift */; }; 2C8A09D42675184700993638 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09D32675184700993638 /* Window.swift */; }; 2C8A09D726751A0C00993638 /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09D626751A0C00993638 /* WalletConnect.swift */; }; @@ -87,10 +87,10 @@ 2C1995552674D0F300A8E370 /* Ethereum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ethereum.swift; sourceTree = ""; }; 2C208A9E26813408005BA500 /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; 2C528A15267FA8EB00CA3ADD /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = ""; }; + 2C603D0126B6E13F00956955 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 2C6706A4267A6BFE006AAEF2 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; 2C78F8272683BDCC00C10670 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; 2C797E7D267BB88800F2CE2D /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; - 2C8A09B42675101300993638 /* AccountsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsService.swift; sourceTree = ""; }; 2C8A09C5267513FC00993638 /* Agent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Agent.swift; sourceTree = ""; }; 2C8A09D32675184700993638 /* Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; 2C8A09D626751A0C00993638 /* WalletConnect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnect.swift; sourceTree = ""; }; @@ -215,6 +215,7 @@ children = ( 2C6706A4267A6BFE006AAEF2 /* Bundle.swift */, 2C03D1D4269B428C00EF10EA /* Notification.swift */, + 2C603D0126B6E13F00956955 /* String.swift */, 2CD0B3F426A0DAA900488D92 /* NSPasteboard.swift */, 2CC89470269A334A00879245 /* UserDefaults.swift */, ); @@ -261,7 +262,6 @@ 2C91742B267D2A7900049075 /* Services */ = { isa = PBXGroup; children = ( - 2C8A09B42675101300993638 /* AccountsService.swift */, 2C03D1D1269B407900EF10EA /* NetworkMonitor.swift */, 2C901C4C268A033100D0926A /* GasService.swift */, 2CC0CDBD2692027E0072922A /* PriceService.swift */, @@ -446,6 +446,7 @@ 2C8A09E326757FC000993638 /* AccountCellView.swift in Sources */, 0DB729202674E2DB0011F7A1 /* EIP712Domain.swift in Sources */, 0DB729182674E2DB0011F7A1 /* EIP712Signer.swift in Sources */, + 2C603D0226B6E13F00956955 /* String.swift in Sources */, 2CC89471269A334A00879245 /* UserDefaults.swift in Sources */, 2C78F8282683BDCC00C10670 /* Alert.swift in Sources */, 2C8A09EE2675965F00993638 /* WaitingViewController.swift in Sources */, @@ -457,7 +458,6 @@ 2C1995422674C4B900A8E370 /* ImportViewController.swift in Sources */, 2C8E47A326A322E8007B8354 /* RightClickTableView.swift in Sources */, 2C901C472689E6D400D0926A /* ApproveTransactionViewController.swift in Sources */, - 2C8A09B52675101300993638 /* AccountsService.swift in Sources */, 2CDAB3722675B3F0009F8B97 /* PasswordViewController.swift in Sources */, 2C1995402674C4B900A8E370 /* AppDelegate.swift in Sources */, 0DB729142674E2DB0011F7A1 /* EIP712Parameter.swift in Sources */, diff --git a/Encrypted Ink/Extensions/String.swift b/Encrypted Ink/Extensions/String.swift new file mode 100644 index 00000000..a4c03924 --- /dev/null +++ b/Encrypted Ink/Extensions/String.swift @@ -0,0 +1,11 @@ +// Copyright © 2021 Encrypted Ink. All rights reserved. + +import Foundation + +extension String { + + var maybeJSON: Bool { + return hasPrefix("{") && hasSuffix("}") && count > 3 + } + +} diff --git a/Encrypted Ink/Services/AccountsService.swift b/Encrypted Ink/Services/AccountsService.swift deleted file mode 100644 index 226d2784..00000000 --- a/Encrypted Ink/Services/AccountsService.swift +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright © 2021 Encrypted Ink. All rights reserved. - -import Foundation -import WalletCore - -struct AccountsService { - - private init() {} - private let keychain = Keychain.shared - - static let shared = AccountsService() - - enum InputValidationResult { - case valid, invalid, requiresPassword - } - - func validateAccountInput(_ 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 createAccount() { - guard let password = keychain.password?.data(using: .utf8) else { return } - let key = StoredKey(name: "", password: password) - guard let privateKey = key.wallet(password: password)?.getKeyForCoin(coin: .ethereum) else { return } - _ = saveAccount(privateKey: privateKey) - } - - func addAccount(input: String, password: String?) -> LegacyAccountWithKey? { - let key: PrivateKey - if Mnemonic.isValid(mnemonic: input) { - key = HDWallet(mnemonic: input, passphrase: "").getKeyForCoin(coin: .ethereum) - } else if let data = Data(hexString: input), let privateKey = PrivateKey(data: data) { - key = privateKey - } else if input.maybeJSON, - let password = password, - let json = input.data(using: .utf8), - let jsonKey = StoredKey.importJSON(json: json), - let data = jsonKey.decryptPrivateKey(password: Data(password.utf8)), - let privateKey = PrivateKey(data: data) { - key = privateKey - } else { - return nil - } - - let account = saveAccount(privateKey: key) - return account - } - - private func saveAccount(privateKey: PrivateKey) -> LegacyAccountWithKey? { - let address = CoinType.ethereum.deriveAddress(privateKey: privateKey).lowercased() - // TODO: use checksum address - let account = LegacyAccountWithKey(privateKey: privateKey.data.hexString, address: address) - var accounts = getAccounts() - guard !accounts.contains(where: { $0.address == address }) else { return nil } - accounts.append(account) - try? keychain.save(accounts: accounts) - return account - } - - func removeAccount(_ account: LegacyAccountWithKey) { - var accounts = getAccounts() - accounts.removeAll(where: {$0.address == account.address }) - try? keychain.save(accounts: accounts) - } - - func getAccounts() -> [LegacyAccountWithKey] { - return keychain.accounts - } - - func getAccountForAddress(_ address: String) -> LegacyAccountWithKey? { - let allAccounts = getAccounts() - return allAccounts.first(where: { $0.address == address.lowercased() }) - } - -} - -private extension String { - - var maybeJSON: Bool { - return hasPrefix("{") && hasSuffix("}") && count > 3 - } - -} From 1f8e398653fd43b8c6c5b513cb9dabbd811afb79 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 18:22:22 +0300 Subject: [PATCH 11/21] Remove accounts legacy from keychain --- Encrypted Ink/Services/Keychain.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Encrypted Ink/Services/Keychain.swift b/Encrypted Ink/Services/Keychain.swift index 70460359..eb8506b9 100644 --- a/Encrypted Ink/Services/Keychain.swift +++ b/Encrypted Ink/Services/Keychain.swift @@ -65,17 +65,6 @@ struct Keychain { removeData(forKey: .legacyAccounts) } - // TODO: remove - var accounts: [LegacyAccountWithKey] { - return (try? getLegacyAccounts()) ?? [] - } - - // TODO: remove - func save(accounts: [LegacyAccountWithKey]) throws { - guard let data = try? JSONEncoder().encode(accounts) else { return } - save(data: data, key: .legacyAccounts) - } - // MARK: - WalletCore func getAllWalletsIds() -> [String] { From 61ac099c9f00b4399f6e369980d42a53584fce69 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 18:23:20 +0300 Subject: [PATCH 12/21] Fix functions order in Keychain --- Encrypted Ink/Services/Keychain.swift | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Encrypted Ink/Services/Keychain.swift b/Encrypted Ink/Services/Keychain.swift index eb8506b9..ac3b686f 100644 --- a/Encrypted Ink/Services/Keychain.swift +++ b/Encrypted Ink/Services/Keychain.swift @@ -51,20 +51,6 @@ struct Keychain { save(data: data, key: .password) } - // MARK: - Legacy - - func getLegacyAccounts() throws -> [LegacyAccountWithKey] { - if let data = get(key: .legacyAccounts), let accounts = try? JSONDecoder().decode([LegacyAccountWithKey].self, from: data) { - return accounts - } else { - return [] - } - } - - func removeLegacyAccounts() throws { - removeData(forKey: .legacyAccounts) - } - // MARK: - WalletCore func getAllWalletsIds() -> [String] { @@ -91,6 +77,20 @@ struct Keychain { } } + // MARK: - Legacy + + func getLegacyAccounts() throws -> [LegacyAccountWithKey] { + if let data = get(key: .legacyAccounts), let accounts = try? JSONDecoder().decode([LegacyAccountWithKey].self, from: data) { + return accounts + } else { + return [] + } + } + + func removeLegacyAccounts() throws { + removeData(forKey: .legacyAccounts) + } + // MARK: Private private func save(data: Data, key: ItemKey) { From 2af469ad3549d1ae873c3c853e376845ffe007b7 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sun, 1 Aug 2021 19:42:27 +0300 Subject: [PATCH 13/21] Complete keychain migration --- Encrypted Ink/Agent.swift | 19 +- Encrypted Ink/Ethereum/Ethereum.swift | 29 ++- .../Screens/AccountsListViewController.swift | 88 ++++---- .../Screens/ImportViewController.swift | 15 +- Encrypted Ink/Services/SessionStorage.swift | 6 +- Encrypted Ink/Views/AccountCellView.swift | 2 +- Encrypted Ink/WalletConnect.swift | 41 ++-- Encrypted Ink/Wallets/InkWallet.swift | 18 ++ Encrypted Ink/Wallets/WalletsManager.swift | 213 +++++++++++------- 9 files changed, 255 insertions(+), 176 deletions(-) diff --git a/Encrypted Ink/Agent.swift b/Encrypted Ink/Agent.swift index 87196f55..2d939b9b 100644 --- a/Encrypted Ink/Agent.swift +++ b/Encrypted Ink/Agent.swift @@ -11,7 +11,6 @@ class Agent: NSObject { private override init() { super.init() } private var statusBarItem: NSStatusItem! - private let accountsService = AccountsService.shared private var hasPassword = Keychain.shared.password != nil private var didEnterPasswordOnStart = false @@ -72,9 +71,9 @@ class Agent: NSObject { } let windowController = Window.showNew() - let completion = onSelectedAccount(session: session) + let completion = onSelectedWallet(session: session) let accountsList = instantiate(AccountsListViewController.self) - accountsList.onSelectedAccount = completion + accountsList.onSelectedWallet = completion windowController.contentViewController = accountsList } @@ -120,9 +119,9 @@ class Agent: NSObject { showInitialScreen(wcSession: session) } - func getAccountSelectionCompletionIfShouldSelect() -> ((LegacyAccountWithKey) -> Void)? { + func getWalletSelectionCompletionIfShouldSelect() -> ((InkWallet) -> Void)? { let session = getSessionFromPasteboard() - return onSelectedAccount(session: session) + return onSelectedWallet(session: session) } lazy private var statusBarMenu: NSMenu = { @@ -223,10 +222,10 @@ class Agent: NSObject { } } - private func onSelectedAccount(session: WCSession?) -> ((LegacyAccountWithKey) -> Void)? { + private func onSelectedWallet(session: WCSession?) -> ((InkWallet) -> Void)? { guard let session = session else { return nil } - return { [weak self] account in - self?.connectWallet(session: session, account: account) + return { [weak self] wallet in + self?.connectWallet(session: session, wallet: wallet) } } @@ -285,12 +284,12 @@ class Agent: NSObject { } } - private func connectWallet(session: WCSession, account: LegacyAccountWithKey) { + private func connectWallet(session: WCSession, wallet: InkWallet) { let windowController = Window.showNew() let window = windowController.window windowController.contentViewController = WaitingViewController.withReason("Connecting") - WalletConnect.shared.connect(session: session, address: account.address) { [weak window] _ in + WalletConnect.shared.connect(session: session, walletId: wallet.id) { [weak window] _ in if window?.isVisible == true { Window.closeAllAndActivateBrowser() } diff --git a/Encrypted Ink/Ethereum/Ethereum.swift b/Encrypted Ink/Ethereum/Ethereum.swift index 05a7e61c..57dd7afd 100644 --- a/Encrypted Ink/Ethereum/Ethereum.swift +++ b/Encrypted Ink/Ethereum/Ethereum.swift @@ -6,9 +6,10 @@ import CryptoSwift struct Ethereum { - enum Errors: Error { + enum Error: Swift.Error { case invalidInputData case failedToSendTransaction + case keyNotFound } private let queue = DispatchQueue(label: "Ethereum", qos: .default) @@ -21,8 +22,9 @@ struct Ethereum { apiKey: Secrets.alchemy ) - func sign(message: String, account: LegacyAccountWithKey) throws -> String { - let ethPrivateKey = EthPrivateKey(hex: account.privateKey) + func sign(message: String, wallet: InkWallet) throws -> String { + guard let privateKeyString = wallet.ethereumPrivateKey else { throw Error.keyNotFound } + let ethPrivateKey = EthPrivateKey(hex: privateKeyString) let signature = SECP256k1Signature( privateKey: ethPrivateKey, @@ -39,32 +41,35 @@ struct Ethereum { return data.toPrefixedHexString() } - func signPersonal(message: String, account: LegacyAccountWithKey) throws -> String { - let ethPrivateKey = EthPrivateKey(hex: account.privateKey) + func signPersonal(message: String, wallet: InkWallet) throws -> String { + guard let privateKeyString = wallet.ethereumPrivateKey else { throw Error.keyNotFound } + let ethPrivateKey = EthPrivateKey(hex: privateKeyString) let signed = SignedPersonalMessageBytes(message: message, signerKey: ethPrivateKey) let data = try signed.value().toPrefixedHexString() return data } - func sign(typedData: String, account: LegacyAccountWithKey) throws -> String { + func sign(typedData: String, wallet: InkWallet) throws -> String { + guard let privateKeyString = wallet.ethereumPrivateKey else { throw Error.keyNotFound } let data = try EIP712TypedData(jsonString: typedData) let hash = EIP712Hash(domain: data.domain, typedData: data) - let privateKey = EthPrivateKey(hex: account.privateKey) + let privateKey = EthPrivateKey(hex: privateKeyString) let signer = EIP712Signer(privateKey: privateKey) return try signer.signatureData(hash: hash).toPrefixedHexString() } - func send(transaction: Transaction, account: LegacyAccountWithKey) throws -> String { - let bytes = signedTransactionBytes(transaction: transaction, account: account) + func send(transaction: Transaction, wallet: InkWallet) throws -> String { + let bytes = try signedTransactionBytes(transaction: transaction, wallet: wallet) let response = try SendRawTransactionProcedure(network: network, transactionBytes: bytes).call() guard let hash = response["result"].string else { - throw Errors.failedToSendTransaction + throw Error.failedToSendTransaction } return hash } - private func signedTransactionBytes(transaction: Transaction, account: LegacyAccountWithKey) -> EthContractCallBytes { - let senderKey = EthPrivateKey(hex: account.privateKey) + private func signedTransactionBytes(transaction: Transaction, wallet: InkWallet) throws -> EthContractCallBytes { + guard let privateKeyString = wallet.ethereumPrivateKey else { throw Error.keyNotFound } + let senderKey = EthPrivateKey(hex: privateKeyString) let contractAddress = EthAddress(hex: transaction.to) let functionCall = BytesFromHexString(hex: transaction.data) let bytes: EthContractCallBytes diff --git a/Encrypted Ink/Screens/AccountsListViewController.swift b/Encrypted Ink/Screens/AccountsListViewController.swift index 72de30f3..9617d8fc 100644 --- a/Encrypted Ink/Screens/AccountsListViewController.swift +++ b/Encrypted Ink/Screens/AccountsListViewController.swift @@ -5,14 +5,13 @@ import Cocoa class AccountsListViewController: NSViewController { private let agent = Agent.shared - private let accountsService = AccountsService.shared - private var accounts = [LegacyAccountWithKey]() + private let walletsManager = WalletsManager.shared private var cellModels = [CellModel]() - var onSelectedAccount: ((LegacyAccountWithKey) -> Void)? + var onSelectedWallet: ((InkWallet) -> Void)? enum CellModel { - case account(LegacyAccountWithKey) + case wallet(InkWallet) case addAccountOption(AddAccountOption) } @@ -45,11 +44,14 @@ class AccountsListViewController: NSViewController { } } + private var wallets: [InkWallet] { + return walletsManager.wallets + } + override func viewDidLoad() { super.viewDidLoad() setupAccountsMenu() - reloadAccounts() reloadTitle() updateCellModels() NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSApplication.didBecomeActiveNotification, object: nil) @@ -61,30 +63,26 @@ class AccountsListViewController: NSViewController { menu.addItem(NSMenuItem(title: "Copy address", action: #selector(didClickCopyAddress(_:)), keyEquivalent: "")) menu.addItem(NSMenuItem(title: "View on Zerion", action: #selector(didClickViewOnZerion(_:)), keyEquivalent: "")) menu.addItem(.separator()) - menu.addItem(NSMenuItem(title: "Show private key", action: #selector(didClickExportAccount(_:)), keyEquivalent: "")) // TODO: show different texts for secret words export + menu.addItem(NSMenuItem(title: "Show account key", action: #selector(didClickExportAccount(_:)), keyEquivalent: "")) menu.addItem(NSMenuItem(title: "Remove account", action: #selector(didClickRemoveAccount(_:)), keyEquivalent: "")) menu.addItem(.separator()) menu.addItem(NSMenuItem(title: "How to WalletConnect?", action: #selector(showInstructionsAlert), keyEquivalent: "")) tableView.menu = menu } - private func reloadAccounts() { - accounts = accountsService.getAccounts() - } - deinit { NotificationCenter.default.removeObserver(self) } private func reloadTitle() { - titleLabel.stringValue = onSelectedAccount != nil && !accounts.isEmpty ? "Select\nAccount" : "Accounts" - addButton.isHidden = accounts.isEmpty + titleLabel.stringValue = onSelectedWallet != nil && !wallets.isEmpty ? "Select\nAccount" : "Accounts" + addButton.isHidden = wallets.isEmpty } @objc private func didBecomeActive() { guard view.window?.isVisible == true else { return } - if let completion = agent.getAccountSelectionCompletionIfShouldSelect() { - onSelectedAccount = completion + if let completion = agent.getWalletSelectionCompletionIfShouldSelect() { + onSelectedWallet = completion } reloadTitle() } @@ -107,8 +105,7 @@ class AccountsListViewController: NSViewController { } @objc private func didClickCreateAccount() { - accountsService.createAccount() - reloadAccounts() + _ = try? walletsManager.createWallet() reloadTitle() updateCellModels() tableView.reloadData() @@ -117,14 +114,13 @@ class AccountsListViewController: NSViewController { @objc private func didClickImportAccount() { let importViewController = instantiate(ImportViewController.self) - importViewController.onSelectedAccount = onSelectedAccount + importViewController.onSelectedWallet = onSelectedWallet view.window?.contentViewController = importViewController } @objc private func didClickViewOnZerion(_ sender: AnyObject) { let row = tableView.deselectedRow - guard row >= 0 else { return } - let address = accounts[row].address + guard row >= 0, let address = wallets[row].ethereumAddress else { return } if let url = URL(string: "https://app.zerion.io/\(address)/overview") { NSWorkspace.shared.open(url) } @@ -132,8 +128,8 @@ class AccountsListViewController: NSViewController { @objc private func didClickCopyAddress(_ sender: AnyObject) { let row = tableView.deselectedRow - guard row >= 0 else { return } - NSPasteboard.general.clearAndSetString(accounts[row].address) + guard row >= 0, let address = wallets[row].ethereumAddress else { return } + NSPasteboard.general.clearAndSetString(address) } @objc private func didClickRemoveAccount(_ sender: AnyObject) { @@ -155,34 +151,46 @@ class AccountsListViewController: NSViewController { } @objc private func didClickExportAccount(_ sender: AnyObject) { - // TODO: show different texts for secret words export let row = tableView.deselectedRow guard row >= 0 else { return } + let isMnemonic = wallets[row].isMnemonic let alert = Alert() - alert.messageText = "Private key gives full access to your funds." + + alert.messageText = "\(isMnemonic ? "Secret words give" : "Private key gives") full access to your funds." alert.alertStyle = .critical alert.addButton(withTitle: "I understand the risks") alert.addButton(withTitle: "Cancel") if alert.runModal() == .alertFirstButtonReturn { - agent.askAuthentication(on: view.window, getBackTo: self, onStart: false, reason: "Show private key") { [weak self] allowed in + let reason = "Show \(isMnemonic ? "secret words" : "private key")" + agent.askAuthentication(on: view.window, getBackTo: self, onStart: false, reason: reason) { [weak self] allowed in Window.activateWindow(self?.view.window) if allowed { - self?.showPrivateKey(index: row) + self?.showKey(index: row, mnemonic: isMnemonic) } } } } - private func showPrivateKey(index: Int) { - let privateKey = accounts[index].privateKey + private func showKey(index: Int, mnemonic: Bool) { + let wallet = wallets[index] + + let secret: String + if mnemonic, let mnemonicString = try? walletsManager.exportMnemonic(wallet: wallet) { + secret = mnemonicString + } else if let data = try? walletsManager.exportPrivateKey(wallet: wallet) { + secret = data.hexString + } else { + return + } + let alert = Alert() - alert.messageText = "Private key" - alert.informativeText = privateKey + alert.messageText = mnemonic ? "Secret words" : "Private key" + alert.informativeText = secret alert.alertStyle = .informational alert.addButton(withTitle: "OK") alert.addButton(withTitle: "Copy") if alert.runModal() != .alertFirstButtonReturn { - NSPasteboard.general.clearAndSetString(privateKey) + NSPasteboard.general.clearAndSetString(secret) } } @@ -191,19 +199,19 @@ class AccountsListViewController: NSViewController { } private func removeAccountAtIndex(_ index: Int) { - accountsService.removeAccount(accounts[index]) - accounts.remove(at: index) + let wallet = wallets[index] + try? walletsManager.delete(wallet: wallet) reloadTitle() updateCellModels() tableView.reloadData() } private func updateCellModels() { - if accounts.isEmpty { + if wallets.isEmpty { cellModels = [.addAccountOption(.createNew), .addAccountOption(.importExisting)] tableView.shouldShowRightClickMenu = false } else { - cellModels = accounts.map { .account($0) } + cellModels = wallets.map { .wallet($0) } tableView.shouldShowRightClickMenu = true } } @@ -217,9 +225,9 @@ extension AccountsListViewController: NSTableViewDelegate { let model = cellModels[row] switch model { - case let .account(account): - if let onSelectedAccount = onSelectedAccount { - onSelectedAccount(account) + case let .wallet(wallet): + if let onSelectedWallet = onSelectedWallet { + onSelectedWallet(wallet) } else { Timer.scheduledTimer(withTimeInterval: 0.01, repeats: false) { [weak self] _ in var point = NSEvent.mouseLocation @@ -246,9 +254,9 @@ extension AccountsListViewController: NSTableViewDataSource { func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { let model = cellModels[row] switch model { - case let .account(account): + case let .wallet(wallet): let rowView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("AccountCellView"), owner: self) as? AccountCellView - rowView?.setup(address: account.address) + rowView?.setup(address: wallet.ethereumAddress ?? "") return rowView case let .addAccountOption(addAccountOption): let rowView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("AddAccountOptionCellView"), owner: self) as? AddAccountOptionCellView @@ -258,7 +266,7 @@ extension AccountsListViewController: NSTableViewDataSource { } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { - if case .account = cellModels[row] { + if case .wallet = cellModels[row] { return 50 } else { return 44 diff --git a/Encrypted Ink/Screens/ImportViewController.swift b/Encrypted Ink/Screens/ImportViewController.swift index 60f1e3b4..f35fc619 100644 --- a/Encrypted Ink/Screens/ImportViewController.swift +++ b/Encrypted Ink/Screens/ImportViewController.swift @@ -4,9 +4,9 @@ import Cocoa class ImportViewController: NSViewController { - private let accountsService = AccountsService.shared - var onSelectedAccount: ((LegacyAccountWithKey) -> Void)? - private var inputValidationResult = AccountsService.InputValidationResult.invalid + private let walletsManager = WalletsManager.shared + var onSelectedWallet: ((InkWallet) -> Void)? + private var inputValidationResult = WalletsManager.InputValidationResult.invalid @IBOutlet weak var textField: NSTextField! { didSet { @@ -51,16 +51,17 @@ class ImportViewController: NSViewController { } private func importWith(input: String, password: String?) { - if accountsService.addAccount(input: input, password: password) != nil { + do { + _ = try walletsManager.addWallet(input: input, inputPassword: password) showAccountsList() - } else { + } catch { Alert.showWithMessage("Failed to import account", style: .critical) } } private func showAccountsList() { let accountsListViewController = instantiate(AccountsListViewController.self) - accountsListViewController.onSelectedAccount = onSelectedAccount + accountsListViewController.onSelectedWallet = onSelectedWallet view.window?.contentViewController = accountsListViewController } @@ -73,7 +74,7 @@ class ImportViewController: NSViewController { extension ImportViewController: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { - inputValidationResult = accountsService.validateAccountInput(textField.stringValue) + inputValidationResult = walletsManager.validateWalletInput(textField.stringValue) okButton.isEnabled = inputValidationResult != .invalid } diff --git a/Encrypted Ink/Services/SessionStorage.swift b/Encrypted Ink/Services/SessionStorage.swift index 234c9cb5..e65f548b 100644 --- a/Encrypted Ink/Services/SessionStorage.swift +++ b/Encrypted Ink/Services/SessionStorage.swift @@ -7,7 +7,7 @@ class SessionStorage { struct Item: Codable { let session: WCSession - let address: String + let walletId: String let clientId: String let sessionDetails: WCSessionRequestParam } @@ -51,8 +51,8 @@ class SessionStorage { } } - func add(interactor: WCInteractor, address: String, sessionDetails: WCSessionRequestParam) { - let item = Item(session: interactor.session, address: address, clientId: interactor.clientId, sessionDetails: sessionDetails) + func add(interactor: WCInteractor, walletId: String, sessionDetails: WCSessionRequestParam) { + let item = Item(session: interactor.session, walletId: walletId, clientId: interactor.clientId, sessionDetails: sessionDetails) WCSessionStore.store(interactor.session, peerId: sessionDetails.peerId, peerMeta: sessionDetails.peerMeta) Defaults.storedSessions[interactor.clientId] = item didInteractWith(clientId: interactor.clientId) diff --git a/Encrypted Ink/Views/AccountCellView.swift b/Encrypted Ink/Views/AccountCellView.swift index b252e61a..cdd4ceca 100644 --- a/Encrypted Ink/Views/AccountCellView.swift +++ b/Encrypted Ink/Views/AccountCellView.swift @@ -15,7 +15,7 @@ class AccountCellView: NSTableRowView { @IBOutlet weak var addressTextField: NSTextField! func setup(address: String) { - addressImageView.image = Blockies(seed: address).createImage() + addressImageView.image = Blockies(seed: address.lowercased()).createImage() let without0x = address.dropFirst(2) addressTextField.stringValue = without0x.prefix(4) + "..." + without0x.suffix(4) } diff --git a/Encrypted Ink/WalletConnect.swift b/Encrypted Ink/WalletConnect.swift index 93310e19..449c3064 100644 --- a/Encrypted Ink/WalletConnect.swift +++ b/Encrypted Ink/WalletConnect.swift @@ -8,7 +8,7 @@ class WalletConnect { private let sessionStorage = SessionStorage.shared private let networkMonitor = NetworkMonitor.shared private let ethereum = Ethereum.shared - private let accountsService = AccountsService.shared + private let walletsManager = WalletsManager.shared static let shared = WalletConnect() @@ -28,10 +28,10 @@ class WalletConnect { return WCSession.from(string: link) } - func connect(session: WCSession, address: String, uuid: UUID = UUID(), completion: @escaping ((Bool) -> Void)) { + func connect(session: WCSession, walletId: String, uuid: UUID = UUID(), completion: @escaping ((Bool) -> Void)) { let clientMeta = WCPeerMeta(name: "Encrypted Ink", url: "https://encrypted.ink", description: "Ethereum agent for macOS", icons: ["https://encrypted.ink/icon.png"]) let interactor = WCInteractor(session: session, meta: clientMeta, uuid: uuid) - configure(interactor: interactor, address: address) + configure(interactor: interactor, walletId: walletId) interactor.connect().done { connected in completion(connected) @@ -49,7 +49,7 @@ class WalletConnect { for item in items { guard let uuid = UUID(uuidString: item.clientId) else { continue } - connect(session: item.session, address: item.address, uuid: uuid) { _ in } + connect(session: item.session, walletId: item.walletId, uuid: uuid) { _ in } peers[item.clientId] = item.sessionDetails.peerMeta } } @@ -87,7 +87,8 @@ class WalletConnect { return peers[id] } - private func configure(interactor: WCInteractor, address: String) { + private func configure(interactor: WCInteractor, walletId: String) { + guard let address = walletsManager.getWallet(id: walletId)?.ethereumAddress else { return } let accounts = [address] let chainId = 1 @@ -96,7 +97,7 @@ class WalletConnect { interactor.onSessionRequest = { [weak self, weak interactor] (id, peerParam) in guard let interactor = interactor else { return } self?.peers[interactor.clientId] = peerParam.peerMeta - self?.sessionStorage.add(interactor: interactor, address: address, sessionDetails: peerParam) + self?.sessionStorage.add(interactor: interactor, walletId: walletId, sessionDetails: peerParam) interactor.approveSession(accounts: accounts, chainId: chainId).cauterize() } @@ -107,12 +108,12 @@ class WalletConnect { } interactor.eth.onSign = { [weak self, weak interactor] (id, payload) in - self?.approveSign(id: id, payload: payload, address: address, interactor: interactor) + self?.approveSign(id: id, payload: payload, walletId: walletId, interactor: interactor) self?.sessionStorage.didInteractWith(clientId: interactor?.clientId) } interactor.eth.onTransaction = { [weak self, weak interactor] (id, event, transaction) in - self?.approveTransaction(id: id, wct: transaction, address: address, interactor: interactor) + self?.approveTransaction(id: id, wct: transaction, walletId: walletId, interactor: interactor) self?.sessionStorage.didInteractWith(clientId: interactor?.clientId) } } @@ -128,7 +129,7 @@ class WalletConnect { } } - private func approveTransaction(id: Int64, wct: WCEthereumTransaction, address: String, interactor: WCInteractor?) { + private func approveTransaction(id: Int64, wct: WCEthereumTransaction, walletId: String, interactor: WCInteractor?) { guard let to = wct.to else { rejectRequest(id: id, interactor: interactor, message: "Something went wrong.") return @@ -138,14 +139,14 @@ class WalletConnect { let transaction = Transaction(from: wct.from, to: to, nonce: wct.nonce, gasPrice: wct.gasPrice, gas: wct.gas, value: wct.value, data: wct.data) Agent.shared.showApprove(transaction: transaction, peerMeta: peer) { [weak self, weak interactor] transaction in if let transaction = transaction { - self?.sendTransaction(transaction, address: address, requestId: id, interactor: interactor) + self?.sendTransaction(transaction, walletId: walletId, requestId: id, interactor: interactor) } else { self?.rejectRequest(id: id, interactor: interactor, message: "Cancelled") } } } - private func approveSign(id: Int64, payload: WCEthereumSignPayload, address: String, interactor: WCInteractor?) { + private func approveSign(id: Int64, payload: WCEthereumSignPayload, walletId: String, interactor: WCInteractor?) { var message: String? let title: String switch payload { @@ -165,7 +166,7 @@ class WalletConnect { let peer = getPeerOfInteractor(interactor) Agent.shared.showApprove(title: title, meta: message ?? "", peerMeta: peer) { [weak self, weak interactor] approved in if approved { - self?.sign(id: id, message: message, payload: payload, address: address, interactor: interactor) + self?.sign(id: id, message: message, payload: payload, walletId: walletId, interactor: interactor) } else { self?.rejectRequest(id: id, interactor: interactor, message: "Cancelled") } @@ -176,31 +177,31 @@ class WalletConnect { interactor?.rejectRequest(id: id, message: message).cauterize() } - private func sendTransaction(_ transaction: Transaction, address: String, requestId: Int64, interactor: WCInteractor?) { - guard let account = accountsService.getAccountForAddress(address) else { + private func sendTransaction(_ transaction: Transaction, walletId: String, requestId: Int64, interactor: WCInteractor?) { + guard let wallet = walletsManager.getWallet(id: walletId) else { rejectRequest(id: requestId, interactor: interactor, message: "Something went wrong.") return } - guard let hash = try? ethereum.send(transaction: transaction, account: account) else { + guard let hash = try? ethereum.send(transaction: transaction, wallet: wallet) else { rejectRequest(id: requestId, interactor: interactor, message: "Failed to send") return } interactor?.approveRequest(id: requestId, result: hash).cauterize() } - private func sign(id: Int64, message: String?, payload: WCEthereumSignPayload, address: String, interactor: WCInteractor?) { - guard let message = message, let account = accountsService.getAccountForAddress(address) else { + private func sign(id: Int64, message: String?, payload: WCEthereumSignPayload, walletId: String, interactor: WCInteractor?) { + guard let message = message, let wallet = walletsManager.getWallet(id: walletId) else { rejectRequest(id: id, interactor: interactor, message: "Something went wrong.") return } var signed: String? switch payload { case .personalSign: - signed = try? ethereum.signPersonal(message: message, account: account) + signed = try? ethereum.signPersonal(message: message, wallet: wallet) case .signTypeData: - signed = try? ethereum.sign(typedData: message, account: account) + signed = try? ethereum.sign(typedData: message, wallet: wallet) case .sign: - signed = try? ethereum.sign(message: message, account: account) + signed = try? ethereum.sign(message: message, wallet: wallet) } guard let result = signed else { rejectRequest(id: id, interactor: interactor, message: "Something went wrong.") diff --git a/Encrypted Ink/Wallets/InkWallet.swift b/Encrypted Ink/Wallets/InkWallet.swift index 141a23f5..7c07aee1 100644 --- a/Encrypted Ink/Wallets/InkWallet.swift +++ b/Encrypted Ink/Wallets/InkWallet.swift @@ -43,3 +43,21 @@ final class InkWallet: Hashable, Equatable { } } + +extension InkWallet { + + var ethereumAddress: String? { + return accounts.first(where: { $0.coin == .ethereum })?.address + } + + var ethereumPrivateKey: String? { + guard let password = Keychain.shared.password, + let privateKey = try? privateKey(password: password, coin: .ethereum).data else { return nil } + return privateKey.hexString + } + + var isMnemonic: Bool { + return key.isMnemonic + } + +} diff --git a/Encrypted Ink/Wallets/WalletsManager.swift b/Encrypted Ink/Wallets/WalletsManager.swift index 0f62c293..45d9d851 100644 --- a/Encrypted Ink/Wallets/WalletsManager.swift +++ b/Encrypted Ink/Wallets/WalletsManager.swift @@ -6,6 +6,15 @@ 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]() @@ -17,6 +26,127 @@ final class WalletsManager { 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 } + let name = "Wallet \(wallets.count + 1)" // TODO: finalize naming convention + return try createWallet(name: name, 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 = "Wallet \(wallets.count + 1)" // TODO: finalize naming convention + 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), 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.. 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 - } - - 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) - } - - func checkMnemonic(_ data: Data) -> String? { - guard let mnemonic = String(data: data, encoding: .ascii), Mnemonic.isValid(mnemonic: mnemonic) else { return nil } - return mnemonic - } - - 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 - } - - 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, password: String) throws -> Data { - guard let key = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } - return key - } - - func exportMnemonic(wallet: InkWallet, password: String) throws -> String { - 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) - } - private func update(wallet: InkWallet, password: String, newPassword: String, newName: String) throws { guard let index = wallets.firstIndex(of: wallet) else { throw KeyStore.Error.accountNotFound } guard var privateKeyData = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } @@ -119,19 +179,6 @@ final class WalletsManager { try save(wallet: wallets[index]) } - func delete(wallet: InkWallet, password: String) throws { - 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.. Date: Mon, 2 Aug 2021 18:53:44 +0300 Subject: [PATCH 14/21] Add private key validation --- Encrypted Ink/Wallets/WalletsManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Encrypted Ink/Wallets/WalletsManager.swift b/Encrypted Ink/Wallets/WalletsManager.swift index 45d9d851..b53335cf 100644 --- a/Encrypted Ink/Wallets/WalletsManager.swift +++ b/Encrypted Ink/Wallets/WalletsManager.swift @@ -52,7 +52,7 @@ final class WalletsManager { 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), let privateKey = PrivateKey(data: data) { + } 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) From d7cf1985a956a05e92f3c75adbfd65ccf499e7af Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Mon, 2 Aug 2021 22:09:35 +0300 Subject: [PATCH 15/21] Sign typed data with WalletCore --- Encrypted Ink.xcodeproj/project.pbxproj | 72 -------- .../Ethereum/EIP712/EIP712Domain.swift | 66 -------- .../Ethereum/EIP712/EIP712Error.swift | 30 ---- .../Ethereum/EIP712/EIP712Hash.swift | 33 ---- .../Ethereum/EIP712/EIP712Parameter.swift | 111 ------------ .../EIP712/EIP712ParameterEncoder.swift | 119 ------------- .../Ethereum/EIP712/EIP712Signer.swift | 33 ---- .../Ethereum/EIP712/EIP712SimpleValue.swift | 24 --- .../Ethereum/EIP712/EIP712StructType.swift | 32 ---- .../Ethereum/EIP712/EIP712Type.swift | 22 --- .../Ethereum/EIP712/EIP712TypedData.swift | 160 ------------------ .../EIP712/Protocols/EIP712Hashable.swift | 16 -- .../Protocols/EIP712Representable.swift | 64 ------- .../EIP712/Protocols/EIP712Signable.swift | 35 ---- .../EIP712/Protocols/EIP712Value.swift | 20 --- Encrypted Ink/Ethereum/Ethereum.swift | 18 +- Encrypted Ink/Wallets/InkWallet.swift | 11 +- 17 files changed, 16 insertions(+), 850 deletions(-) delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712Domain.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712Error.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712Hash.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712Parameter.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712ParameterEncoder.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712Signer.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712SimpleValue.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712StructType.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712Type.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/EIP712TypedData.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Hashable.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Representable.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Signable.swift delete mode 100644 Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Value.swift diff --git a/Encrypted Ink.xcodeproj/project.pbxproj b/Encrypted Ink.xcodeproj/project.pbxproj index 6b1894cc..1e609c8f 100644 --- a/Encrypted Ink.xcodeproj/project.pbxproj +++ b/Encrypted Ink.xcodeproj/project.pbxproj @@ -7,20 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 0DB729122674E2DB0011F7A1 /* EIP712ParameterEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729022674E2DB0011F7A1 /* EIP712ParameterEncoder.swift */; }; - 0DB729142674E2DB0011F7A1 /* EIP712Parameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729042674E2DB0011F7A1 /* EIP712Parameter.swift */; }; - 0DB729152674E2DB0011F7A1 /* EIP712Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729052674E2DB0011F7A1 /* EIP712Error.swift */; }; - 0DB729162674E2DB0011F7A1 /* EIP712SimpleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729062674E2DB0011F7A1 /* EIP712SimpleValue.swift */; }; - 0DB729172674E2DB0011F7A1 /* EIP712StructType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729072674E2DB0011F7A1 /* EIP712StructType.swift */; }; - 0DB729182674E2DB0011F7A1 /* EIP712Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729082674E2DB0011F7A1 /* EIP712Signer.swift */; }; - 0DB729192674E2DB0011F7A1 /* EIP712TypedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729092674E2DB0011F7A1 /* EIP712TypedData.swift */; }; - 0DB7291A2674E2DB0011F7A1 /* EIP712Type.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290A2674E2DB0011F7A1 /* EIP712Type.swift */; }; - 0DB7291B2674E2DB0011F7A1 /* EIP712Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290B2674E2DB0011F7A1 /* EIP712Hash.swift */; }; - 0DB7291C2674E2DB0011F7A1 /* EIP712Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290D2674E2DB0011F7A1 /* EIP712Value.swift */; }; - 0DB7291D2674E2DB0011F7A1 /* EIP712Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290E2674E2DB0011F7A1 /* EIP712Hashable.swift */; }; - 0DB7291E2674E2DB0011F7A1 /* EIP712Signable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290F2674E2DB0011F7A1 /* EIP712Signable.swift */; }; - 0DB7291F2674E2DB0011F7A1 /* EIP712Representable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729102674E2DB0011F7A1 /* EIP712Representable.swift */; }; - 0DB729202674E2DB0011F7A1 /* EIP712Domain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729112674E2DB0011F7A1 /* EIP712Domain.swift */; }; 28BDF30EBC80362870C988B6 /* Pods_Encrypted_Ink.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E2A642C960E4952955E6E82 /* Pods_Encrypted_Ink.framework */; }; 2C03D1D2269B407900EF10EA /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C03D1D1269B407900EF10EA /* NetworkMonitor.swift */; }; 2C03D1D5269B428C00EF10EA /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C03D1D4269B428C00EF10EA /* Notification.swift */; }; @@ -61,20 +47,6 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 0DB729022674E2DB0011F7A1 /* EIP712ParameterEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712ParameterEncoder.swift; sourceTree = ""; }; - 0DB729042674E2DB0011F7A1 /* EIP712Parameter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Parameter.swift; sourceTree = ""; }; - 0DB729052674E2DB0011F7A1 /* EIP712Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Error.swift; sourceTree = ""; }; - 0DB729062674E2DB0011F7A1 /* EIP712SimpleValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712SimpleValue.swift; sourceTree = ""; }; - 0DB729072674E2DB0011F7A1 /* EIP712StructType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712StructType.swift; sourceTree = ""; }; - 0DB729082674E2DB0011F7A1 /* EIP712Signer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Signer.swift; sourceTree = ""; }; - 0DB729092674E2DB0011F7A1 /* EIP712TypedData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712TypedData.swift; sourceTree = ""; }; - 0DB7290A2674E2DB0011F7A1 /* EIP712Type.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Type.swift; sourceTree = ""; }; - 0DB7290B2674E2DB0011F7A1 /* EIP712Hash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Hash.swift; sourceTree = ""; }; - 0DB7290D2674E2DB0011F7A1 /* EIP712Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Value.swift; sourceTree = ""; }; - 0DB7290E2674E2DB0011F7A1 /* EIP712Hashable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Hashable.swift; sourceTree = ""; }; - 0DB7290F2674E2DB0011F7A1 /* EIP712Signable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Signable.swift; sourceTree = ""; }; - 0DB729102674E2DB0011F7A1 /* EIP712Representable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Representable.swift; sourceTree = ""; }; - 0DB729112674E2DB0011F7A1 /* EIP712Domain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Domain.swift; sourceTree = ""; }; 2C03D1D1269B407900EF10EA /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; 2C03D1D4269B428C00EF10EA /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; 2C19953C2674C4B900A8E370 /* Encrypted Ink.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Encrypted Ink.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -131,35 +103,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0DB729012674E2DB0011F7A1 /* EIP712 */ = { - isa = PBXGroup; - children = ( - 0DB729022674E2DB0011F7A1 /* EIP712ParameterEncoder.swift */, - 0DB729042674E2DB0011F7A1 /* EIP712Parameter.swift */, - 0DB729052674E2DB0011F7A1 /* EIP712Error.swift */, - 0DB729062674E2DB0011F7A1 /* EIP712SimpleValue.swift */, - 0DB729072674E2DB0011F7A1 /* EIP712StructType.swift */, - 0DB729082674E2DB0011F7A1 /* EIP712Signer.swift */, - 0DB729092674E2DB0011F7A1 /* EIP712TypedData.swift */, - 0DB7290A2674E2DB0011F7A1 /* EIP712Type.swift */, - 0DB7290B2674E2DB0011F7A1 /* EIP712Hash.swift */, - 0DB729112674E2DB0011F7A1 /* EIP712Domain.swift */, - 0DB7290C2674E2DB0011F7A1 /* Protocols */, - ); - path = EIP712; - sourceTree = ""; - }; - 0DB7290C2674E2DB0011F7A1 /* Protocols */ = { - isa = PBXGroup; - children = ( - 0DB7290D2674E2DB0011F7A1 /* EIP712Value.swift */, - 0DB7290E2674E2DB0011F7A1 /* EIP712Hashable.swift */, - 0DB7290F2674E2DB0011F7A1 /* EIP712Signable.swift */, - 0DB729102674E2DB0011F7A1 /* EIP712Representable.swift */, - ); - path = Protocols; - sourceTree = ""; - }; 2C1995332674C4B900A8E370 = { isa = PBXGroup; children = ( @@ -228,7 +171,6 @@ 2C1995552674D0F300A8E370 /* Ethereum.swift */, 2CE3D014267F73E80032A62E /* LegacyAccountWithKey.swift */, 2CE3D011267F73C00032A62E /* Transaction.swift */, - 0DB729012674E2DB0011F7A1 /* EIP712 */, ); path = Ethereum; sourceTree = ""; @@ -429,30 +371,22 @@ buildActionMask = 2147483647; files = ( 2C901C4A2689F01700D0926A /* Strings.swift in Sources */, - 0DB729152674E2DB0011F7A1 /* EIP712Error.swift in Sources */, 2C6706A5267A6BFE006AAEF2 /* Bundle.swift in Sources */, 2CC0CDBE2692027E0072922A /* PriceService.swift in Sources */, 2C8A09C6267513FC00993638 /* Agent.swift in Sources */, 2C8A09D42675184700993638 /* Window.swift in Sources */, - 0DB7291A2674E2DB0011F7A1 /* EIP712Type.swift in Sources */, 2C208A9F26813408005BA500 /* Secrets.swift in Sources */, 2CC8946F269A2E8C00879245 /* SessionStorage.swift in Sources */, - 0DB7291D2674E2DB0011F7A1 /* EIP712Hashable.swift in Sources */, - 0DB729162674E2DB0011F7A1 /* EIP712SimpleValue.swift in Sources */, - 0DB729122674E2DB0011F7A1 /* EIP712ParameterEncoder.swift in Sources */, 2CD0669126B5537B00728C20 /* InkWallet.swift in Sources */, 2C8A09D726751A0C00993638 /* WalletConnect.swift in Sources */, 2C03D1D2269B407900EF10EA /* NetworkMonitor.swift in Sources */, 2C8A09E326757FC000993638 /* AccountCellView.swift in Sources */, - 0DB729202674E2DB0011F7A1 /* EIP712Domain.swift in Sources */, - 0DB729182674E2DB0011F7A1 /* EIP712Signer.swift in Sources */, 2C603D0226B6E13F00956955 /* String.swift in Sources */, 2CC89471269A334A00879245 /* UserDefaults.swift in Sources */, 2C78F8282683BDCC00C10670 /* Alert.swift in Sources */, 2C8A09EE2675965F00993638 /* WaitingViewController.swift in Sources */, 2C797E7E267BB88800F2CE2D /* WelcomeViewController.swift in Sources */, 2CD0669226B5537B00728C20 /* WalletsManager.swift in Sources */, - 0DB729192674E2DB0011F7A1 /* EIP712TypedData.swift in Sources */, 2CD0B3F726AC619900488D92 /* AddAccountOptionCellView.swift in Sources */, 2C8A09E82675960D00993638 /* ErrorViewController.swift in Sources */, 2C1995422674C4B900A8E370 /* ImportViewController.swift in Sources */, @@ -460,17 +394,11 @@ 2C901C472689E6D400D0926A /* ApproveTransactionViewController.swift in Sources */, 2CDAB3722675B3F0009F8B97 /* PasswordViewController.swift in Sources */, 2C1995402674C4B900A8E370 /* AppDelegate.swift in Sources */, - 0DB729142674E2DB0011F7A1 /* EIP712Parameter.swift in Sources */, 2C901C4D268A033100D0926A /* GasService.swift in Sources */, 2C528A16267FA8EB00CA3ADD /* Defaults.swift in Sources */, 2CD0B3F526A0DAA900488D92 /* NSPasteboard.swift in Sources */, 2CE3D015267F73E80032A62E /* LegacyAccountWithKey.swift in Sources */, 2CE3D012267F73C00032A62E /* Transaction.swift in Sources */, - 0DB7291B2674E2DB0011F7A1 /* EIP712Hash.swift in Sources */, - 0DB729172674E2DB0011F7A1 /* EIP712StructType.swift in Sources */, - 0DB7291F2674E2DB0011F7A1 /* EIP712Representable.swift in Sources */, - 0DB7291E2674E2DB0011F7A1 /* EIP712Signable.swift in Sources */, - 0DB7291C2674E2DB0011F7A1 /* EIP712Value.swift in Sources */, 2C8A09EB2675964700993638 /* ApproveViewController.swift in Sources */, 2C03D1D5269B428C00EF10EA /* Notification.swift in Sources */, 2C1995562674D0F300A8E370 /* Ethereum.swift in Sources */, diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712Domain.swift b/Encrypted Ink/Ethereum/EIP712/EIP712Domain.swift deleted file mode 100644 index cca247bd..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712Domain.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Domain.swift -// -// Created by Igor Shmakov on 09/04/2019 -// - -import Foundation - -public struct EIP712Domain: EIP712Representable { - - public let name: String? - public let version: String? - public let chainID: Int? - public let verifyingContract: String? - public let salt: Data? - - public var typeName: String { - return "EIP712Domain" - } - - public func values() throws -> [EIP712Value] { - - var data = [EIP712Value]() - - if let name = name { - data.append(EIP712SimpleValue( - parameter: EIP712Parameter(name: "name", type: .string), - value: name) - ) - } - - if let version = version { - data.append(EIP712SimpleValue( - parameter: EIP712Parameter(name: "version", type: .string), - value: version) - ) - } - - if let chainID = chainID { - data.append(EIP712SimpleValue( - parameter: EIP712Parameter(name: "chainId", type: .uint(len: 256)), - value: chainID) - ) - } - - if let verifyingContract = verifyingContract { - data.append(EIP712SimpleValue( - parameter: EIP712Parameter(name: "verifyingContract", type: .address), - value: verifyingContract) - ) - } - - if let salt = salt { - data.append(EIP712SimpleValue( - parameter: EIP712Parameter(name: "salt", type: .fixedBytes(len: 32)), - value: salt) - ) - } - - return data - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712Error.swift b/Encrypted Ink/Ethereum/EIP712/EIP712Error.swift deleted file mode 100644 index 389338e6..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712Error.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Error.swift -// -// Created by Igor Shmakov on 09/04/2019 -// - -import Foundation - -public enum EIP712Error: Error { - - case notImplemented - case invalidInput - case invalidMessage - case invalidParameter(name: String) - case invalidType(name: String) - - case invalidTypedData - case invalidTypedDataPrimaryType - case invalidTypedDataDomain - case invalidTypedDataMessage - case invalidTypedDataType - case invalidTypedDataValue - case integerOverflow - - case signatureVerificationError -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712Hash.swift b/Encrypted Ink/Ethereum/EIP712/EIP712Hash.swift deleted file mode 100644 index f10334ea..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712Hash.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// Hash:.swift -// -// Created by Igor Shmakov on 09/04/2019 -// - -import Foundation - -public class EIP712Hash: EIP712Hashable { - - private let typedData: EIP712Hashable - private let domain: EIP712Hashable - - public init(domain: EIP712Hashable, typedData: EIP712Hashable) { - - self.domain = domain - self.typedData = typedData - } - - public func hash() throws -> Data { - guard - let domainData = try? domain.hash(), - let structData = try? typedData.hash() - else { - throw EIP712Error.invalidMessage - } - return (Data(hex: "0x1901") + domainData + structData).sha3(.keccak256) - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712Parameter.swift b/Encrypted Ink/Ethereum/EIP712/EIP712Parameter.swift deleted file mode 100644 index 055a107b..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712Parameter.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Parameter.swift -// -// Created by Igor Shmakov on 15/04/2019 -// - -import Foundation - -public enum EIP712ParameterType { - - case bool - case address - case string - case bytes - case fixedBytes(len: Int) - case uint(len: Int) - case int(len: Int) - case object(name: String) - - private static func parseBytesSize(type: String, prefix: String) throws -> Int { - - guard type.starts(with: prefix) else { - throw EIP712Error.invalidType(name: type) - } - guard let size = Int(type.dropFirst(prefix.count)) else { - throw EIP712Error.invalidType(name: type) - } - if size < 1 || size > 32 { - throw EIP712Error.invalidType(name: type) - } - return size - } - - private static func parseIntSize(type: String, prefix: String) throws -> Int { - - guard type.starts(with: prefix) else { - throw EIP712Error.invalidType(name: type) - } - guard let size = Int(type.dropFirst(prefix.count)) else { - if type == prefix { - return 256 - } - throw EIP712Error.invalidType(name: type) - } - if size < 8 || size > 256 || size % 8 != 0 { - throw EIP712Error.invalidType(name: type) - } - return size - } - - public static func parse(type: String) throws -> EIP712ParameterType { - - if type == "bool" { - return .bool - } - - if type == "address" { - return .address - } - - if type == "string" { - return .string - } - - if type == "bytes" { - return .bytes - } - - if type.hasPrefix("uint") { - return try .uint(len: parseIntSize(type: type, prefix: "uint")) - } - - if type.hasPrefix("int") { - return try .int(len: parseIntSize(type: type, prefix: "int")) - } - - if type.hasPrefix("bytes") { - return try .fixedBytes(len: parseBytesSize(type: type, prefix: "bytes")) - } - - return object(name: type) - } - - public func raw() -> String { - - switch self { - case .bool: return "bool" - case .address: return "address" - case .string: return "string" - case let .fixedBytes(len): return "bytes\(len)" - case let .uint(len): return "uint\(len)" - case let .int(len): return "int\(len)" - case .bytes: return "bytes" - case let .object(name): return name - } - } -} - -public struct EIP712Parameter { - - public let name: String - public let type: EIP712ParameterType - - public func encode() -> String { - return "\(type.raw()) \(name)" - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712ParameterEncoder.swift b/Encrypted Ink/Ethereum/EIP712/EIP712ParameterEncoder.swift deleted file mode 100644 index c39f9871..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712ParameterEncoder.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712ValueEncoder.swift -// -// Created by Igor Shmakov on 15/04/2019 -// - -import Foundation -import BigInt -import Web3Swift - -public final class EIP712ValueEncoder { - - private let type: EIP712ParameterType - private let value: Any - - public init(type: EIP712ParameterType, value: Any) { - - self.type = type - self.value = value - } - - public func makeABIEncodedParameter() throws -> ABIEncodedParameter { - - switch type { - case .bool: - return try encodeBool() - case .address: - return try encodeAddress() - case .string: - return try encodeString() - case .fixedBytes: - return try encodeFixedBytes() - case .uint, .int: - return try encodeInt() - case .bytes: - return try encodeBytes() - case .object: - return try encodeObject() - } - } -} - -extension EIP712ValueEncoder { - - private func encodeBool() throws -> ABIEncodedParameter { - - guard let bool = value as? Bool else { - throw EIP712Error.invalidTypedDataValue - } - return ABIBoolean(origin: bool) - } - - private func encodeAddress() throws -> ABIEncodedParameter { - - guard let value = value as? String else { - throw EIP712Error.invalidTypedDataValue - } - return ABIAddress(address: EthAddress(hex: value)) - } - - private func encodeString() throws -> ABIEncodedParameter { - - guard let value = value as? String, let data = value.data(using: .utf8) else { - throw EIP712Error.invalidTypedDataValue - } - return ABIFixedBytes(origin: SimpleBytes(bytes: data.sha3(.keccak256))) - } - - private func encodeFixedBytes() throws -> ABIEncodedParameter { - - let data: Data - if let value = value as? String { - data = Data(hex: value) - } else if let value = value as? Data { - data = value - } else { - throw EIP712Error.invalidTypedDataValue - } - return ABIFixedBytes(origin: SimpleBytes(bytes: data)) - } - - private func encodeInt() throws -> ABIEncodedParameter { - - let number: Int - if let value = value as? Int { - number = value - } else if let str = value as? String { - return ABIUnsignedNumber(origin: EthNumber(hex: str)) - } else { - throw EIP712Error.invalidTypedDataValue - } - return ABIUnsignedNumber(origin: EthNumber(value: number)) - } - - private func encodeBytes() throws -> ABIEncodedParameter { - - let data: Data - if let value = value as? String { - data = Data(hex: value) - } else if let value = value as? Data { - data = value - } else { - throw EIP712Error.invalidTypedDataValue - } - return ABIFixedBytes(origin: SimpleBytes(bytes: data.sha3(.keccak256))) - } - - private func encodeObject() throws -> ABIEncodedParameter { - - guard let value = value as? EIP712Representable else { - throw EIP712Error.invalidTypedDataValue - } - return ABIFixedBytes(origin: SimpleBytes(bytes: try value.hash())) - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712Signer.swift b/Encrypted Ink/Ethereum/EIP712/EIP712Signer.swift deleted file mode 100644 index 7d146e89..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712Signer.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Signer.swift -// -// Created by Igor Shmakov on 19/04/2019 -// - -import Foundation -import Web3Swift -import CryptoSwift - -public final class EIP712Signer: EIP712Signable { - - private let privateKey: EthPrivateKey - - public init(privateKey: EthPrivateKey) { - - self.privateKey = privateKey - } - - public func sign(hash: EIP712Hashable) throws -> SECP256k1Signature { - - let signature = SECP256k1Signature( - digest: SimpleBytes(bytes: try hash.hash()), - privateKey: privateKey - ) - - return signature - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712SimpleValue.swift b/Encrypted Ink/Ethereum/EIP712/EIP712SimpleValue.swift deleted file mode 100644 index b4ed4102..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712SimpleValue.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712SimpleValue.swift -// -// Created by Igor Shmakov on 15/04/2019 -// - -import Foundation -import Web3Swift - -public struct EIP712SimpleValue: EIP712Value { - - public let parameter: EIP712Parameter - public let value: Any - - public func makeABIEncodedParameter() throws -> ABIEncodedParameter { - - let encoder = EIP712ValueEncoder(type: parameter.type, value: value) - return try encoder.makeABIEncodedParameter() - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712StructType.swift b/Encrypted Ink/Ethereum/EIP712/EIP712StructType.swift deleted file mode 100644 index 69233d7d..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712StructType.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712StructType.swift -// -// Created by Igor Shmakov on 15/04/2019 -// - -import Foundation - -public struct EIP712StructType { - - public let types: [EIP712Type] - - public var primaryType: EIP712Type? { - return types.first - } - - public init(primary: EIP712Type, referenced: [EIP712Type] = []) { - types = [primary] + referenced.sorted { $0.name < $1.name } - } - - public func encode() -> String { - return types.reduce("") { $0 + $1.encode()} - } - - public func hashType() throws -> Data { - return encode().data(using: .utf8)!.sha3(.keccak256) - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712Type.swift b/Encrypted Ink/Ethereum/EIP712/EIP712Type.swift deleted file mode 100644 index badd1dec..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712Type.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Type.swift -// -// Created by Igor Shmakov on 15/04/2019 -// - -import Foundation - -public struct EIP712Type { - - public let name: String - public let parameters: [EIP712Parameter] - - public func encode() -> String { - let encodedParameters = parameters.map { $0.encode() }.joined(separator: ",") - return "\(name)(\(encodedParameters))" - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/EIP712TypedData.swift b/Encrypted Ink/Ethereum/EIP712/EIP712TypedData.swift deleted file mode 100644 index b04bcaaf..00000000 --- a/Encrypted Ink/Ethereum/EIP712/EIP712TypedData.swift +++ /dev/null @@ -1,160 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712TypedData.swift -// -// Created by Igor Shmakov on 12/04/2019 -// - -import Foundation -import SwiftyJSON -import Web3Swift -import BigInt - -public class EIP712TypedData { - - public private(set) var type: EIP712StructType! - public private(set) var domain: EIP712Domain! - public private(set) var encodedData: Data! - - private let domainType = "EIP712Domain" - - public convenience init(jsonData: Data) throws { - try self.init(json: try JSON(data: jsonData)) - } - - public convenience init(jsonString: String) throws { - try self.init(json: JSON(parseJSON: jsonString)) - } - - public convenience init(jsonObject: Any) throws { - try self.init(json: JSON(jsonObject)) - } - - public init(json: JSON) throws { - - let jsonDomain = json["domain"] - let jsonMessage = json["message"] - - guard - let jsonTypes = json["types"].dictionary, - let primaryTypeName = json["primaryType"].string - else { - throw EIP712Error.invalidTypedData - } - - let types = try parseTypes(jsonTypes: jsonTypes) - - guard let primaryType = types[primaryTypeName] else { - throw EIP712Error.invalidTypedDataPrimaryType - } - - guard let domainType = types[domainType] else { - throw EIP712Error.invalidTypedDataDomain - } - - let dependencies = try findTypeDependencies(primaryType: primaryType, types: types).filter { $0.name != primaryType.name } - let type = EIP712StructType(primary: primaryType, referenced: dependencies) - let domain = try parseDomain(jsonDomain: jsonDomain, type: domainType) - let data = try encodeData(data: jsonMessage, primaryType: primaryType, types: types) - - self.type = type - self.encodedData = data - self.domain = domain - } - - private func parseDomain(jsonDomain: JSON, type: EIP712Type) throws -> EIP712Domain { - - return EIP712Domain(name: jsonDomain["name"].string, - version: jsonDomain["version"].string, - chainID: jsonDomain["chainId"].int, - verifyingContract: jsonDomain["verifyingContract"].string, - salt: jsonDomain["salt"].string?.data(using: .utf8)) - } - - private func parseTypes(jsonTypes: [String: JSON]) throws -> [String: EIP712Type] { - - var types = [String: EIP712Type]() - - for (name, jsonParameters) in jsonTypes { - var parameters = [EIP712Parameter]() - for (_, jsonParameter) in jsonParameters { - guard - let name = jsonParameter["name"].string, - let type = jsonParameter["type"].string - else { - throw EIP712Error.invalidTypedDataType - } - parameters.append(EIP712Parameter(name: name, type: try EIP712ParameterType.parse(type: type))) - } - let type = EIP712Type(name: name, parameters: parameters) - types[name] = type - } - - return types - } - - private func findTypeDependencies(primaryType: EIP712Type, types: [String: EIP712Type], results: [EIP712Type] = []) throws -> [EIP712Type] { - - var results = results - if (results.contains(where: { $0.name == primaryType.name }) || types[primaryType.name] == nil) { - return results - } - - results.append(primaryType) - - for parameter in primaryType.parameters { - if let type = types[parameter.type.raw()] { - let dependencies = try findTypeDependencies(primaryType: type, types: types, results: results) - results += dependencies.filter { dep in !results.contains(where: { res in res.name == dep.name }) } - } - } - return results - } - - private func encodeType(primaryType: EIP712Type, types: [String: EIP712Type]) throws -> EIP712StructType { - - let dependencies = try findTypeDependencies(primaryType: primaryType, types: types).filter { $0.name != primaryType.name } - return EIP712StructType(primary: primaryType, referenced: dependencies) - } - - private func hashType(primaryType: EIP712Type, types: [String: EIP712Type]) throws -> Data { - - let type = try encodeType(primaryType: primaryType, types: types) - return try type.hashType() - } - - private func encodeData(data: JSON, primaryType: EIP712Type, types: [String: EIP712Type]) throws -> Data { - - var encodedTypes: [EIP712ParameterType] = [.fixedBytes(len: 32)] - var encodedValues: [Any] = [try self.hashType(primaryType: primaryType, types: types)] - - for parameter in primaryType.parameters { - let value = data[parameter.name] - if let type = types[parameter.type.raw()] { - encodedTypes.append(.fixedBytes(len: 32)) - let data = try encodeData(data: value, primaryType: type, types: types) - encodedValues.append(data.sha3(.keccak256)) - } else { - encodedTypes.append(parameter.type) - encodedValues.append(value.object) - } - } - - let parameters = try zip(encodedTypes, encodedValues).map { - try EIP712ValueEncoder(type: $0.0, value: $0.1).makeABIEncodedParameter() - } - - return try EncodedABITuple(parameters: parameters).value() - } -} - -extension EIP712TypedData: EIP712Hashable { - - public func hash() throws -> Data { - - return encodedData.sha3(.keccak256) - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Hashable.swift b/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Hashable.swift deleted file mode 100644 index 48a94418..00000000 --- a/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Hashable.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Hashable.swift -// -// Created by Igor Shmakov on 17/04/2019 -// - -import Foundation - -public protocol EIP712Hashable { - - func hash() throws -> Data -} diff --git a/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Representable.swift b/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Representable.swift deleted file mode 100644 index 90d31802..00000000 --- a/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Representable.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Representable.swift -// -// Created by Igor Shmakov on 15/04/2019 -// - -import Foundation -import Web3Swift - -public protocol EIP712Representable: EIP712Hashable { - - var typeName: String { get } - func values() throws -> [EIP712Value] -} - -public extension EIP712Representable { - - func typeDependencies() throws -> [EIP712Type] { - - let types = try values() - .compactMap { $0.value as? EIP712Representable } - .flatMap { [try $0.type()] + (try $0.typeDependencies()) } - - var uniqueTypes = [EIP712Type]() - for type in types { - if !uniqueTypes.contains(where: { $0.name == type.name }) { - uniqueTypes.append(type) - } - } - return uniqueTypes - } - - func type() throws -> EIP712Type { - return EIP712Type(name: typeName, parameters: try values().map { $0.parameter }) - } - - func encodeType() throws -> EIP712StructType { - - let primary = try type() - let referenced = try typeDependencies().filter { $0.name != primary.name } - return EIP712StructType(primary: primary, referenced: referenced) - } - - func hashType() throws -> Data { - - return try encodeType().hashType() - } - - func encodeData() throws -> Data { - - var parameters: [ABIEncodedParameter] = [ABIFixedBytes(origin: SimpleBytes(bytes: try hashType()))] - parameters += try values().map { try $0.makeABIEncodedParameter() } - return try EncodedABITuple(parameters: parameters).value() - } - - func hash() throws -> Data { - - return try encodeData().sha3(.keccak256) - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Signable.swift b/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Signable.swift deleted file mode 100644 index 15131b1d..00000000 --- a/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Signable.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Signable.swift -// -// Created by Igor Shmakov on 19/04/2019 -// - -import Foundation -import Web3Swift - -public protocol EIP712Signable { - - func sign(hash: EIP712Hashable) throws -> SECP256k1Signature -} - -public extension EIP712Signable { - - func signatureData(hash: EIP712Hashable) throws -> Data { - - let signature = try sign(hash: hash) - - let message = ConcatenatedBytes( - bytes: [ - try signature.r(), - try signature.s(), - SimpleBytes(bytes: [UInt8(try signature.recoverID().value() + 27)]) - ] - ) - - return try message.value() - } -} diff --git a/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Value.swift b/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Value.swift deleted file mode 100644 index 93cbd9ae..00000000 --- a/Encrypted Ink/Ethereum/EIP712/Protocols/EIP712Value.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// This source file is part of the 0x.swift open source project -// Copyright 2019 The 0x.swift Authors -// Licensed under Apache License v2.0 -// -// EIP712Value.swift -// -// Created by Igor Shmakov on 15/04/2019 -// - -import Foundation -import Web3Swift - -public protocol EIP712Value { - - var parameter: EIP712Parameter { get } - var value: Any { get } - - func makeABIEncodedParameter() throws -> ABIEncodedParameter -} diff --git a/Encrypted Ink/Ethereum/Ethereum.swift b/Encrypted Ink/Ethereum/Ethereum.swift index 57dd7afd..f1642668 100644 --- a/Encrypted Ink/Ethereum/Ethereum.swift +++ b/Encrypted Ink/Ethereum/Ethereum.swift @@ -1,6 +1,7 @@ // Copyright © 2021 Encrypted Ink. All rights reserved. import Foundation +import WalletCore import Web3Swift import CryptoSwift @@ -9,6 +10,7 @@ struct Ethereum { enum Error: Swift.Error { case invalidInputData case failedToSendTransaction + case failedToSign case keyNotFound } @@ -23,7 +25,7 @@ struct Ethereum { ) func sign(message: String, wallet: InkWallet) throws -> String { - guard let privateKeyString = wallet.ethereumPrivateKey else { throw Error.keyNotFound } + guard let privateKeyString = wallet.ethereumPrivateKeyString else { throw Error.keyNotFound } let ethPrivateKey = EthPrivateKey(hex: privateKeyString) let signature = SECP256k1Signature( @@ -42,7 +44,7 @@ struct Ethereum { } func signPersonal(message: String, wallet: InkWallet) throws -> String { - guard let privateKeyString = wallet.ethereumPrivateKey else { throw Error.keyNotFound } + guard let privateKeyString = wallet.ethereumPrivateKeyString else { throw Error.keyNotFound } let ethPrivateKey = EthPrivateKey(hex: privateKeyString) let signed = SignedPersonalMessageBytes(message: message, signerKey: ethPrivateKey) let data = try signed.value().toPrefixedHexString() @@ -50,12 +52,10 @@ struct Ethereum { } func sign(typedData: String, wallet: InkWallet) throws -> String { - guard let privateKeyString = wallet.ethereumPrivateKey else { throw Error.keyNotFound } - let data = try EIP712TypedData(jsonString: typedData) - let hash = EIP712Hash(domain: data.domain, typedData: data) - let privateKey = EthPrivateKey(hex: privateKeyString) - let signer = EIP712Signer(privateKey: privateKey) - return try signer.signatureData(hash: hash).toPrefixedHexString() + guard let ethereumPrivateKey = wallet.ethereumPrivateKey else { throw Error.keyNotFound } + let digest = EthereumAbi.encodeTyped(messageJson: typedData) + guard let signed = ethereumPrivateKey.sign(digest: digest, curve: CoinType.ethereum.curve)?.toPrefixedHexString() else { throw Error.failedToSign } + return signed } func send(transaction: Transaction, wallet: InkWallet) throws -> String { @@ -68,7 +68,7 @@ struct Ethereum { } private func signedTransactionBytes(transaction: Transaction, wallet: InkWallet) throws -> EthContractCallBytes { - guard let privateKeyString = wallet.ethereumPrivateKey else { throw Error.keyNotFound } + guard let privateKeyString = wallet.ethereumPrivateKeyString else { throw Error.keyNotFound } let senderKey = EthPrivateKey(hex: privateKeyString) let contractAddress = EthAddress(hex: transaction.to) let functionCall = BytesFromHexString(hex: transaction.data) diff --git a/Encrypted Ink/Wallets/InkWallet.swift b/Encrypted Ink/Wallets/InkWallet.swift index 7c07aee1..f9d218ee 100644 --- a/Encrypted Ink/Wallets/InkWallet.swift +++ b/Encrypted Ink/Wallets/InkWallet.swift @@ -50,10 +50,13 @@ extension InkWallet { return accounts.first(where: { $0.coin == .ethereum })?.address } - var ethereumPrivateKey: String? { - guard let password = Keychain.shared.password, - let privateKey = try? privateKey(password: password, coin: .ethereum).data else { return nil } - return privateKey.hexString + var ethereumPrivateKey: PrivateKey? { + guard let password = Keychain.shared.password else { return nil } + return try? privateKey(password: password, coin: .ethereum) + } + + var ethereumPrivateKeyString: String? { + return ethereumPrivateKey?.data.hexString } var isMnemonic: Bool { From f563fa8190c8dd103a4023cc87ca1f37653298b6 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Mon, 2 Aug 2021 22:43:52 +0300 Subject: [PATCH 16/21] Use empty wallet names --- Encrypted Ink/Wallets/WalletsManager.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Encrypted Ink/Wallets/WalletsManager.swift b/Encrypted Ink/Wallets/WalletsManager.swift index b53335cf..4a6b51e6 100644 --- a/Encrypted Ink/Wallets/WalletsManager.swift +++ b/Encrypted Ink/Wallets/WalletsManager.swift @@ -38,8 +38,7 @@ final class WalletsManager { func createWallet() throws -> InkWallet { guard let password = keychain.password else { throw Error.keychainAccessFailure } - let name = "Wallet \(wallets.count + 1)" // TODO: finalize naming convention - return try createWallet(name: name, password: password, coin: .ethereum) + return try createWallet(name: defaultWalletName, password: password, coin: .ethereum) } func getWallet(id: String) -> InkWallet? { @@ -48,7 +47,7 @@ final class WalletsManager { func addWallet(input: String, inputPassword: String?) throws -> InkWallet { guard let password = keychain.password else { throw Error.keychainAccessFailure } - let name = "Wallet \(wallets.count + 1)" // TODO: finalize naming convention + let name = defaultWalletName let coin = CoinType.ethereum if Mnemonic.isValid(mnemonic: input) { return try importMnemonic(input, name: name, encryptPassword: password, coin: coin) @@ -152,7 +151,7 @@ final class WalletsManager { guard !legacyAccountsWithKeys.isEmpty, let password = keychain.password else { return } for legacyAccount in legacyAccountsWithKeys { if let data = Data(hexString: legacyAccount.privateKey), let privateKey = PrivateKey(data: data) { - _ = try importPrivateKey(privateKey, name: legacyAccount.address, password: password, coin: .ethereum) + _ = try importPrivateKey(privateKey, name: defaultWalletName, password: password, coin: .ethereum) } } try keychain.removeLegacyAccounts() @@ -184,6 +183,8 @@ final class WalletsManager { try keychain.saveWallet(id: wallet.id, data: data) } + private var defaultWalletName = "" + private func makeNewWalletId() -> String { let uuid = UUID().uuidString let date = Date().timeIntervalSince1970 From 8f197a94b074ec5768400831fde820bc99582860 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Tue, 3 Aug 2021 22:26:08 +0300 Subject: [PATCH 17/21] Bump build number --- Encrypted Ink.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Encrypted Ink.xcodeproj/project.pbxproj b/Encrypted Ink.xcodeproj/project.pbxproj index 1e609c8f..5bd34c79 100644 --- a/Encrypted Ink.xcodeproj/project.pbxproj +++ b/Encrypted Ink.xcodeproj/project.pbxproj @@ -546,7 +546,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = XWNXDSM6BU; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/Encrypted Ink/Supporting Files/Info.plist"; @@ -573,7 +573,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = XWNXDSM6BU; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/Encrypted Ink/Supporting Files/Info.plist"; From 8cfe8d219f278bf7720fd0e322bcf85655f73056 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Tue, 3 Aug 2021 23:14:46 +0300 Subject: [PATCH 18/21] Add colors --- Encrypted Ink.xcodeproj/project.pbxproj | 8 ++++++++ Encrypted Ink/Colors.xcassets/Contents.json | 6 ++++++ .../InkBlue.colorset/Contents.json | 20 +++++++++++++++++++ .../InkGreen.colorset/Contents.json | 20 +++++++++++++++++++ .../InkRed.colorset/Contents.json | 20 +++++++++++++++++++ Encrypted Ink/Extensions/NSColor.swift | 9 +++++++++ 6 files changed, 83 insertions(+) create mode 100644 Encrypted Ink/Colors.xcassets/Contents.json create mode 100644 Encrypted Ink/Colors.xcassets/InkBlue.colorset/Contents.json create mode 100644 Encrypted Ink/Colors.xcassets/InkGreen.colorset/Contents.json create mode 100644 Encrypted Ink/Colors.xcassets/InkRed.colorset/Contents.json create mode 100644 Encrypted Ink/Extensions/NSColor.swift diff --git a/Encrypted Ink.xcodeproj/project.pbxproj b/Encrypted Ink.xcodeproj/project.pbxproj index 5bd34c79..6a1f9d06 100644 --- a/Encrypted Ink.xcodeproj/project.pbxproj +++ b/Encrypted Ink.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ 2C528A16267FA8EB00CA3ADD /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C528A15267FA8EB00CA3ADD /* Defaults.swift */; }; 2C603D0226B6E13F00956955 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C603D0126B6E13F00956955 /* String.swift */; }; 2C6706A5267A6BFE006AAEF2 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6706A4267A6BFE006AAEF2 /* Bundle.swift */; }; + 2C6B964C26B9D92500D2C819 /* NSColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6B964B26B9D92500D2C819 /* NSColor.swift */; }; + 2C6B964F26B9D98C00D2C819 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C6B964E26B9D98C00D2C819 /* Colors.xcassets */; }; 2C78F8282683BDCC00C10670 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78F8272683BDCC00C10670 /* Alert.swift */; }; 2C797E7E267BB88800F2CE2D /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C797E7D267BB88800F2CE2D /* WelcomeViewController.swift */; }; 2C8A09C6267513FC00993638 /* Agent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09C5267513FC00993638 /* Agent.swift */; }; @@ -61,6 +63,8 @@ 2C528A15267FA8EB00CA3ADD /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = ""; }; 2C603D0126B6E13F00956955 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 2C6706A4267A6BFE006AAEF2 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; + 2C6B964B26B9D92500D2C819 /* NSColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColor.swift; sourceTree = ""; }; + 2C6B964E26B9D98C00D2C819 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 2C78F8272683BDCC00C10670 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; 2C797E7D267BB88800F2CE2D /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; 2C8A09C5267513FC00993638 /* Agent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Agent.swift; sourceTree = ""; }; @@ -137,6 +141,7 @@ 2C8A09C2267513A700993638 /* Ethereum */, 2C8A09E5267595C200993638 /* Views */, 2C1995432674C4BA00A8E370 /* Assets.xcassets */, + 2C6B964E26B9D98C00D2C819 /* Colors.xcassets */, 2C1995452674C4BA00A8E370 /* Main.storyboard */, 2C208AA126813497005BA500 /* Supporting Files */, ); @@ -159,6 +164,7 @@ 2C6706A4267A6BFE006AAEF2 /* Bundle.swift */, 2C03D1D4269B428C00EF10EA /* Notification.swift */, 2C603D0126B6E13F00956955 /* String.swift */, + 2C6B964B26B9D92500D2C819 /* NSColor.swift */, 2CD0B3F426A0DAA900488D92 /* NSPasteboard.swift */, 2CC89470269A334A00879245 /* UserDefaults.swift */, ); @@ -299,6 +305,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2C6B964F26B9D98C00D2C819 /* Colors.xcassets in Resources */, 2C1995442674C4BA00A8E370 /* Assets.xcassets in Resources */, 2C1995472674C4BA00A8E370 /* Main.storyboard in Resources */, ); @@ -381,6 +388,7 @@ 2C8A09D726751A0C00993638 /* WalletConnect.swift in Sources */, 2C03D1D2269B407900EF10EA /* NetworkMonitor.swift in Sources */, 2C8A09E326757FC000993638 /* AccountCellView.swift in Sources */, + 2C6B964C26B9D92500D2C819 /* NSColor.swift in Sources */, 2C603D0226B6E13F00956955 /* String.swift in Sources */, 2CC89471269A334A00879245 /* UserDefaults.swift in Sources */, 2C78F8282683BDCC00C10670 /* Alert.swift in Sources */, diff --git a/Encrypted Ink/Colors.xcassets/Contents.json b/Encrypted Ink/Colors.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Encrypted Ink/Colors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Encrypted Ink/Colors.xcassets/InkBlue.colorset/Contents.json b/Encrypted Ink/Colors.xcassets/InkBlue.colorset/Contents.json new file mode 100644 index 00000000..8ade10cb --- /dev/null +++ b/Encrypted Ink/Colors.xcassets/InkBlue.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xB0", + "green" : "0x17", + "red" : "0x1B" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Encrypted Ink/Colors.xcassets/InkGreen.colorset/Contents.json b/Encrypted Ink/Colors.xcassets/InkGreen.colorset/Contents.json new file mode 100644 index 00000000..ea48f807 --- /dev/null +++ b/Encrypted Ink/Colors.xcassets/InkGreen.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x5A", + "green" : "0x5D", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Encrypted Ink/Colors.xcassets/InkRed.colorset/Contents.json b/Encrypted Ink/Colors.xcassets/InkRed.colorset/Contents.json new file mode 100644 index 00000000..892a5660 --- /dev/null +++ b/Encrypted Ink/Colors.xcassets/InkRed.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x14", + "green" : "0x00", + "red" : "0xFA" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Encrypted Ink/Extensions/NSColor.swift b/Encrypted Ink/Extensions/NSColor.swift new file mode 100644 index 00000000..7fe4d672 --- /dev/null +++ b/Encrypted Ink/Extensions/NSColor.swift @@ -0,0 +1,9 @@ +// Copyright © 2021 Encrypted Ink. All rights reserved. + +import Cocoa + +extension NSColor { + + static let inkGreen = NSColor(named: "InkGreen")! + +} From a3155beb58b91099e9ba735b799677615e439f56 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Tue, 3 Aug 2021 23:15:58 +0300 Subject: [PATCH 19/21] Blink new account cell --- Encrypted Ink/Screens/AccountsListViewController.swift | 7 +++++++ Encrypted Ink/Views/AccountCellView.swift | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/Encrypted Ink/Screens/AccountsListViewController.swift b/Encrypted Ink/Screens/AccountsListViewController.swift index 9617d8fc..c3783f5d 100644 --- a/Encrypted Ink/Screens/AccountsListViewController.swift +++ b/Encrypted Ink/Screens/AccountsListViewController.swift @@ -109,9 +109,16 @@ class AccountsListViewController: NSViewController { reloadTitle() updateCellModels() tableView.reloadData() + blinkNewWalletCell() // TODO: show backup phrase } + private func blinkNewWalletCell() { + let row = cellModels.count - 1 + tableView.scrollRowToVisible(row) + (tableView.rowView(atRow: row, makeIfNecessary: false) as? AccountCellView)?.blink() + } + @objc private func didClickImportAccount() { let importViewController = instantiate(ImportViewController.self) importViewController.onSelectedWallet = onSelectedWallet diff --git a/Encrypted Ink/Views/AccountCellView.swift b/Encrypted Ink/Views/AccountCellView.swift index cdd4ceca..29950921 100644 --- a/Encrypted Ink/Views/AccountCellView.swift +++ b/Encrypted Ink/Views/AccountCellView.swift @@ -20,4 +20,14 @@ class AccountCellView: NSTableRowView { addressTextField.stringValue = without0x.prefix(4) + "..." + without0x.suffix(4) } + func blink() { + let initialBackgroundColor = backgroundColor + backgroundColor = .inkGreen + NSAnimationContext.runAnimationGroup { [weak self] context in + context.duration = 1.2 + context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) + self?.animator().backgroundColor = initialBackgroundColor + } + } + } From 4887c892c8c925eea14ece3c661c04325844cedc Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Tue, 3 Aug 2021 23:37:01 +0300 Subject: [PATCH 20/21] Pass imported wallet id to accounts list --- Encrypted Ink/Screens/AccountsListViewController.swift | 1 + Encrypted Ink/Screens/ImportViewController.swift | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Encrypted Ink/Screens/AccountsListViewController.swift b/Encrypted Ink/Screens/AccountsListViewController.swift index c3783f5d..2cb85f8d 100644 --- a/Encrypted Ink/Screens/AccountsListViewController.swift +++ b/Encrypted Ink/Screens/AccountsListViewController.swift @@ -9,6 +9,7 @@ class AccountsListViewController: NSViewController { private var cellModels = [CellModel]() var onSelectedWallet: ((InkWallet) -> Void)? + var newWalletId: String? enum CellModel { case wallet(InkWallet) diff --git a/Encrypted Ink/Screens/ImportViewController.swift b/Encrypted Ink/Screens/ImportViewController.swift index f35fc619..b9b45f97 100644 --- a/Encrypted Ink/Screens/ImportViewController.swift +++ b/Encrypted Ink/Screens/ImportViewController.swift @@ -52,21 +52,22 @@ class ImportViewController: NSViewController { private func importWith(input: String, password: String?) { do { - _ = try walletsManager.addWallet(input: input, inputPassword: password) - showAccountsList() + let wallet = try walletsManager.addWallet(input: input, inputPassword: password) + showAccountsList(newWalletId: wallet.id) } catch { Alert.showWithMessage("Failed to import account", style: .critical) } } - private func showAccountsList() { + private func showAccountsList(newWalletId: String?) { let accountsListViewController = instantiate(AccountsListViewController.self) accountsListViewController.onSelectedWallet = onSelectedWallet + accountsListViewController.newWalletId = newWalletId view.window?.contentViewController = accountsListViewController } @IBAction func cancelButtonTapped(_ sender: NSButton) { - showAccountsList() + showAccountsList(newWalletId: nil) } } From 6a2f5eb7250dd13da9758990ba61b03933576ca6 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Tue, 3 Aug 2021 23:58:05 +0300 Subject: [PATCH 21/21] Blink imported wallet in list, don't copy wallets for list models --- .../Screens/AccountsListViewController.swift | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Encrypted Ink/Screens/AccountsListViewController.swift b/Encrypted Ink/Screens/AccountsListViewController.swift index 2cb85f8d..6a7aa001 100644 --- a/Encrypted Ink/Screens/AccountsListViewController.swift +++ b/Encrypted Ink/Screens/AccountsListViewController.swift @@ -12,7 +12,7 @@ class AccountsListViewController: NSViewController { var newWalletId: String? enum CellModel { - case wallet(InkWallet) + case wallet case addAccountOption(AddAccountOption) } @@ -58,6 +58,11 @@ class AccountsListViewController: NSViewController { NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSApplication.didBecomeActiveNotification, object: nil) } + override func viewDidAppear() { + super.viewDidAppear() + blinkNewWalletCellIfNeeded() + } + private func setupAccountsMenu() { let menu = NSMenu() menu.delegate = self @@ -106,18 +111,21 @@ class AccountsListViewController: NSViewController { } @objc private func didClickCreateAccount() { - _ = try? walletsManager.createWallet() + let wallet = try? walletsManager.createWallet() + newWalletId = wallet?.id reloadTitle() updateCellModels() tableView.reloadData() - blinkNewWalletCell() + blinkNewWalletCellIfNeeded() // TODO: show backup phrase } - private func blinkNewWalletCell() { - let row = cellModels.count - 1 + private func blinkNewWalletCellIfNeeded() { + guard let id = newWalletId else { return } + newWalletId = nil + guard let row = wallets.firstIndex(where: { $0.id == id }), row < cellModels.count else { return } tableView.scrollRowToVisible(row) - (tableView.rowView(atRow: row, makeIfNecessary: false) as? AccountCellView)?.blink() + (tableView.rowView(atRow: row, makeIfNecessary: true) as? AccountCellView)?.blink() } @objc private func didClickImportAccount() { @@ -219,7 +227,7 @@ class AccountsListViewController: NSViewController { cellModels = [.addAccountOption(.createNew), .addAccountOption(.importExisting)] tableView.shouldShowRightClickMenu = false } else { - cellModels = wallets.map { .wallet($0) } + cellModels = Array(repeating: CellModel.wallet, count: wallets.count) tableView.shouldShowRightClickMenu = true } } @@ -233,7 +241,8 @@ extension AccountsListViewController: NSTableViewDelegate { let model = cellModels[row] switch model { - case let .wallet(wallet): + case .wallet: + let wallet = wallets[row] if let onSelectedWallet = onSelectedWallet { onSelectedWallet(wallet) } else { @@ -262,7 +271,8 @@ extension AccountsListViewController: NSTableViewDataSource { func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { let model = cellModels[row] switch model { - case let .wallet(wallet): + case .wallet: + let wallet = wallets[row] let rowView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("AccountCellView"), owner: self) as? AccountCellView rowView?.setup(address: wallet.ethereumAddress ?? "") return rowView