Migration for iOS.

M committed Sep 23, 2020 at 21:26 UTC b0a31147dd50ce7ec2d0dd3779bf5fbd0129b2f6
18 files changed +466 -212
ios/CakeWallet/EncryptedFile.swift deleted
-50
@@ -1,50 +0,0 @@
1 -import Foundation
2 -import CryptoSwift
3 -
4 -class EncryptedFile {
5 -
6 - private(set) var fileName: String
7 - private(set) var url: URL
8 - private let key: Array<UInt8>
9 - private let salt: Array<UInt8>
10 -
11 - init(url: URL, key: String, salt: String) {
12 - self.key = key.data(using: .utf8)?.bytes ?? []
13 - self.salt = salt.data(using: .utf8)?.bytes ?? []
14 - self.url = url
15 - self.fileName = url.lastPathComponent
16 - }
17 -
18 - func readRawContent() -> String? {
19 - guard let binaryContent = try? Data(contentsOf: url) else {
20 - return nil
21 - }
22 -
23 - return String(data: binaryContent, encoding: .utf8)
24 - }
25 -
26 - func decryptedContent() -> String? {
27 - guard
28 - let rawContent = readRawContent(),
29 - let decryptedBytes = try? cipherBuilder().decrypt(rawContent.bytes) else {
30 - return nil
31 - }
32 -
33 - let decryptedData = Data(decryptedBytes)
34 - return String(data: decryptedData, encoding: .utf8)
35 - }
36 -
37 - func cipherBuilder() -> Cipher {
38 - let PBKDF2key = try! PKCS5.PBKDF2(password: key, salt: salt, iterations: 4096, variant: .sha256).calculate()
39 - return try! Blowfish(key: PBKDF2key, padding: .pkcs7)
40 - }
41 -}
42 -
43 -func readTradesList(key: String, salt: String) -> String? {
44 - let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("trades_list.json")
45 -
46 - return EncryptedFile(
47 - url: url,
48 - key: key,
49 - salt: salt).decryptedContent()
50 -}
ios/CakeWallet/decrypt.swift new
+16
@@ -0,0 +1,16 @@
1 +import Foundation
2 +import CryptoSwift
3 +
4 +func decrypt(data: Data, key: String, salt: String) -> String? {
5 + let keyBytes = key.data(using: .utf8)?.bytes ?? []
6 + let saltBytes = salt.data(using: .utf8)?.bytes ?? []
7 +
8 + guard let PBKDF2key = try? PKCS5.PBKDF2(password: keyBytes, salt: saltBytes, iterations: 4096, variant: .sha256).calculate(),
9 + let cipher = try? Blowfish(key: PBKDF2key, padding: .pkcs7),
10 + let decryptedBytes = try? cipher.decrypt(data.bytes) else {
11 + return nil
12 + }
13 +
14 + let decryptedData = Data(decryptedBytes)
15 + return String(data: decryptedData, encoding: .utf8)
16 +}
ios/Flutter/.last_build_id
+1 -1
@@ -1 +1 @@
1 -09c81fe0a3d701eb6da3bd2c6fc5ec65
\ No newline at end of file
1 +bc336703210c48e30d7216fac3fe1c0f
\ No newline at end of file
ios/Podfile
+1
@@ -1,5 +1,6 @@
1 # Uncomment this line to define a global platform for your project
2 platform :ios, '9.0'
3 +source 'https://github.com/CocoaPods/Specs.git'
4
5 # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
6 ENV['COCOAPODS_DISABLE_STATS'] = 'true'
ios/Podfile.lock
+3 -3
@@ -64,7 +64,7 @@ DEPENDENCIES:
64 - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
65
66 SPEC REPOS:
67 - trunk:
67 + https://github.com/CocoaPods/Specs.git:
68 - CryptoSwift
69 - MTBBarcodeScanner
70 - Reachability
@@ -117,6 +117,6 @@ SPEC CHECKSUMS:
117 SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699
118 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
119
120 -PODFILE CHECKSUM: ade2ba43f8c2af4060c025bfd25a553d068ab914
120 +PODFILE CHECKSUM: ba3d2157523e2f4dc333b987efdac6635da8125d
121
122 -COCOAPODS: 1.8.4
122 +COCOAPODS: 1.9.3
ios/Runner.xcodeproj/project.pbxproj
+6 -18
@@ -7,7 +7,7 @@
7 objects = {
8
9 /* Begin PBXBuildFile section */
10 - 0C44A71A2518EF8000B570ED /* EncryptedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44A7192518EF8000B570ED /* EncryptedFile.swift */; };
10 + 0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44A7192518EF8000B570ED /* decrypt.swift */; };
11 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
12 20ED0868E1BD7E12278C0CB3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B26E3F56D69167FBB1DC160A /* Pods_Runner.framework */; };
13 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
@@ -17,21 +17,9 @@
17 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
18 /* End PBXBuildFile section */
19
20 -/* Begin PBXCopyFilesBuildPhase section */
21 - 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
22 - isa = PBXCopyFilesBuildPhase;
23 - buildActionMask = 2147483647;
24 - dstPath = "";
25 - dstSubfolderSpec = 10;
26 - files = (
27 - );
28 - name = "Embed Frameworks";
29 - runOnlyForDeploymentPostprocessing = 0;
30 - };
31 -/* End PBXCopyFilesBuildPhase section */
32 -
20 /* Begin PBXFileReference section */
34 - 0C44A7192518EF8000B570ED /* EncryptedFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedFile.swift; sourceTree = "<group>"; };
21 + 0C44A7192518EF8000B570ED /* decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = decrypt.swift; sourceTree = "<group>"; };
22 + 0C9986A3251A932F00D566FD /* CryptoSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CryptoSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
23 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
24 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
25 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 = "<group>"; };
@@ -66,6 +54,7 @@
54 06957875428D0F5AAE053765 /* Frameworks */ = {
55 isa = PBXGroup;
56 children = (
57 + 0C9986A3251A932F00D566FD /* CryptoSwift.framework */,
58 B26E3F56D69167FBB1DC160A /* Pods_Runner.framework */,
59 );
60 name = Frameworks;
@@ -74,7 +63,7 @@
63 0C44A7182518EF4A00B570ED /* CakeWallet */ = {
64 isa = PBXGroup;
65 children = (
77 - 0C44A7192518EF8000B570ED /* EncryptedFile.swift */,
66 + 0C44A7192518EF8000B570ED /* decrypt.swift */,
67 );
68 path = CakeWallet;
69 sourceTree = "<group>";
@@ -147,7 +136,6 @@
136 97C146EA1CF9000F007C117D /* Sources */,
137 97C146EB1CF9000F007C117D /* Frameworks */,
138 97C146EC1CF9000F007C117D /* Resources */,
150 - 9705A1C41CF9048500538489 /* Embed Frameworks */,
139 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
140 DD8DB3179CA4E511F9954A6F /* [CP] Embed Pods Frameworks */,
141 );
@@ -284,7 +272,7 @@
272 files = (
273 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
274 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
287 - 0C44A71A2518EF8000B570ED /* EncryptedFile.swift in Sources */,
275 + 0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */,
276 );
277 runOnlyForDeploymentPostprocessing = 0;
278 };
ios/Runner/AppDelegate.swift
+24 -9
@@ -14,26 +14,41 @@ import Flutter
14 (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
15
16 switch call.method {
17 - case "read_trade_list":
17 + case "decrypt":
18 guard let args = call.arguments as? Dictionary<String, Any>,
19 + let data = args["bytes"] as? FlutterStandardTypedData,
20 let key = args["key"] as? String,
21 let salt = args["salt"] as? String else {
22 + result(nil)
23 return
24 }
23 - let normalizedKey = key.replacingOccurrences(of: "-", with: "")
24 - result(readTradesList(key: normalizedKey, salt: salt))
25 - case "read_encrypted_file":
25 +
26 + let content = decrypt(data: data.data, key: key, salt: salt)
27 + result(content)
28 + case "read_user_defaults":
29 guard let args = call.arguments as? Dictionary<String, Any>,
27 - let path = args["path"] as? String,
30 let key = args["key"] as? String,
29 - let salt = args["salt"] as? String else {
31 + let type = args["type"] as? String else {
32 + result(nil)
33 return
34 }
35
33 - let content = EncryptedFile(url: URL(fileURLWithPath: path), key: key, salt: salt).decryptedContent()
34 - result(content)
36 + var value: Any?
37 +
38 + switch (type) {
39 + case "string":
40 + value = UserDefaults.standard.string(forKey: key)
41 + case "int":
42 + value = UserDefaults.standard.integer(forKey: key)
43 + case "bool":
44 + value = UserDefaults.standard.bool(forKey: key)
45 + default:
46 + break
47 + }
48 +
49 + result(value)
50 default:
36 - break
51 + result(FlutterMethodNotImplemented)
52 }
53 })
54
lib/entities/default_settings_migration.dart
+14 -1
@@ -1,3 +1,4 @@
1 +import 'dart:io' show Platform;
2 import 'package:flutter/foundation.dart';
3 import 'package:hive/hive.dart';
4 import 'package:shared_preferences/shared_preferences.dart';
@@ -8,11 +9,22 @@ import 'package:cake_wallet/entities/balance_display_mode.dart';
9 import 'package:cake_wallet/entities/fiat_currency.dart';
10 import 'package:cake_wallet/entities/node_list.dart';
11 import 'package:cake_wallet/entities/transaction_priority.dart';
12 +import 'package:cake_wallet/entities/contact.dart';
13 +import 'package:cake_wallet/entities/fs_migration.dart';
14 +import 'package:cake_wallet/entities/wallet_info.dart';
15 +import 'package:cake_wallet/exchange/trade.dart';
16
17 Future defaultSettingsMigration(
18 {@required int version,
19 @required SharedPreferences sharedPreferences,
15 - @required Box<Node> nodes}) async {
20 + @required Box<Node> nodes,
21 + @required Box<WalletInfo> walletInfoSource,
22 + @required Box<Trade> tradeSource,
23 + @required Box<Contact> contactSource}) async {
24 + if (Platform.isIOS) {
25 + await ios_migrate_v1(walletInfoSource, tradeSource, contactSource);
26 + }
27 +
28 final currentVersion =
29 sharedPreferences.getInt('current_default_settings_migration_version') ??
30 0;
@@ -60,6 +72,7 @@ Future defaultSettingsMigration(
72 await changeBitcoinCurrentElectrumServerToDefault(
73 sharedPreferences: sharedPreferences, nodes: nodes);
74 break;
75 +
76 default:
77 break;
78 }
lib/entities/fiat_currency.dart
+42 -38
@@ -3,44 +3,7 @@ import 'package:cake_wallet/entities/enumerable_item.dart';
3 class FiatCurrency extends EnumerableItem<String> with Serializable<String> {
4 const FiatCurrency({String symbol}) : super(title: symbol, raw: symbol);
5
6 - @override
7 - bool operator ==(Object other) => other is FiatCurrency && other.raw == raw;
8 -
9 - static const all = [
10 - FiatCurrency.aud,
11 - FiatCurrency.bgn,
12 - FiatCurrency.brl,
13 - FiatCurrency.cad,
14 - FiatCurrency.chf,
15 - FiatCurrency.cny,
16 - FiatCurrency.czk,
17 - FiatCurrency.eur,
18 - FiatCurrency.dkk,
19 - FiatCurrency.gbp,
20 - FiatCurrency.hkd,
21 - FiatCurrency.hrk,
22 - FiatCurrency.huf,
23 - FiatCurrency.idr,
24 - FiatCurrency.ils,
25 - FiatCurrency.inr,
26 - FiatCurrency.isk,
27 - FiatCurrency.jpy,
28 - FiatCurrency.krw,
29 - FiatCurrency.mxn,
30 - FiatCurrency.myr,
31 - FiatCurrency.nok,
32 - FiatCurrency.nzd,
33 - FiatCurrency.php,
34 - FiatCurrency.pln,
35 - FiatCurrency.ron,
36 - FiatCurrency.rub,
37 - FiatCurrency.sek,
38 - FiatCurrency.sgd,
39 - FiatCurrency.thb,
40 - FiatCurrency.usd,
41 - FiatCurrency.zar,
42 - FiatCurrency.vef
43 - ];
6 + static List<FiatCurrency> get all => _all.values.toList();
7
8 static const aud = FiatCurrency(symbol: 'AUD');
9 static const bgn = FiatCurrency(symbol: 'BGN');
@@ -76,6 +39,47 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> {
39 static const zar = FiatCurrency(symbol: 'ZAR');
40 static const vef = FiatCurrency(symbol: 'VEF');
41
42 + static final _all = {
43 + FiatCurrency.aud.raw: FiatCurrency.aud,
44 + FiatCurrency.bgn.raw: FiatCurrency.bgn,
45 + FiatCurrency.brl.raw: FiatCurrency.brl,
46 + FiatCurrency.cad.raw: FiatCurrency.cad,
47 + FiatCurrency.chf.raw: FiatCurrency.chf,
48 + FiatCurrency.cny.raw: FiatCurrency.cny,
49 + FiatCurrency.czk.raw: FiatCurrency.czk,
50 + FiatCurrency.eur.raw: FiatCurrency.eur,
51 + FiatCurrency.dkk.raw: FiatCurrency.dkk,
52 + FiatCurrency.gbp.raw: FiatCurrency.gbp,
53 + FiatCurrency.hkd.raw: FiatCurrency.hkd,
54 + FiatCurrency.hrk.raw: FiatCurrency.hrk,
55 + FiatCurrency.huf.raw: FiatCurrency.huf,
56 + FiatCurrency.idr.raw: FiatCurrency.idr,
57 + FiatCurrency.ils.raw: FiatCurrency.ils,
58 + FiatCurrency.inr.raw: FiatCurrency.inr,
59 + FiatCurrency.isk.raw: FiatCurrency.isk,
60 + FiatCurrency.jpy.raw: FiatCurrency.jpy,
61 + FiatCurrency.krw.raw: FiatCurrency.krw,
62 + FiatCurrency.mxn.raw: FiatCurrency.mxn,
63 + FiatCurrency.myr.raw: FiatCurrency.myr,
64 + FiatCurrency.nok.raw: FiatCurrency.nok,
65 + FiatCurrency.nzd.raw: FiatCurrency.nzd,
66 + FiatCurrency.php.raw: FiatCurrency.php,
67 + FiatCurrency.pln.raw: FiatCurrency.pln,
68 + FiatCurrency.ron.raw: FiatCurrency.ron,
69 + FiatCurrency.rub.raw: FiatCurrency.rub,
70 + FiatCurrency.sek.raw: FiatCurrency.sek,
71 + FiatCurrency.sgd.raw: FiatCurrency.sgd,
72 + FiatCurrency.thb.raw: FiatCurrency.thb,
73 + FiatCurrency.usd.raw: FiatCurrency.usd,
74 + FiatCurrency.zar.raw: FiatCurrency.zar,
75 + FiatCurrency.vef.raw: FiatCurrency.vef
76 + };
77 +
78 + static FiatCurrency deserialize({String raw}) => _all[raw];
79 +
80 + @override
81 + bool operator ==(Object other) => other is FiatCurrency && other.raw == raw;
82 +
83 @override
84 int get hashCode => raw.hashCode ^ title.hashCode;
85 }
lib/entities/fs_migration.dart
+296 -57
@@ -1,56 +1,94 @@
1 import 'dart:io';
2 import 'dart:convert';
3 +import 'package:cake_wallet/core/key_service.dart';
4 +import 'package:cake_wallet/di.dart';
5 import 'package:cake_wallet/entities/contact.dart';
6 import 'package:cake_wallet/entities/crypto_currency.dart';
7 +import 'package:cake_wallet/entities/encrypt.dart';
8 +import 'package:cake_wallet/entities/fiat_currency.dart';
9 +import 'package:cake_wallet/entities/ios_legacy_helper.dart'
10 + as ios_legacy_helper;
11 +import 'package:cake_wallet/entities/secret_store_key.dart';
12 import 'package:cake_wallet/entities/wallet_info.dart';
13 import 'package:cake_wallet/entities/wallet_type.dart';
14 +import 'package:cake_wallet/exchange/exchange_provider_description.dart';
15 import 'package:cake_wallet/exchange/trade.dart';
16 import 'package:flutter_secure_storage/flutter_secure_storage.dart';
17 import 'package:shared_preferences/shared_preferences.dart';
18 import 'package:flutter/foundation.dart';
19 import 'package:hive/hive.dart';
20 import 'package:path_provider/path_provider.dart';
21 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
22
23 const reservedNames = ["flutter_assets", "wallets", "db"];
24
25 Future<void> migrate_android_v1() async {
26 final appDocDir = await getApplicationDocumentsDirectory();
27
19 - await migrate_hives(appDocDir: appDocDir);
20 - await migrate_wallets(appDocDir: appDocDir);
28 + await android_migrate_hives(appDocDir: appDocDir);
29 + await android_migrate_wallets(appDocDir: appDocDir);
30 }
31
23 -Future<void> migrate_ios_v1() async {
24 - final appDocDir = await getApplicationDocumentsDirectory();
32 +Future<void> ios_migrate_v1(Box<WalletInfo> walletInfoSource, Box<Trade> tradeSource, Box<Contact> contactSource) async {
33 + final prefs = await SharedPreferences.getInstance();
34 +
35 + if (prefs.getBool('ios_migration_v1_completed') ?? false) {
36 + return;
37 + }
38 +
39 + await ios_migrate_user_defaults();
40 + await ios_migrate_pin();
41 + await ios_migrate_wallet_passwords();
42 + await ios_migrate_wallet_info(walletInfoSource);
43 + await ios_migrate_trades_list(tradeSource);
44 + await ios_migrate_address_book(contactSource);
45 +
46 + await prefs.setBool('ios_migration_v1_completed', true);
47 +}
48 +
49 +Future<void> ios_migrate_user_defaults() async {
50 //get the new shared preferences instance
26 - SharedPreferences prefs = await SharedPreferences.getInstance();
51 + final prefs = await SharedPreferences.getInstance();
52 +
53 + if (prefs.getBool('ios_migration_user_defaults_completed') ?? false) {
54 + return;
55 + }
56
57 //translate the node uri
29 - String nodeURI = prefs.getString('node_uri');
30 - await prefs.setString('current_node_id', nodeURI);
58 + final nodeURI = await ios_legacy_helper.getString('node_uri');
59 + // await prefs.setString('current_node_id', nodeURI);
60 + await prefs.setInt('current_node_id', 0);
61
62 //should we provide default btc node key?
33 - int activeCurrency = prefs.getInt('currency');
34 - await prefs.setInt('current_fiat_currency', activeCurrency);
63 + final activeCurrency = await ios_legacy_helper.getInt('currency');
64 + final convertedCurrency = convertFiatLegacy(activeCurrency);
65 +
66 + if (convertedCurrency != null) {
67 + await prefs.setString(
68 + 'current_fiat_currency', convertedCurrency.serialize());
69 + }
70
71 //translate fee priority
37 - int activeFeeTier = prefs.getInt('saved_fee_priority');
72 + final activeFeeTier = await ios_legacy_helper.getInt('saved_fee_priority');
73 await prefs.setInt('current_fee_priority', activeFeeTier);
74
75 //translate current balance mode
41 - int currentBalanceMode = prefs.getInt('display_balance_mode');
76 + final currentBalanceMode =
77 + await ios_legacy_helper.getInt('display_balance_mode');
78 await prefs.setInt('current_balance_display_mode', currentBalanceMode);
79
80 //translate should save recipient address
45 - bool shouldSave = prefs.getBool('should_save_recipient_address');
81 + final shouldSave =
82 + await ios_legacy_helper.getBool('should_save_recipient_address');
83 await prefs.setBool('save_recipient_address', shouldSave);
84
85 //translate biometric
49 - bool biometricOn = prefs.getBool('biometric_authentication_on');
86 + final biometricOn =
87 + await ios_legacy_helper.getBool('biometric_authentication_on');
88 await prefs.setBool('allow_biometrical_authentication', biometricOn);
89
90 //read the current theme as integer, write it back as a bool
53 - int currentTheme = prefs.getInt('current-theme');
91 + final currentTheme = prefs.getInt('current-theme');
92 bool isDark = false;
93 if (currentTheme == 1) {
94 isDark = true;
@@ -58,15 +96,108 @@ Future<void> migrate_ios_v1() async {
96 await prefs.setBool('dark_theme', isDark);
97
98 //assign the pin lenght
61 - int pinLength = prefs.getInt('pin-length');
99 + final pinLength = await ios_legacy_helper.getInt('pin-length');
100 await prefs.setInt('pin-length', pinLength);
101
102 //default value for display list key?
65 - String walletName = prefs.getString('current_wallet_name');
103 + final walletName = await ios_legacy_helper.getString('current_wallet_name');
104 await prefs.setString('current_wallet_name', walletName);
105 +
106 + await prefs.setInt('current_wallet_type', serializeToInt(WalletType.monero));
107 +
108 + await prefs.setBool('ios_migration_user_defaults_completed', true);
109 }
110
69 -Future<void> migrate_hives({Directory appDocDir}) async {
111 +Future<void> ios_migrate_pin() async {
112 + final prefs = await SharedPreferences.getInstance();
113 +
114 + if (prefs.getBool('ios_migration_pin_completed') ?? false) {
115 + return;
116 + }
117 +
118 + final flutterSecureStorage = FlutterSecureStorage();
119 + final pinPassword = await flutterSecureStorage.read(
120 + key: 'pin_password', iOptions: IOSOptions(syncFlag: "syna"));
121 + final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
122 + final encodedPassword = encodedPinCode(pin: pinPassword);
123 + await flutterSecureStorage.write(key: key, value: encodedPassword);
124 + await prefs.setBool('ios_migration_pin_completed', true);
125 +}
126 +
127 +Future<void> ios_migrate_wallet_passwords() async {
128 + final prefs = await SharedPreferences.getInstance();
129 +
130 + if (prefs.getBool('ios_migration_wallet_passwords_completed') ?? false) {
131 + return;
132 + }
133 +
134 + final appDocDir = await getApplicationDocumentsDirectory();
135 + final flutterSecureStorage = FlutterSecureStorage();
136 + final keyService = KeyService(flutterSecureStorage);
137 + final walletsDir = Directory('${appDocDir.path}/wallets');
138 + final moneroWalletsDir = Directory('${walletsDir.path}/monero');
139 +
140 + moneroWalletsDir.listSync().forEach((item) async {
141 + try {
142 + if (item is Directory) {
143 + final name = item.path.split('/').last;
144 + final oldKey = 'wallet_monero_' + name + '_password';
145 + final password = await flutterSecureStorage.read(
146 + key: oldKey, iOptions: IOSOptions(syncFlag: "syna"));
147 + await keyService.saveWalletPassword(
148 + walletName: name, password: password);
149 + }
150 + } catch (e) {
151 + print(e.toString());
152 + }
153 + });
154 +
155 + await prefs.setBool('ios_migration_wallet_passwords_completed', true);
156 +}
157 +
158 +FiatCurrency convertFiatLegacy(int raw) {
159 + final _map = {
160 + 0: 'aud',
161 + 1: 'bgn',
162 + 2: 'brl',
163 + 3: 'cad',
164 + 4: 'chf',
165 + 5: 'cny',
166 + 6: 'czk',
167 + 7: 'eur',
168 + 8: 'dkk',
169 + 9: 'gbp',
170 + 10: 'hkd',
171 + 11: 'hrk',
172 + 12: 'huf',
173 + 13: 'idr',
174 + 14: 'ils',
175 + 15: 'inr',
176 + 16: 'isk',
177 + 17: 'jpy',
178 + 18: 'krw',
179 + 19: 'mxn',
180 + 20: 'myr',
181 + 21: 'nok',
182 + 22: 'nzd',
183 + 23: 'php',
184 + 24: 'pln',
185 + 25: 'ron',
186 + 26: 'rub',
187 + 27: 'sek',
188 + 28: 'sgd',
189 + 29: 'thb',
190 + 30: 'try',
191 + 31: 'usd',
192 + 32: 'zar',
193 + 33: 'vef'
194 + };
195 + final fiatAsString = _map[raw];
196 +
197 + return FiatCurrency.deserialize(raw: fiatAsString.toUpperCase());
198 +}
199 +
200 +Future<void> android_migrate_hives({Directory appDocDir}) async {
201 final dbDir = Directory('${appDocDir.path}/db');
202 final files = List<File>();
203
@@ -89,7 +220,7 @@ Future<void> migrate_hives({Directory appDocDir}) async {
220 });
221 }
222
92 -Future<void> migrate_wallets({Directory appDocDir}) async {
223 +Future<void> android_migrate_wallets({Directory appDocDir}) async {
224 final walletsDir = Directory('${appDocDir.path}/wallets');
225 final moneroWalletsDir = Directory('${walletsDir.path}/monero');
226 final dirs = List<Directory>();
@@ -123,54 +254,162 @@ Future<void> migrate_wallets({Directory appDocDir}) async {
254 });
255 }
256
126 -Future<void> migrate_ios_wallet_info(
127 - {@required Directory appDocDir,
128 - @required Box<WalletInfo> walletsInfo}) async {
129 - // final walletsDir = Directory('${appDocDir.path}/wallets');
130 - // final moneroWalletsDir = Directory('${walletsDir.path}/monero');
131 -
132 - // moneroWalletsDir.listSync().forEach((item) async {
133 - // try {
134 - // if (item is Directory) {
135 - // final name = item.path.split('/').last;
136 - // final configFile = File('${item.path}/$name.json');
137 - // final config =
138 - // json.decode(configFile.readAsStringSync()) as Map<String, dynamic>;
139 - // final isRecovery = config["isRecovery"] as bool ?? false;
140 - // final id =
141 - // walletTypeToString(WalletType.monero).toLowerCase() + '_' + name;
142 - // final walletInfo =
143 - // WalletInfo(id: id, name: name, isRecovery: isRecovery);
144 -
145 - // await walletsInfo.add(walletInfo);
146 - // }
147 - // } catch (e) {
148 - // print(e.toString());
149 - // }
150 - // });
257 +Future<void> ios_migrate_wallet_info(Box<WalletInfo> walletsInfoSource) async {
258 + final prefs = await SharedPreferences.getInstance();
259 +
260 + if (prefs.getBool('ios_migration_wallet_info_completed') ?? false) {
261 + return;
262 + }
263 +
264 + try {
265 + final appDocDir = await getApplicationDocumentsDirectory();
266 + final walletsDir = Directory('${appDocDir.path}/wallets');
267 + final moneroWalletsDir = Directory('${walletsDir.path}/monero');
268 + final infoRecords = moneroWalletsDir
269 + .listSync()
270 + .map((item) {
271 + try {
272 + if (item is Directory) {
273 + final name = item.path.split('/').last;
274 + final configFile = File('${item.path}/$name.json');
275 +
276 + if (!configFile.existsSync()) {
277 + return null;
278 + }
279 +
280 + final config = json.decode(configFile.readAsStringSync())
281 + as Map<String, dynamic>;
282 + final isRecovery = config['isRecovery'] as bool ?? false;
283 + final dateAsDouble = config['date'] as double;
284 + final timestamp = dateAsDouble.toInt() * 1000;
285 + final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
286 + final id = walletTypeToString(WalletType.monero).toLowerCase() +
287 + '_' +
288 + name;
289 + final exist = walletsInfoSource.values
290 + .firstWhere((el) => el.id == id, orElse: () => null) !=
291 + null;
292 +
293 + if (exist) {
294 + return null;
295 + }
296 +
297 + final walletInfo = WalletInfo.external(
298 + id: id,
299 + type: WalletType.monero,
300 + name: name,
301 + isRecovery: isRecovery,
302 + restoreHeight: 0,
303 + date: date,
304 + dirPath: item.path,
305 + path: '${item.path}/$name');
306 +
307 + return walletInfo;
308 + }
309 + } catch (e) {
310 + print(e.toString());
311 + return null;
312 + }
313 + })
314 + .where((el) => el != null)
315 + .toList();
316 + print(infoRecords);
317 + await walletsInfoSource.addAll(infoRecords);
318 + await prefs.setBool('ios_migration_wallet_info_completed', true);
319 + } catch (e) {
320 + print(e.toString());
321 + }
322 }
323
153 -Future<void> migrate_ios_trades_list(
154 - {@required Directory appDocDir, @required Box<Trade> trades}) async {
155 - final adderessBookJSON = File('${appDocDir.path}/trades_list.json');
156 - final List<dynamic> trades =
157 - json.decode(adderessBookJSON.readAsStringSync()) as List<dynamic>;
324 +Future<void> ios_migrate_trades_list(Box<Trade> tradeSource) async {
325 + final prefs = await SharedPreferences.getInstance();
326 +
327 + if (prefs.getBool('ios_migration_trade_list_completed') ?? false) {
328 + return;
329 + }
330 +
331 + try {
332 + final appDocDir = await getApplicationDocumentsDirectory();
333 + final url = '${appDocDir.path}/trades_list.json';
334 + final file = File(url);
335 +
336 + if (!file.existsSync()) {
337 + await prefs.setBool('ios_migration_trade_list_completed', true);
338 + return;
339 + }
340 +
341 + final content = file.readAsBytesSync();
342 + final flutterSecureStorage = FlutterSecureStorage();
343 + final masterPassword = await flutterSecureStorage.read(
344 + key: 'master_password', iOptions: IOSOptions(syncFlag: "syna"));
345 + final key = masterPassword.replaceAll('-', '');
346 + final decoded = await ios_legacy_helper.decrypt(content,
347 + key: key, salt: secrets.keychainSalt);
348 + final decodedJson = json.decode(decoded) as List<dynamic>;
349 + final trades = decodedJson.map((dynamic el) {
350 + final elAsMap = el as Map<String, dynamic>;
351 + final providerAsString = elAsMap['provider'] as String;
352 + final fromAsString = elAsMap['from'] as String;
353 + final toAsString = elAsMap['to'] as String;
354 + final dateAsDouble = elAsMap['date'] as double;
355 + final tradeId = elAsMap['tradeID'] as String;
356 + final to = CryptoCurrency.fromString(toAsString);
357 + final from = CryptoCurrency.fromString(fromAsString);
358 + final timestamp = dateAsDouble.toInt() * 1000;
359 + final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
360 + ExchangeProviderDescription provider;
361 +
362 + switch (providerAsString.toLowerCase()) {
363 + case 'changenow':
364 + provider = ExchangeProviderDescription.changeNow;
365 + break;
366 + case 'xmr.to':
367 + provider = ExchangeProviderDescription.xmrto;
368 + break;
369 + case 'morph':
370 + provider = ExchangeProviderDescription.morphToken;
371 + break;
372 + default:
373 + break;
374 + }
375 +
376 + return Trade(
377 + id: tradeId, provider: provider, from: from, to: to, createdAt: date);
378 + });
379 + await tradeSource.addAll(trades);
380 + await prefs.setBool('ios_migration_trade_list_completed', true);
381 + } catch (e) {
382 + print(e.toString());
383 + }
384 }
385
160 -Future<void> migrate_ios_address_book(
161 - {@required Directory appDocDir, @required Box<Contact> contacts}) async {
162 - final adderessBookJSON = File('${appDocDir.path}/address_book.json');
163 - final List<dynamic> addresses =
164 - json.decode(adderessBookJSON.readAsStringSync()) as List<dynamic>;
386 +Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
387 + final prefs = await SharedPreferences.getInstance();
388 +
389 + if (prefs.getBool('ios_migration_address_book_completed') ?? false) {
390 + return;
391 + }
392 +
393 + final appDocDir = await getApplicationDocumentsDirectory();
394 + final addressBookJSON = File('${appDocDir.path}/address_book.json');
395 +
396 + if (!addressBookJSON.existsSync()) {
397 + await prefs.setBool('ios_migration_address_book_completed', true);
398 + return;
399 + }
400
166 - addresses.forEach((dynamic item) async {
401 + final List<dynamic> addresses =
402 + json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>;
403 + final contacts = addresses.map((dynamic item) {
404 final _item = item as Map<String, dynamic>;
405 final type = _item["type"] as String;
406 final address = _item["address"] as String;
407 final name = _item["name"] as String;
171 - final contact = Contact(
172 - address: address, name: name, type: CryptoCurrency.fromString(type));
408
174 - await contacts.add(contact);
409 + return Contact(
410 + address: address, name: name, type: CryptoCurrency.fromString(type));
411 });
412 +
413 + await contactSource.addAll(contacts);
414 + await prefs.setBool('ios_migration_address_book_completed', true);
415 }
lib/entities/ios_legacy_helper.dart
+16 -6
@@ -1,15 +1,25 @@
1 import 'dart:async';
2 +import 'dart:typed_data';
3 import 'package:flutter/foundation.dart';
4 import 'package:flutter/services.dart';
5
6 const platform =
7 const MethodChannel('com.cakewallet.cakewallet/legacy_wallet_migration');
8
8 -Future<String> readTradeList(
9 +Future<String> decrypt(Uint8List bytes,
10 {@required String key, @required String salt}) async =>
10 - await platform.invokeMethod('read_trade_list', {'key': key, 'salt': salt});
11 + await platform
12 + .invokeMethod('decrypt', {'bytes': bytes, 'key': key, 'salt': salt});
13
12 -Future<String> readEncryptedFile(String url,
13 - {@required String key, @required String salt}) async =>
14 - await platform.invokeMethod(
15 - 'read_encrypted_file', {'url': url, 'key': key, 'salt': salt});
14 +Future<dynamic> readUserDefaults(String key, {@required String type}) async =>
15 + await platform
16 + .invokeMethod<dynamic>('read_user_defaults', {'key': key, 'type': type});
17 +
18 +Future<String> getString(String key) async =>
19 + await readUserDefaults(key, type: 'string') as String;
20 +
21 +Future<bool> getBool(String key) async =>
22 + await readUserDefaults(key, type: 'bool') as bool;
23 +
24 +Future<int> getInt(String key) async =>
25 + await readUserDefaults(key, type: 'int') as int;
lib/main.dart
+17 -9
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/entities/fs_migration.dart';
2 import 'package:cake_wallet/entities/transaction_description.dart';
3 import 'package:cake_wallet/entities/transaction_description.dart';
4 import 'package:cake_wallet/reactions/bootstrap.dart';
@@ -94,7 +95,7 @@ void main() async {
95 final exchangeTemplates =
96 await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
97
97 - final sharedPreferences = await SharedPreferences.getInstance();
98 + // final sharedPreferences = await SharedPreferences.getInstance();
99 // final walletService = WalletService();
100 // final fiatConvertationService = FiatConvertationService();
101 // final walletListService = WalletListService(
@@ -138,7 +139,6 @@ void main() async {
139 templates: templates,
140 exchangeTemplates: exchangeTemplates,
141 initialMigrationVersion: 4);
141 -
142 // setReactions(
143 // settingsStore: settingsStore,
144 // priceStore: priceStore,
@@ -147,8 +147,8 @@ void main() async {
147 // walletService: walletService,
148 // // authenticationStore: authenticationStore,
149 // loginStore: loginStore);
150 -
151 - runApp(CakeWalletApp());
150 + final initialLanguage = await Language.localeDetection();
151 + runApp(CakeWalletApp(initialLanguage));
152 }
153
154 Future<void> initialSetup(
@@ -160,10 +160,13 @@ Future<void> initialSetup(
160 // @required FiatConvertationService fiatConvertationService,
161 @required Box<Template> templates,
162 @required Box<ExchangeTemplate> exchangeTemplates,
163 - int initialMigrationVersion = 4}) async {
163 + int initialMigrationVersion = 5}) async {
164 await defaultSettingsMigration(
165 version: initialMigrationVersion,
166 sharedPreferences: sharedPreferences,
167 + walletInfoSource: walletInfoSource,
168 + contactSource: contactSource,
169 + tradeSource: tradesSource,
170 nodes: nodes);
171 await setup(
172 walletInfoSource: walletInfoSource,
@@ -177,11 +180,13 @@ Future<void> initialSetup(
180 }
181
182 class CakeWalletApp extends StatelessWidget {
180 - CakeWalletApp() {
183 + CakeWalletApp(this.initialLanguage) {
184 SystemChrome.setPreferredOrientations(
185 [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
186 }
187
188 + final String initialLanguage;
189 +
190 @override
191 Widget build(BuildContext context) {
192 //final settingsStore = Provider.of<SettingsStore>(context);
@@ -192,11 +197,14 @@ class CakeWalletApp extends StatelessWidget {
197 settingsStore.isDarkTheme ? Themes.darkTheme : Themes.lightTheme),
198 child: ChangeNotifierProvider<Language>(
199 create: (_) => Language(settingsStore.languageCode),
195 - child: MaterialAppWithTheme()));
200 + child: MaterialAppWithTheme(initialLanguage)));
201 }
202 }
203
204 class MaterialAppWithTheme extends StatelessWidget {
205 + MaterialAppWithTheme(this.initialLanguage);
206 + final String initialLanguage;
207 +
208 @override
209 Widget build(BuildContext context) {
210 // final sharedPreferences = Provider.of<SharedPreferences>(context);
@@ -209,7 +217,7 @@ class MaterialAppWithTheme extends StatelessWidget {
217 // final syncStore = Provider.of<SyncStore>(context);
218 // final balanceStore = Provider.of<BalanceStore>(context);
219 final theme = Provider.of<ThemeChanger>(context);
212 - // final currentLanguage = Provider.of<Language>(context);
220 + final currentLanguage = Provider.of<Language>(context);
221 // final contacts = Provider.of<Box<Contact>>(context);
222 // final nodes = Provider.of<Box<Node>>(context);
223 // final trades = Provider.of<Box<Trade>>(context);
@@ -253,7 +261,7 @@ class MaterialAppWithTheme extends StatelessWidget {
261 GlobalWidgetsLocalizations.delegate,
262 ],
263 supportedLocales: S.delegate.supportedLocales,
256 - // locale: Locale(currentLanguage.getCurrentLanguage()),
264 + locale: Locale(currentLanguage.getCurrentLanguage()),
265 onGenerateRoute: (settings) => Router.generateRoute(settings),
266 initialRoute: initialRoute,
267 ));
lib/monero/monero_wallet_service.dart
+1 -4
@@ -90,12 +90,9 @@ class MoneroWalletService extends WalletService<
90 final path = await pathForWallet(name: name, type: WalletType.monero);
91 final file = File(path);
92 final stat = await file.stat();
93 - print(stat.changed);
94 - print(stat.modified);
95 - print(stat.accessed);
93 monero_wallet_manager.openWallet(path: path, password: password);
94 final walletInfo = walletInfoSource.values.firstWhere(
98 - (info) => info.id == WalletBase.idFor(name, WalletType.monero));
95 + (info) => info.id == WalletBase.idFor(name, WalletType.monero), orElse: () => null);
96 final wallet = MoneroWallet(
97 filename: monero_wallet.getFilename(), walletInfo: walletInfo);
98 await wallet.init();
lib/reactions/bootstrap.dart
+4 -4
@@ -19,10 +19,10 @@ Future<void> bootstrap(GlobalKey<NavigatorState> navigatorKey) async {
19 final fiatConversionStore = getIt.get<FiatConversionStore>();
20
21 if (authenticationStore.state == AuthenticationState.uninitialized) {
22 - authenticationStore.state = getIt
23 - .get<SharedPreferences>()
24 - .getString(PreferencesKey.currentWalletName) ==
25 - null
22 + final currentWalletName = getIt
23 + .get<SharedPreferences>()
24 + .getString(PreferencesKey.currentWalletName);
25 + authenticationStore.state = currentWalletName == null
26 ? AuthenticationState.denied
27 : AuthenticationState.installed;
28 }
pubspec.lock
+19 -10
@@ -399,10 +399,12 @@ packages:
399 flutter_secure_storage:
400 dependency: "direct main"
401 description:
402 - name: flutter_secure_storage
403 - url: "https://pub.dartlang.org"
404 - source: hosted
405 - version: "3.3.4"
402 + path: "."
403 + ref: cake
404 + resolved-ref: a734c2ea3239f9153dba6f5bec740e1df54ee754
405 + url: "https://github.com/cake-tech/flutter_secure_storage.git"
406 + source: git
407 + version: "3.3.55"
408 flutter_slidable:
409 dependency: "direct main"
410 description:
@@ -643,7 +645,7 @@ packages:
645 name: path_provider
646 url: "https://pub.dartlang.org"
647 source: hosted
646 - version: "1.6.16"
648 + version: "1.6.17"
649 path_provider_linux:
650 dependency: transitive
651 description:
@@ -671,7 +673,7 @@ packages:
673 name: path_provider_windows
674 url: "https://pub.dartlang.org"
675 source: hosted
674 - version: "0.0.3"
676 + version: "0.0.4+1"
677 pedantic:
678 dependency: "direct dev"
679 description:
@@ -790,7 +792,7 @@ packages:
792 name: shared_preferences
793 url: "https://pub.dartlang.org"
794 source: hosted
793 - version: "0.5.10"
795 + version: "0.5.11"
796 shared_preferences_linux:
797 dependency: transitive
798 description:
@@ -819,6 +821,13 @@ packages:
821 url: "https://pub.dartlang.org"
822 source: hosted
823 version: "0.1.2+7"
824 + shared_preferences_windows:
825 + dependency: transitive
826 + description:
827 + name: shared_preferences_windows
828 + url: "https://pub.dartlang.org"
829 + source: hosted
830 + version: "0.0.1+1"
831 shelf:
832 dependency: transitive
833 description:
@@ -851,7 +860,7 @@ packages:
860 name: source_gen
861 url: "https://pub.dartlang.org"
862 source: hosted
854 - version: "0.9.6"
863 + version: "0.9.7+1"
864 source_span:
865 dependency: transitive
866 description:
@@ -928,7 +937,7 @@ packages:
937 name: url_launcher
938 url: "https://pub.dartlang.org"
939 source: hosted
931 - version: "5.6.0"
940 + version: "5.7.0"
941 url_launcher_linux:
942 dependency: transitive
943 description:
@@ -1022,4 +1031,4 @@ packages:
1031 version: "2.2.1"
1032 sdks:
1033 dart: ">=2.9.0-14.0.dev <3.0.0"
1025 - flutter: ">=1.20.0 <2.0.0"
1034 + flutter: ">=1.12.13+hotfix.5 <2.0.0"
pubspec.yaml
+4 -1
@@ -27,7 +27,10 @@ dependencies:
27 qr: ^1.2.0
28 uuid: 2.0.1
29 shared_preferences: ^0.5.3+4
30 - flutter_secure_storage: ^3.2.1+1
30 + flutter_secure_storage:
31 + git:
32 + url: https://github.com/cake-tech/flutter_secure_storage.git
33 + ref: cake
34 provider: ^3.1.0
35 rxdart: ^0.22.2
36 yaml: ^2.1.16
tool/.secrets-test.json
+1
@@ -1,5 +1,6 @@
1 {
2 "salt": "",
3 + "keychainSalt": "",
4 "key": "",
5 "walletSalt": "",
6 "shortKey": "",
tool/secrets.dart
+1 -1
@@ -14,7 +14,7 @@ Future<void> main() async {
14 final inoutContent = File(inputPath).readAsStringSync();
15 final config = json.decode(inoutContent) as Map<String, dynamic>;
16 final output =
17 - 'const salt = \'${config["salt"]}\';\nconst key = \'${config["key"]}\';\nconst walletSalt = \'${config["walletSalt"]}\';\nconst shortKey = \'${config["shortKey"]}\';\nconst change_now_api_key = \'${config["change_now_api_key"]}\';';
17 + 'const salt = \'${config["salt"]}\';const keychainSalt = \'${config["keychainSalt"]}\';\nconst key = \'${config["key"]}\';\nconst walletSalt = \'${config["walletSalt"]}\';\nconst shortKey = \'${config["shortKey"]}\';\nconst change_now_api_key = \'${config["change_now_api_key"]}\';';
18
19 await File(outputPath).writeAsString(output);
20 }