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
}