From b0a31147dd50ce7ec2d0dd3779bf5fbd0129b2f6 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 23 Sep 2020 21:26:10 +0300 Subject: [PATCH] Migration for iOS. --- ios/CakeWallet/EncryptedFile.swift | 50 --- ios/CakeWallet/decrypt.swift | 16 + ios/Flutter/.last_build_id | 2 +- ios/Podfile | 1 + ios/Podfile.lock | 6 +- ios/Runner.xcodeproj/project.pbxproj | 24 +- ios/Runner/AppDelegate.swift | 39 ++- lib/entities/default_settings_migration.dart | 15 +- lib/entities/fiat_currency.dart | 80 +++-- lib/entities/fs_migration.dart | 349 ++++++++++++++++--- lib/entities/ios_legacy_helper.dart | 22 +- lib/main.dart | 26 +- lib/monero/monero_wallet_service.dart | 5 +- lib/reactions/bootstrap.dart | 8 +- pubspec.lock | 29 +- pubspec.yaml | 5 +- tool/.secrets-test.json | 1 + tool/secrets.dart | 2 +- 18 files changed, 467 insertions(+), 213 deletions(-) delete mode 100644 ios/CakeWallet/EncryptedFile.swift create mode 100644 ios/CakeWallet/decrypt.swift diff --git a/ios/CakeWallet/EncryptedFile.swift b/ios/CakeWallet/EncryptedFile.swift deleted file mode 100644 index 8e4e4f9a1..000000000 --- a/ios/CakeWallet/EncryptedFile.swift +++ /dev/null @@ -1,50 +0,0 @@ -import Foundation -import CryptoSwift - -class EncryptedFile { - - private(set) var fileName: String - private(set) var url: URL - private let key: Array - private let salt: Array - - init(url: URL, key: String, salt: String) { - self.key = key.data(using: .utf8)?.bytes ?? [] - self.salt = salt.data(using: .utf8)?.bytes ?? [] - self.url = url - self.fileName = url.lastPathComponent - } - - func readRawContent() -> String? { - guard let binaryContent = try? Data(contentsOf: url) else { - return nil - } - - return String(data: binaryContent, encoding: .utf8) - } - - func decryptedContent() -> String? { - guard - let rawContent = readRawContent(), - let decryptedBytes = try? cipherBuilder().decrypt(rawContent.bytes) else { - return nil - } - - let decryptedData = Data(decryptedBytes) - return String(data: decryptedData, encoding: .utf8) - } - - func cipherBuilder() -> Cipher { - let PBKDF2key = try! PKCS5.PBKDF2(password: key, salt: salt, iterations: 4096, variant: .sha256).calculate() - return try! Blowfish(key: PBKDF2key, padding: .pkcs7) - } -} - -func readTradesList(key: String, salt: String) -> String? { - let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("trades_list.json") - - return EncryptedFile( - url: url, - key: key, - salt: salt).decryptedContent() -} diff --git a/ios/CakeWallet/decrypt.swift b/ios/CakeWallet/decrypt.swift new file mode 100644 index 000000000..5f24ad3fb --- /dev/null +++ b/ios/CakeWallet/decrypt.swift @@ -0,0 +1,16 @@ +import Foundation +import CryptoSwift + +func decrypt(data: Data, key: String, salt: String) -> String? { + let keyBytes = key.data(using: .utf8)?.bytes ?? [] + let saltBytes = salt.data(using: .utf8)?.bytes ?? [] + + guard let PBKDF2key = try? PKCS5.PBKDF2(password: keyBytes, salt: saltBytes, iterations: 4096, variant: .sha256).calculate(), + let cipher = try? Blowfish(key: PBKDF2key, padding: .pkcs7), + let decryptedBytes = try? cipher.decrypt(data.bytes) else { + return nil + } + + let decryptedData = Data(decryptedBytes) + return String(data: decryptedData, encoding: .utf8) +} diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index 969ed63e6..c4f58572f 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -09c81fe0a3d701eb6da3bd2c6fc5ec65 \ No newline at end of file +bc336703210c48e30d7216fac3fe1c0f \ No newline at end of file diff --git a/ios/Podfile b/ios/Podfile index 46c468f1a..9270a61cb 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,6 @@ # Uncomment this line to define a global platform for your project platform :ios, '9.0' +source 'https://github.com/CocoaPods/Specs.git' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b2c8f4047..6bf6752d0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -64,7 +64,7 @@ DEPENDENCIES: - url_launcher (from `.symlinks/plugins/url_launcher/ios`) SPEC REPOS: - trunk: + https://github.com/CocoaPods/Specs.git: - CryptoSwift - MTBBarcodeScanner - Reachability @@ -117,6 +117,6 @@ SPEC CHECKSUMS: SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef -PODFILE CHECKSUM: ade2ba43f8c2af4060c025bfd25a553d068ab914 +PODFILE CHECKSUM: ba3d2157523e2f4dc333b987efdac6635da8125d -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e9ba1e5d1..7a6beda6a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 0C44A71A2518EF8000B570ED /* EncryptedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44A7192518EF8000B570ED /* EncryptedFile.swift */; }; + 0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44A7192518EF8000B570ED /* decrypt.swift */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 20ED0868E1BD7E12278C0CB3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B26E3F56D69167FBB1DC160A /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; @@ -17,21 +17,9 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ - 0C44A7192518EF8000B570ED /* EncryptedFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedFile.swift; sourceTree = ""; }; + 0C44A7192518EF8000B570ED /* decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = decrypt.swift; sourceTree = ""; }; + 0C9986A3251A932F00D566FD /* CryptoSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CryptoSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 20F67A1B2C2FCB2A3BB048C1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -66,6 +54,7 @@ 06957875428D0F5AAE053765 /* Frameworks */ = { isa = PBXGroup; children = ( + 0C9986A3251A932F00D566FD /* CryptoSwift.framework */, B26E3F56D69167FBB1DC160A /* Pods_Runner.framework */, ); name = Frameworks; @@ -74,7 +63,7 @@ 0C44A7182518EF4A00B570ED /* CakeWallet */ = { isa = PBXGroup; children = ( - 0C44A7192518EF8000B570ED /* EncryptedFile.swift */, + 0C44A7192518EF8000B570ED /* decrypt.swift */, ); path = CakeWallet; sourceTree = ""; @@ -147,7 +136,6 @@ 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, DD8DB3179CA4E511F9954A6F /* [CP] Embed Pods Frameworks */, ); @@ -284,7 +272,7 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - 0C44A71A2518EF8000B570ED /* EncryptedFile.swift in Sources */, + 0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index c93e03e8c..01aa14195 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -14,26 +14,41 @@ import Flutter (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in switch call.method { - case "read_trade_list": + case "decrypt": guard let args = call.arguments as? Dictionary, + let data = args["bytes"] as? FlutterStandardTypedData, let key = args["key"] as? String, let salt = args["salt"] as? String else { - return - } - let normalizedKey = key.replacingOccurrences(of: "-", with: "") - result(readTradesList(key: normalizedKey, salt: salt)) - case "read_encrypted_file": - guard let args = call.arguments as? Dictionary, - let path = args["path"] as? String, - let key = args["key"] as? String, - let salt = args["salt"] as? String else { + result(nil) return } - let content = EncryptedFile(url: URL(fileURLWithPath: path), key: key, salt: salt).decryptedContent() + let content = decrypt(data: data.data, key: key, salt: salt) result(content) + case "read_user_defaults": + guard let args = call.arguments as? Dictionary, + let key = args["key"] as? String, + let type = args["type"] as? String else { + result(nil) + return + } + + var value: Any? + + switch (type) { + case "string": + value = UserDefaults.standard.string(forKey: key) + case "int": + value = UserDefaults.standard.integer(forKey: key) + case "bool": + value = UserDefaults.standard.bool(forKey: key) + default: + break + } + + result(value) default: - break + result(FlutterMethodNotImplemented) } }) diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index f16349387..28fc3da90 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -1,3 +1,4 @@ +import 'dart:io' show Platform; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -8,11 +9,22 @@ import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/node_list.dart'; import 'package:cake_wallet/entities/transaction_priority.dart'; +import 'package:cake_wallet/entities/contact.dart'; +import 'package:cake_wallet/entities/fs_migration.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; +import 'package:cake_wallet/exchange/trade.dart'; Future defaultSettingsMigration( {@required int version, @required SharedPreferences sharedPreferences, - @required Box nodes}) async { + @required Box nodes, + @required Box walletInfoSource, + @required Box tradeSource, + @required Box contactSource}) async { + if (Platform.isIOS) { + await ios_migrate_v1(walletInfoSource, tradeSource, contactSource); + } + final currentVersion = sharedPreferences.getInt('current_default_settings_migration_version') ?? 0; @@ -60,6 +72,7 @@ Future defaultSettingsMigration( await changeBitcoinCurrentElectrumServerToDefault( sharedPreferences: sharedPreferences, nodes: nodes); break; + default: break; } diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index 8066c8f6a..c47f91818 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -3,44 +3,7 @@ import 'package:cake_wallet/entities/enumerable_item.dart'; class FiatCurrency extends EnumerableItem with Serializable { const FiatCurrency({String symbol}) : super(title: symbol, raw: symbol); - @override - bool operator ==(Object other) => other is FiatCurrency && other.raw == raw; - - static const all = [ - FiatCurrency.aud, - FiatCurrency.bgn, - FiatCurrency.brl, - FiatCurrency.cad, - FiatCurrency.chf, - FiatCurrency.cny, - FiatCurrency.czk, - FiatCurrency.eur, - FiatCurrency.dkk, - FiatCurrency.gbp, - FiatCurrency.hkd, - FiatCurrency.hrk, - FiatCurrency.huf, - FiatCurrency.idr, - FiatCurrency.ils, - FiatCurrency.inr, - FiatCurrency.isk, - FiatCurrency.jpy, - FiatCurrency.krw, - FiatCurrency.mxn, - FiatCurrency.myr, - FiatCurrency.nok, - FiatCurrency.nzd, - FiatCurrency.php, - FiatCurrency.pln, - FiatCurrency.ron, - FiatCurrency.rub, - FiatCurrency.sek, - FiatCurrency.sgd, - FiatCurrency.thb, - FiatCurrency.usd, - FiatCurrency.zar, - FiatCurrency.vef - ]; + static List get all => _all.values.toList(); static const aud = FiatCurrency(symbol: 'AUD'); static const bgn = FiatCurrency(symbol: 'BGN'); @@ -76,6 +39,47 @@ class FiatCurrency extends EnumerableItem with Serializable { static const zar = FiatCurrency(symbol: 'ZAR'); static const vef = FiatCurrency(symbol: 'VEF'); + static final _all = { + FiatCurrency.aud.raw: FiatCurrency.aud, + FiatCurrency.bgn.raw: FiatCurrency.bgn, + FiatCurrency.brl.raw: FiatCurrency.brl, + FiatCurrency.cad.raw: FiatCurrency.cad, + FiatCurrency.chf.raw: FiatCurrency.chf, + FiatCurrency.cny.raw: FiatCurrency.cny, + FiatCurrency.czk.raw: FiatCurrency.czk, + FiatCurrency.eur.raw: FiatCurrency.eur, + FiatCurrency.dkk.raw: FiatCurrency.dkk, + FiatCurrency.gbp.raw: FiatCurrency.gbp, + FiatCurrency.hkd.raw: FiatCurrency.hkd, + FiatCurrency.hrk.raw: FiatCurrency.hrk, + FiatCurrency.huf.raw: FiatCurrency.huf, + FiatCurrency.idr.raw: FiatCurrency.idr, + FiatCurrency.ils.raw: FiatCurrency.ils, + FiatCurrency.inr.raw: FiatCurrency.inr, + FiatCurrency.isk.raw: FiatCurrency.isk, + FiatCurrency.jpy.raw: FiatCurrency.jpy, + FiatCurrency.krw.raw: FiatCurrency.krw, + FiatCurrency.mxn.raw: FiatCurrency.mxn, + FiatCurrency.myr.raw: FiatCurrency.myr, + FiatCurrency.nok.raw: FiatCurrency.nok, + FiatCurrency.nzd.raw: FiatCurrency.nzd, + FiatCurrency.php.raw: FiatCurrency.php, + FiatCurrency.pln.raw: FiatCurrency.pln, + FiatCurrency.ron.raw: FiatCurrency.ron, + FiatCurrency.rub.raw: FiatCurrency.rub, + FiatCurrency.sek.raw: FiatCurrency.sek, + FiatCurrency.sgd.raw: FiatCurrency.sgd, + FiatCurrency.thb.raw: FiatCurrency.thb, + FiatCurrency.usd.raw: FiatCurrency.usd, + FiatCurrency.zar.raw: FiatCurrency.zar, + FiatCurrency.vef.raw: FiatCurrency.vef + }; + + static FiatCurrency deserialize({String raw}) => _all[raw]; + + @override + bool operator ==(Object other) => other is FiatCurrency && other.raw == raw; + @override int get hashCode => raw.hashCode ^ title.hashCode; } diff --git a/lib/entities/fs_migration.dart b/lib/entities/fs_migration.dart index 80547df16..1d3ae8d4f 100644 --- a/lib/entities/fs_migration.dart +++ b/lib/entities/fs_migration.dart @@ -1,56 +1,94 @@ import 'dart:io'; import 'dart:convert'; +import 'package:cake_wallet/core/key_service.dart'; +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; +import 'package:cake_wallet/entities/encrypt.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/entities/ios_legacy_helper.dart' + as ios_legacy_helper; +import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/wallet_info.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; const reservedNames = ["flutter_assets", "wallets", "db"]; Future migrate_android_v1() async { final appDocDir = await getApplicationDocumentsDirectory(); - await migrate_hives(appDocDir: appDocDir); - await migrate_wallets(appDocDir: appDocDir); + await android_migrate_hives(appDocDir: appDocDir); + await android_migrate_wallets(appDocDir: appDocDir); } -Future migrate_ios_v1() async { - final appDocDir = await getApplicationDocumentsDirectory(); +Future ios_migrate_v1(Box walletInfoSource, Box tradeSource, Box contactSource) async { + final prefs = await SharedPreferences.getInstance(); + + if (prefs.getBool('ios_migration_v1_completed') ?? false) { + return; + } + + await ios_migrate_user_defaults(); + await ios_migrate_pin(); + await ios_migrate_wallet_passwords(); + await ios_migrate_wallet_info(walletInfoSource); + await ios_migrate_trades_list(tradeSource); + await ios_migrate_address_book(contactSource); + + await prefs.setBool('ios_migration_v1_completed', true); +} + +Future ios_migrate_user_defaults() async { //get the new shared preferences instance - SharedPreferences prefs = await SharedPreferences.getInstance(); + final prefs = await SharedPreferences.getInstance(); + + if (prefs.getBool('ios_migration_user_defaults_completed') ?? false) { + return; + } //translate the node uri - String nodeURI = prefs.getString('node_uri'); - await prefs.setString('current_node_id', nodeURI); + final nodeURI = await ios_legacy_helper.getString('node_uri'); + // await prefs.setString('current_node_id', nodeURI); + await prefs.setInt('current_node_id', 0); //should we provide default btc node key? - int activeCurrency = prefs.getInt('currency'); - await prefs.setInt('current_fiat_currency', activeCurrency); + final activeCurrency = await ios_legacy_helper.getInt('currency'); + final convertedCurrency = convertFiatLegacy(activeCurrency); + + if (convertedCurrency != null) { + await prefs.setString( + 'current_fiat_currency', convertedCurrency.serialize()); + } //translate fee priority - int activeFeeTier = prefs.getInt('saved_fee_priority'); + final activeFeeTier = await ios_legacy_helper.getInt('saved_fee_priority'); await prefs.setInt('current_fee_priority', activeFeeTier); //translate current balance mode - int currentBalanceMode = prefs.getInt('display_balance_mode'); + final currentBalanceMode = + await ios_legacy_helper.getInt('display_balance_mode'); await prefs.setInt('current_balance_display_mode', currentBalanceMode); //translate should save recipient address - bool shouldSave = prefs.getBool('should_save_recipient_address'); + final shouldSave = + await ios_legacy_helper.getBool('should_save_recipient_address'); await prefs.setBool('save_recipient_address', shouldSave); //translate biometric - bool biometricOn = prefs.getBool('biometric_authentication_on'); + final biometricOn = + await ios_legacy_helper.getBool('biometric_authentication_on'); await prefs.setBool('allow_biometrical_authentication', biometricOn); //read the current theme as integer, write it back as a bool - int currentTheme = prefs.getInt('current-theme'); + final currentTheme = prefs.getInt('current-theme'); bool isDark = false; if (currentTheme == 1) { isDark = true; @@ -58,15 +96,108 @@ Future migrate_ios_v1() async { await prefs.setBool('dark_theme', isDark); //assign the pin lenght - int pinLength = prefs.getInt('pin-length'); + final pinLength = await ios_legacy_helper.getInt('pin-length'); await prefs.setInt('pin-length', pinLength); //default value for display list key? - String walletName = prefs.getString('current_wallet_name'); + final walletName = await ios_legacy_helper.getString('current_wallet_name'); await prefs.setString('current_wallet_name', walletName); + + await prefs.setInt('current_wallet_type', serializeToInt(WalletType.monero)); + + await prefs.setBool('ios_migration_user_defaults_completed', true); } -Future migrate_hives({Directory appDocDir}) async { +Future ios_migrate_pin() async { + final prefs = await SharedPreferences.getInstance(); + + if (prefs.getBool('ios_migration_pin_completed') ?? false) { + return; + } + + final flutterSecureStorage = FlutterSecureStorage(); + final pinPassword = await flutterSecureStorage.read( + key: 'pin_password', iOptions: IOSOptions(syncFlag: "syna")); + final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); + final encodedPassword = encodedPinCode(pin: pinPassword); + await flutterSecureStorage.write(key: key, value: encodedPassword); + await prefs.setBool('ios_migration_pin_completed', true); +} + +Future ios_migrate_wallet_passwords() async { + final prefs = await SharedPreferences.getInstance(); + + if (prefs.getBool('ios_migration_wallet_passwords_completed') ?? false) { + return; + } + + final appDocDir = await getApplicationDocumentsDirectory(); + final flutterSecureStorage = FlutterSecureStorage(); + final keyService = KeyService(flutterSecureStorage); + final walletsDir = Directory('${appDocDir.path}/wallets'); + final moneroWalletsDir = Directory('${walletsDir.path}/monero'); + + moneroWalletsDir.listSync().forEach((item) async { + try { + if (item is Directory) { + final name = item.path.split('/').last; + final oldKey = 'wallet_monero_' + name + '_password'; + final password = await flutterSecureStorage.read( + key: oldKey, iOptions: IOSOptions(syncFlag: "syna")); + await keyService.saveWalletPassword( + walletName: name, password: password); + } + } catch (e) { + print(e.toString()); + } + }); + + await prefs.setBool('ios_migration_wallet_passwords_completed', true); +} + +FiatCurrency convertFiatLegacy(int raw) { + final _map = { + 0: 'aud', + 1: 'bgn', + 2: 'brl', + 3: 'cad', + 4: 'chf', + 5: 'cny', + 6: 'czk', + 7: 'eur', + 8: 'dkk', + 9: 'gbp', + 10: 'hkd', + 11: 'hrk', + 12: 'huf', + 13: 'idr', + 14: 'ils', + 15: 'inr', + 16: 'isk', + 17: 'jpy', + 18: 'krw', + 19: 'mxn', + 20: 'myr', + 21: 'nok', + 22: 'nzd', + 23: 'php', + 24: 'pln', + 25: 'ron', + 26: 'rub', + 27: 'sek', + 28: 'sgd', + 29: 'thb', + 30: 'try', + 31: 'usd', + 32: 'zar', + 33: 'vef' + }; + final fiatAsString = _map[raw]; + + return FiatCurrency.deserialize(raw: fiatAsString.toUpperCase()); +} + +Future android_migrate_hives({Directory appDocDir}) async { final dbDir = Directory('${appDocDir.path}/db'); final files = List(); @@ -89,7 +220,7 @@ Future migrate_hives({Directory appDocDir}) async { }); } -Future migrate_wallets({Directory appDocDir}) async { +Future android_migrate_wallets({Directory appDocDir}) async { final walletsDir = Directory('${appDocDir.path}/wallets'); final moneroWalletsDir = Directory('${walletsDir.path}/monero'); final dirs = List(); @@ -123,54 +254,162 @@ Future migrate_wallets({Directory appDocDir}) async { }); } -Future migrate_ios_wallet_info( - {@required Directory appDocDir, - @required Box walletsInfo}) async { - // final walletsDir = Directory('${appDocDir.path}/wallets'); - // final moneroWalletsDir = Directory('${walletsDir.path}/monero'); +Future ios_migrate_wallet_info(Box walletsInfoSource) async { + final prefs = await SharedPreferences.getInstance(); - // moneroWalletsDir.listSync().forEach((item) async { - // try { - // if (item is Directory) { - // final name = item.path.split('/').last; - // final configFile = File('${item.path}/$name.json'); - // final config = - // json.decode(configFile.readAsStringSync()) as Map; - // final isRecovery = config["isRecovery"] as bool ?? false; - // final id = - // walletTypeToString(WalletType.monero).toLowerCase() + '_' + name; - // final walletInfo = - // WalletInfo(id: id, name: name, isRecovery: isRecovery); + if (prefs.getBool('ios_migration_wallet_info_completed') ?? false) { + return; + } - // await walletsInfo.add(walletInfo); - // } - // } catch (e) { - // print(e.toString()); - // } - // }); + try { + final appDocDir = await getApplicationDocumentsDirectory(); + final walletsDir = Directory('${appDocDir.path}/wallets'); + final moneroWalletsDir = Directory('${walletsDir.path}/monero'); + final infoRecords = moneroWalletsDir + .listSync() + .map((item) { + try { + if (item is Directory) { + final name = item.path.split('/').last; + final configFile = File('${item.path}/$name.json'); + + if (!configFile.existsSync()) { + return null; + } + + final config = json.decode(configFile.readAsStringSync()) + as Map; + final isRecovery = config['isRecovery'] as bool ?? false; + final dateAsDouble = config['date'] as double; + final timestamp = dateAsDouble.toInt() * 1000; + final date = DateTime.fromMillisecondsSinceEpoch(timestamp); + final id = walletTypeToString(WalletType.monero).toLowerCase() + + '_' + + name; + final exist = walletsInfoSource.values + .firstWhere((el) => el.id == id, orElse: () => null) != + null; + + if (exist) { + return null; + } + + final walletInfo = WalletInfo.external( + id: id, + type: WalletType.monero, + name: name, + isRecovery: isRecovery, + restoreHeight: 0, + date: date, + dirPath: item.path, + path: '${item.path}/$name'); + + return walletInfo; + } + } catch (e) { + print(e.toString()); + return null; + } + }) + .where((el) => el != null) + .toList(); + print(infoRecords); + await walletsInfoSource.addAll(infoRecords); + await prefs.setBool('ios_migration_wallet_info_completed', true); + } catch (e) { + print(e.toString()); + } } -Future migrate_ios_trades_list( - {@required Directory appDocDir, @required Box trades}) async { - final adderessBookJSON = File('${appDocDir.path}/trades_list.json'); - final List trades = - json.decode(adderessBookJSON.readAsStringSync()) as List; +Future ios_migrate_trades_list(Box tradeSource) async { + final prefs = await SharedPreferences.getInstance(); + + if (prefs.getBool('ios_migration_trade_list_completed') ?? false) { + return; + } + + try { + final appDocDir = await getApplicationDocumentsDirectory(); + final url = '${appDocDir.path}/trades_list.json'; + final file = File(url); + + if (!file.existsSync()) { + await prefs.setBool('ios_migration_trade_list_completed', true); + return; + } + + final content = file.readAsBytesSync(); + final flutterSecureStorage = FlutterSecureStorage(); + final masterPassword = await flutterSecureStorage.read( + key: 'master_password', iOptions: IOSOptions(syncFlag: "syna")); + final key = masterPassword.replaceAll('-', ''); + final decoded = await ios_legacy_helper.decrypt(content, + key: key, salt: secrets.keychainSalt); + final decodedJson = json.decode(decoded) as List; + final trades = decodedJson.map((dynamic el) { + final elAsMap = el as Map; + final providerAsString = elAsMap['provider'] as String; + final fromAsString = elAsMap['from'] as String; + final toAsString = elAsMap['to'] as String; + final dateAsDouble = elAsMap['date'] as double; + final tradeId = elAsMap['tradeID'] as String; + final to = CryptoCurrency.fromString(toAsString); + final from = CryptoCurrency.fromString(fromAsString); + final timestamp = dateAsDouble.toInt() * 1000; + final date = DateTime.fromMillisecondsSinceEpoch(timestamp); + ExchangeProviderDescription provider; + + switch (providerAsString.toLowerCase()) { + case 'changenow': + provider = ExchangeProviderDescription.changeNow; + break; + case 'xmr.to': + provider = ExchangeProviderDescription.xmrto; + break; + case 'morph': + provider = ExchangeProviderDescription.morphToken; + break; + default: + break; + } + + return Trade( + id: tradeId, provider: provider, from: from, to: to, createdAt: date); + }); + await tradeSource.addAll(trades); + await prefs.setBool('ios_migration_trade_list_completed', true); + } catch (e) { + print(e.toString()); + } } -Future migrate_ios_address_book( - {@required Directory appDocDir, @required Box contacts}) async { - final adderessBookJSON = File('${appDocDir.path}/address_book.json'); +Future ios_migrate_address_book(Box contactSource) async { + final prefs = await SharedPreferences.getInstance(); + + if (prefs.getBool('ios_migration_address_book_completed') ?? false) { + return; + } + + final appDocDir = await getApplicationDocumentsDirectory(); + final addressBookJSON = File('${appDocDir.path}/address_book.json'); + + if (!addressBookJSON.existsSync()) { + await prefs.setBool('ios_migration_address_book_completed', true); + return; + } + final List addresses = - json.decode(adderessBookJSON.readAsStringSync()) as List; - - addresses.forEach((dynamic item) async { + json.decode(addressBookJSON.readAsStringSync()) as List; + final contacts = addresses.map((dynamic item) { final _item = item as Map; final type = _item["type"] as String; final address = _item["address"] as String; final name = _item["name"] as String; - final contact = Contact( - address: address, name: name, type: CryptoCurrency.fromString(type)); - await contacts.add(contact); + return Contact( + address: address, name: name, type: CryptoCurrency.fromString(type)); }); + + await contactSource.addAll(contacts); + await prefs.setBool('ios_migration_address_book_completed', true); } diff --git a/lib/entities/ios_legacy_helper.dart b/lib/entities/ios_legacy_helper.dart index 1e4a2fba5..4f84a6487 100644 --- a/lib/entities/ios_legacy_helper.dart +++ b/lib/entities/ios_legacy_helper.dart @@ -1,15 +1,25 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; const platform = const MethodChannel('com.cakewallet.cakewallet/legacy_wallet_migration'); -Future readTradeList( +Future decrypt(Uint8List bytes, {@required String key, @required String salt}) async => - await platform.invokeMethod('read_trade_list', {'key': key, 'salt': salt}); + await platform + .invokeMethod('decrypt', {'bytes': bytes, 'key': key, 'salt': salt}); -Future readEncryptedFile(String url, - {@required String key, @required String salt}) async => - await platform.invokeMethod( - 'read_encrypted_file', {'url': url, 'key': key, 'salt': salt}); +Future readUserDefaults(String key, {@required String type}) async => + await platform + .invokeMethod('read_user_defaults', {'key': key, 'type': type}); + +Future getString(String key) async => + await readUserDefaults(key, type: 'string') as String; + +Future getBool(String key) async => + await readUserDefaults(key, type: 'bool') as bool; + +Future getInt(String key) async => + await readUserDefaults(key, type: 'int') as int; diff --git a/lib/main.dart b/lib/main.dart index 6370bf55d..267afe256 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/fs_migration.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/reactions/bootstrap.dart'; @@ -94,7 +95,7 @@ void main() async { final exchangeTemplates = await Hive.openBox(ExchangeTemplate.boxName); - final sharedPreferences = await SharedPreferences.getInstance(); + // final sharedPreferences = await SharedPreferences.getInstance(); // final walletService = WalletService(); // final fiatConvertationService = FiatConvertationService(); // final walletListService = WalletListService( @@ -138,7 +139,6 @@ void main() async { templates: templates, exchangeTemplates: exchangeTemplates, initialMigrationVersion: 4); - // setReactions( // settingsStore: settingsStore, // priceStore: priceStore, @@ -147,8 +147,8 @@ void main() async { // walletService: walletService, // // authenticationStore: authenticationStore, // loginStore: loginStore); - - runApp(CakeWalletApp()); + final initialLanguage = await Language.localeDetection(); + runApp(CakeWalletApp(initialLanguage)); } Future initialSetup( @@ -160,10 +160,13 @@ Future initialSetup( // @required FiatConvertationService fiatConvertationService, @required Box