Backup stuff.

This commit is contained in:
M 2021-01-15 19:41:30 +02:00
parent 9a79fcdc23
commit 47ceac2dd6
24 changed files with 459 additions and 253 deletions

View file

@ -14,128 +14,58 @@ import 'package:cake_wallet/entities/encrypt.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
class BackupService {
BackupService(this._flutterSecureStorage, this._authService,
this._walletInfoSource, this._keyService, this._sharedPreferences)
BackupService(this._flutterSecureStorage, this._walletInfoSource,
this._keyService, this._sharedPreferences)
: _cipher = chacha20Poly1305Aead;
static const currentVersion = _v1;
static const _v1 = 1;
final Cipher _cipher;
final FlutterSecureStorage _flutterSecureStorage;
final SharedPreferences _sharedPreferences;
final AuthService _authService;
final Box<WalletInfo> _walletInfoSource;
final KeyService _keyService;
Future<void> importBackup(Uint8List data, String password,
{@required String nonce}) async {
final appDir = await getApplicationDocumentsDirectory();
final decryptedData = await _decrypt(data, password, nonce);
final zip = ZipDecoder().decodeBytes(decryptedData);
{String nonce = secrets.backupSalt}) async {
final version = getVersion(data);
final backupBytes = data.toList()..removeAt(0);
final backupData = Uint8List.fromList(backupBytes);
zip.files.forEach((file) {
final filename = file.name;
if (file.isFile) {
final data = file.content as List<int>;
File('${appDir.path}/' + filename)
..createSync(recursive: true)
..writeAsBytesSync(data);
} else {
Directory('${appDir.path}/' + filename)..create(recursive: true);
}
print(filename);
});
await importKeychainDump(password, nonce: nonce);
await importPreferencesDump();
}
Future<void> importPreferencesDump() async {
final appDir = await getApplicationDocumentsDirectory();
final preferencesFile = File('${appDir.path}/~_preferences_dump');
if (!preferencesFile.existsSync()) {
return;
switch (version) {
case _v1:
await _importBackupV1(backupData, password, nonce: nonce);
break;
default:
break;
}
final data =
json.decode(preferencesFile.readAsStringSync()) as Map<String, Object>;
print('data $data');
await _sharedPreferences.setString(PreferencesKey.currentWalletName,
data[PreferencesKey.currentWalletName] as String);
await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey,
data[PreferencesKey.currentNodeIdKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey,
data[PreferencesKey.currentBalanceDisplayModeKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentWalletType,
data[PreferencesKey.currentWalletType] as int);
await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey,
data[PreferencesKey.currentFiatCurrencyKey] as String);
await _sharedPreferences.setBool(
PreferencesKey.shouldSaveRecipientAddressKey,
data[PreferencesKey.shouldSaveRecipientAddressKey] as bool);
await _sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKey,
data[PreferencesKey.currentTransactionPriorityKey] as int);
await _sharedPreferences.setBool(
PreferencesKey.allowBiometricalAuthenticationKey,
data[PreferencesKey.allowBiometricalAuthenticationKey] as bool);
await _sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentLanguageCode,
data[PreferencesKey.currentLanguageCode] as int);
await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey,
data[PreferencesKey.displayActionListModeKey] as int);
await _sharedPreferences.setInt(
'current_theme', data['current_theme'] as int);
await preferencesFile.delete();
}
Future<void> importKeychainDump(String password,
{@required String nonce}) async {
final appDir = await getApplicationDocumentsDirectory();
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
final decryptedKeychainDumpFileData =
await _decrypt(keychainDumpFile.readAsBytesSync(), password, nonce);
final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
as Map<String, dynamic>;
final keychainWalletsInfo = keychainJSON['wallets'] as List;
final decodedPin = keychainJSON['pin'] as String;
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async {
final info = rawInfo as Map<String, dynamic>;
await importWalletKeychainInfo(info);
});
await _flutterSecureStorage.write(
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync();
}
Future<void> importWalletKeychainInfo(Map<String, dynamic> info) async {
final name = info['name'] as String;
final password = info['password'] as String;
await _keyService.saveWalletPassword(walletName: name, password: password);
}
Future<Uint8List> exportBackup(String password,
{@required String nonce}) async {
{String nonce = secrets.backupSalt, int version = currentVersion}) async {
switch (version) {
case _v1:
return await _exportBackupV1(password, nonce: nonce);
default:
return null;
}
}
Future<Uint8List> _exportBackupV1(String password,
{String nonce = secrets.backupSalt}) async {
final zipEncoder = ZipFileEncoder();
final appDir = await getApplicationDocumentsDirectory();
final now = DateTime.now();
final tmpDir = Directory('${appDir.path}/~_BACKUP_TMP');
final archivePath = '${tmpDir.path}/backup_${now.toString()}.zip';
final fileEntities = appDir.listSync(recursive: false);
final keychainDump = await exportKeychainDump(password, nonce: nonce);
final preferencesDump = await exportPreferencesJSON();
final keychainDump = await _exportKeychainDump(password, nonce: nonce);
final preferencesDump = await _exportPreferencesJSON();
final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
@ -165,12 +95,120 @@ class BackupService {
final content = File(archivePath).readAsBytesSync();
tmpDir.deleteSync(recursive: true);
final encryptedData = await _encrypt(content, password, nonce);
return await _encrypt(content, password, nonce);
return setVersion(encryptedData, currentVersion);
}
Future<Uint8List> exportKeychainDump(String password,
Future<void> _importBackupV1(Uint8List data, String password,
{@required String nonce}) async {
final appDir = await getApplicationDocumentsDirectory();
final decryptedData = await _decrypt(data, password, nonce);
final zip = ZipDecoder().decodeBytes(decryptedData);
zip.files.forEach((file) {
final filename = file.name;
if (file.isFile) {
final content = file.content as List<int>;
File('${appDir.path}/' + filename)
..createSync(recursive: true)
..writeAsBytesSync(content);
} else {
Directory('${appDir.path}/' + filename)..create(recursive: true);
}
});
await _importKeychainDump(password, nonce: nonce);
await _importPreferencesDump();
}
Future<void> _importPreferencesDump() async {
final appDir = await getApplicationDocumentsDirectory();
final preferencesFile = File('${appDir.path}/~_preferences_dump');
const defaultSettingsMigrationVersionKey = PreferencesKey.currentDefaultSettingsMigrationVersion;
if (!preferencesFile.existsSync()) {
return;
}
final data =
json.decode(preferencesFile.readAsStringSync()) as Map<String, Object>;
await _sharedPreferences.setString(PreferencesKey.currentWalletName,
data[PreferencesKey.currentWalletName] as String);
await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey,
data[PreferencesKey.currentNodeIdKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey,
data[PreferencesKey.currentBalanceDisplayModeKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentWalletType,
data[PreferencesKey.currentWalletType] as int);
await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey,
data[PreferencesKey.currentFiatCurrencyKey] as String);
await _sharedPreferences.setBool(
PreferencesKey.shouldSaveRecipientAddressKey,
data[PreferencesKey.shouldSaveRecipientAddressKey] as bool);
await _sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKey,
data[PreferencesKey.currentTransactionPriorityKey] as int);
await _sharedPreferences.setBool(
PreferencesKey.allowBiometricalAuthenticationKey,
data[PreferencesKey.allowBiometricalAuthenticationKey] as bool);
await _sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentLanguageCode,
data[PreferencesKey.currentLanguageCode] as int);
await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey,
data[PreferencesKey.displayActionListModeKey] as int);
await _sharedPreferences.setInt(
'current_theme', data['current_theme'] as int);
await _sharedPreferences.setInt(defaultSettingsMigrationVersionKey,
data[defaultSettingsMigrationVersionKey] as int);
await preferencesFile.delete();
}
Future<void> _importKeychainDump(String password,
{@required String nonce,
String keychainSalt = secrets.backupKeychainSalt}) async {
final appDir = await getApplicationDocumentsDirectory();
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
final decryptedKeychainDumpFileData = await _decrypt(
keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce);
final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
as Map<String, dynamic>;
final keychainWalletsInfo = keychainJSON['wallets'] as List;
final decodedPin = keychainJSON['pin'] as String;
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final backupPasswordKey =
generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = keychainJSON[backupPasswordKey] as String;
await _flutterSecureStorage.write(
key: backupPasswordKey, value: backupPassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async {
final info = rawInfo as Map<String, dynamic>;
await importWalletKeychainInfo(info);
});
await _flutterSecureStorage.write(
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync();
}
Future<void> importWalletKeychainInfo(Map<String, dynamic> info) async {
final name = info['name'] as String;
final password = info['password'] as String;
await _keyService.saveWalletPassword(walletName: name, password: password);
}
Future<Uint8List> _exportKeychainDump(String password,
{@required String nonce,
String keychainSalt = secrets.backupKeychainSalt}) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPin = await _flutterSecureStorage.read(key: key);
final decodedPin = decodedPinCode(pin: encodedPin);
@ -183,15 +221,25 @@ class BackupService {
await _keyService.getWalletPassword(walletName: walletInfo.name)
};
}));
final data =
utf8.encode(json.encode({'pin': decodedPin, 'wallets': wallets}));
final encrypted = await _encrypt(Uint8List.fromList(data), password, nonce);
final backupPasswordKey =
generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword =
await _flutterSecureStorage.read(key: backupPasswordKey);
final data = utf8.encode(json.encode({
'pin': decodedPin,
'wallets': wallets,
backupPasswordKey: backupPassword
}));
final encrypted = await _encrypt(
Uint8List.fromList(data), '$keychainSalt$password', nonce);
return encrypted;
}
Future<String> exportPreferencesJSON() async {
Future<String> _exportPreferencesJSON() async {
const defaultSettingsMigrationVersionKey =
'current_default_settings_migration_version';
final preferences = <String, Object>{
PreferencesKey.currentWalletName:
_sharedPreferences.getString(PreferencesKey.currentWalletName),
@ -219,13 +267,22 @@ class BackupService {
_sharedPreferences.getString(PreferencesKey.currentLanguageCode),
PreferencesKey.displayActionListModeKey:
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
PreferencesKey.currentTheme: _sharedPreferences.getInt(PreferencesKey.currentTheme)
// FIX-ME: Unnamed constant.
PreferencesKey.currentTheme:
_sharedPreferences.getInt(PreferencesKey.currentTheme),
defaultSettingsMigrationVersionKey:
_sharedPreferences.getInt(defaultSettingsMigrationVersionKey)
};
return json.encode(preferences);
}
int getVersion(Uint8List data) => data.toList().first;
Uint8List setVersion(Uint8List data, int version) {
final bytes = data.toList()..insert(0, version);
return Uint8List.fromList(bytes);
}
Future<Uint8List> _encrypt(
Uint8List data, String secretKeySource, String nonceBase64) async {
final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource));

View file

@ -1,5 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/core/backup.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/entities/biometric_auth.dart';
import 'package:cake_wallet/entities/contact_record.dart';
@ -105,6 +105,15 @@ import 'package:cake_wallet/exchange/exchange_template.dart';
final getIt = GetIt.instance;
var _isSetupFinished = false;
Box<WalletInfo> _walletInfoSource;
Box<Node> _nodeSource;
Box<Contact> _contactSource;
Box<Trade> _tradesSource;
Box<Template> _templates;
Box<ExchangeTemplate> _exchangeTemplates;
Box<TransactionDescription> _transactionDescriptionBox;
Future setup(
{Box<WalletInfo> walletInfoSource,
Box<Node> nodeSource,
@ -113,12 +122,26 @@ Future setup(
Box<Template> templates,
Box<ExchangeTemplate> exchangeTemplates,
Box<TransactionDescription> transactionDescriptionBox}) async {
getIt.registerSingletonAsync<SharedPreferences>(
() => SharedPreferences.getInstance());
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
_contactSource = contactSource;
_tradesSource = tradesSource;
_templates = templates;
_exchangeTemplates = exchangeTemplates;
_transactionDescriptionBox = transactionDescriptionBox;
final settingsStore = await SettingsStoreBase.load(nodeSource: nodeSource);
if (!_isSetupFinished) {
getIt.registerSingletonAsync<SharedPreferences>(
() => SharedPreferences.getInstance());
}
getIt.registerSingleton<Box<Node>>(nodeSource);
final settingsStore = await SettingsStoreBase.load(nodeSource: _nodeSource);
if (_isSetupFinished) {
return;
}
getIt.registerFactory<Box<Node>>(() => _nodeSource);
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
getIt.registerSingleton(AuthenticationStore());
@ -131,14 +154,14 @@ Future setup(
settingsStore: getIt.get<SettingsStore>(),
nodeListStore: getIt.get<NodeListStore>()));
getIt.registerSingleton<TradesStore>(TradesStore(
tradesSource: tradesSource, settingsStore: getIt.get<SettingsStore>()));
tradesSource: _tradesSource, settingsStore: getIt.get<SettingsStore>()));
getIt.registerSingleton<TradeFilterStore>(TradeFilterStore());
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore());
getIt.registerSingleton<FiatConversionStore>(FiatConversionStore());
getIt.registerSingleton<SendTemplateStore>(
SendTemplateStore(templateSource: templates));
SendTemplateStore(templateSource: _templates));
getIt.registerSingleton<ExchangeTemplateStore>(
ExchangeTemplateStore(templateSource: exchangeTemplates));
ExchangeTemplateStore(templateSource: _exchangeTemplates));
final secretStore =
await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
@ -157,7 +180,7 @@ Future setup(
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) =>
WalletNewVM(getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type));
getIt
@ -167,7 +190,7 @@ Future setup(
final mnemonic = args[2] as String;
return WalletRestorationFromSeedVM(getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type, language: language, seed: mnemonic);
});
@ -177,7 +200,7 @@ Future setup(
final language = args[1] as String;
return WalletRestorationFromKeysVM(getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type, language: language);
});
@ -262,7 +285,7 @@ Future setup(
getIt.get<AppStore>().settingsStore,
getIt.get<SendTemplateStore>(),
getIt.get<FiatConversionStore>(),
transactionDescriptionBox));
_transactionDescriptionBox));
getIt.registerFactory(
() => SendPage(sendViewModel: getIt.get<SendViewModel>()));
@ -271,7 +294,7 @@ Future setup(
() => SendTemplatePage(sendViewModel: getIt.get<SendViewModel>()));
getIt.registerFactory(() => WalletListViewModel(
walletInfoSource,
_walletInfoSource,
getIt.get<AppStore>(),
getIt.get<KeyService>(),
getIt.get<WalletNewVM>(param1: WalletType.monero)));
@ -342,10 +365,10 @@ Future setup(
getIt.registerFactoryParam<ContactViewModel, ContactRecord, void>(
(ContactRecord contact, _) =>
ContactViewModel(contactSource, contact: contact));
ContactViewModel(_contactSource, contact: contact));
getIt.registerFactory(
() => ContactListViewModel(contactSource, walletInfoSource));
() => ContactListViewModel(_contactSource, _walletInfoSource));
getIt.registerFactoryParam<ContactListPage, bool, void>(
(bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(),
@ -358,27 +381,27 @@ Future setup(
getIt.registerFactory(() {
final appStore = getIt.get<AppStore>();
return NodeListViewModel(
nodeSource, appStore.wallet, appStore.settingsStore);
_nodeSource, appStore.wallet, appStore.settingsStore);
});
getIt.registerFactory(() => NodeListPage(getIt.get<NodeListViewModel>()));
getIt.registerFactory(() =>
NodeCreateOrEditViewModel(nodeSource, getIt.get<AppStore>().wallet));
NodeCreateOrEditViewModel(_nodeSource, getIt.get<AppStore>().wallet));
getIt.registerFactory(
() => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
getIt.registerFactory(() => ExchangeViewModel(
getIt.get<AppStore>().wallet,
tradesSource,
_tradesSource,
getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>(),
getIt.get<AppStore>().settingsStore));
getIt.registerFactory(() => ExchangeTradeViewModel(
wallet: getIt.get<AppStore>().wallet,
trades: tradesSource,
trades: _tradesSource,
tradesStore: getIt.get<TradesStore>(),
sendViewModel: getIt.get<SendViewModel>()));
@ -393,9 +416,9 @@ Future setup(
getIt.registerFactory(
() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
getIt.registerFactory(() => MoneroWalletService(walletInfoSource));
getIt.registerFactory(() => MoneroWalletService(_walletInfoSource));
getIt.registerFactory(() => BitcoinWalletService(walletInfoSource));
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
getIt.registerFactoryParam<WalletService, WalletType, void>(
(WalletType param1, __) {
@ -428,7 +451,7 @@ Future setup(
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, void>(
(type, _) => WalletRestoreViewModel(getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type));
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
@ -438,7 +461,7 @@ Future setup(
.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
transactionInfo: transactionInfo,
transactionDescriptionBox: transactionDescriptionBox,
transactionDescriptionBox: _transactionDescriptionBox,
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
@ -455,12 +478,11 @@ Future setup(
(WalletType type, _) => PreSeedPage(type));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
TradeDetailsViewModel(tradeForDetails: trade, trades: tradesSource));
TradeDetailsViewModel(tradeForDetails: trade, trades: _tradesSource));
getIt.registerFactory(() => BackupService(
getIt.get<FlutterSecureStorage>(),
getIt.get<AuthService>(),
walletInfoSource,
_walletInfoSource,
getIt.get<KeyService>(),
getIt.get<SharedPreferences>()));
@ -476,8 +498,7 @@ Future setup(
getIt.registerFactory(
() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
getIt.registerFactoryParam<RestoreOptionsPage, WalletType, void>(
(WalletType type, _) => RestoreOptionsPage(type: type));
getIt.registerFactory(() => RestoreOptionsPage());
getIt.registerFactory(
() => RestoreFromBackupViewModel(getIt.get<BackupService>()));
@ -487,4 +508,6 @@ Future setup(
getIt.registerFactoryParam<TradeDetailsPage, Trade, void>((Trade trade, _) =>
TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
_isSetupFinished = true;
}

View file

@ -5,11 +5,12 @@ import 'package:cake_wallet/utils/mobx.dart';
part 'contact.g.dart';
@HiveType(typeId: 0)
@HiveType(typeId: Contact.typeId)
class Contact extends HiveObject with Keyable {
Contact({@required this.name, @required this.address, CryptoCurrency type})
: raw = type?.raw;
static const typeId = 0;
static const boxName = 'Contacts';
@HiveField(0)

View file

@ -1,9 +1,12 @@
import 'dart:io' show File, Platform;
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/pathForWallet.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
@ -17,10 +20,12 @@ 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';
import 'package:encrypt/encrypt.dart' as encrypt;
Future defaultSettingsMigration(
{@required int version,
@required SharedPreferences sharedPreferences,
@required FlutterSecureStorage secureStorage,
@required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource,
@required Box<Trade> tradeSource,
@ -29,9 +34,9 @@ Future defaultSettingsMigration(
await ios_migrate_v1(walletInfoSource, tradeSource, contactSource);
}
final currentVersion =
sharedPreferences.getInt('current_default_settings_migration_version') ??
0;
final currentVersion = sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ??
0;
if (currentVersion >= version) {
return;
}
@ -85,6 +90,10 @@ Future defaultSettingsMigration(
await updateDisplayModes(sharedPreferences);
break;
case 9:
await generateBackupPassword(secureStorage);
break;
default:
break;
}
@ -230,5 +239,17 @@ Future<void> updateDisplayModes(SharedPreferences sharedPreferences) async {
final currentBalanceDisplayMode =
sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey);
final balanceDisplayMode = currentBalanceDisplayMode < 2 ? 3 : 2;
await sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
await sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
}
Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
if ((await secureStorage.read(key: key))?.isNotEmpty ?? false) {
return;
}
final password = encrypt.Key.fromSecureRandom(32).base16;
await secureStorage.write(key: key, value: password);
}

View file

@ -8,7 +8,7 @@ import 'package:cake_wallet/entities/digest_request.dart';
part 'node.g.dart';
@HiveType(typeId: 1)
@HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable {
Node(
{@required this.uri,
@ -26,6 +26,7 @@ class Node extends HiveObject with Keyable {
typeRaw = map['typeRaw'] as int,
useSSL = map['useSSL'] as bool;
static const typeId = 1;
static const boxName = 'Nodes';
@HiveField(0)

View file

@ -14,4 +14,5 @@ class PreferencesKey {
static const displayActionListModeKey = 'display_list_mode';
static const currentPinLength = 'current_pin_length';
static const currentLanguageCode = 'language_code';
static const currentDefaultSettingsMigrationVersion = 'current_default_settings_migration_version';
}

View file

@ -2,10 +2,11 @@ import 'package:hive/hive.dart';
part 'template.g.dart';
@HiveType(typeId: 6)
@HiveType(typeId: Template.typeId)
class Template extends HiveObject {
Template({this.name, this.address, this.cryptoCurrency, this.amount});
static const typeId = 6;
static const boxName = 'Template';
@HiveField(0)

View file

@ -2,10 +2,11 @@ import 'package:hive/hive.dart';
part 'transaction_description.g.dart';
@HiveType(typeId: 2)
@HiveType(typeId: TransactionDescription.typeId)
class TransactionDescription extends HiveObject {
TransactionDescription({this.id, this.recipientAddress, this.transactionNote});
static const typeId = 2;
static const boxName = 'TransactionDescriptions';
static const boxKey = 'transactionDescriptionsBoxKey';

View file

@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/wallet_type.dart';
part 'wallet_info.g.dart';
@HiveType(typeId: 4)
@HiveType(typeId: WalletInfo.typeId)
class WalletInfo extends HiveObject {
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
this.timestamp, this.dirPath, this.path, this.address);
@ -23,6 +23,7 @@ class WalletInfo extends HiveObject {
date.millisecondsSinceEpoch ?? 0, dirPath, path, address);
}
static const typeId = 4;
static const boxName = 'WalletInfo';
@HiveField(0)

View file

@ -4,8 +4,9 @@ import 'package:hive/hive.dart';
part 'wallet_type.g.dart';
const walletTypes = [WalletType.monero, WalletType.bitcoin];
const walletTypeTypeId = 5;
@HiveType(typeId: 5)
@HiveType(typeId: walletTypeTypeId)
enum WalletType {
@HiveField(0)
monero,

View file

@ -2,7 +2,7 @@ import 'package:hive/hive.dart';
part 'exchange_template.g.dart';
@HiveType(typeId: 7)
@HiveType(typeId: ExchangeTemplate.typeId)
class ExchangeTemplate extends HiveObject {
ExchangeTemplate({
this.amount,
@ -13,6 +13,7 @@ class ExchangeTemplate extends HiveObject {
this.receiveAddress
});
static const typeId = 7;
static const boxName = 'ExchangeTemplate';
@HiveField(0)

View file

@ -6,7 +6,7 @@ import 'package:cake_wallet/entities/format_amount.dart';
part 'trade.g.dart';
@HiveType(typeId: 3)
@HiveType(typeId: Trade.typeId)
class Trade extends HiveObject {
Trade(
{this.id,
@ -27,6 +27,7 @@ class Trade extends HiveObject {
toRaw = to?.raw,
stateRaw = state?.raw;
static const typeId = 3;
static const boxName = 'Trades';
static const boxKey = 'tradesBoxKey';

View file

@ -1,7 +1,3 @@
import 'package:cake_wallet/core/backup.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
@ -12,6 +8,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cw_monero/wallet.dart' as monero_wallet;
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/router.dart' as Router;
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -32,20 +29,46 @@ import 'package:cake_wallet/src/screens/root/root.dart';
final navigatorKey = GlobalKey<NavigatorState>();
void main() async {
Future<void> main() async {
try {
WidgetsFlutterBinding.ensureInitialized();
final appDir = await getApplicationDocumentsDirectory();
await Hive.close();
Hive.init(appDir.path);
Hive.registerAdapter(ContactAdapter());
Hive.registerAdapter(NodeAdapter());
Hive.registerAdapter(TransactionDescriptionAdapter());
Hive.registerAdapter(TradeAdapter());
Hive.registerAdapter(WalletInfoAdapter());
Hive.registerAdapter(WalletTypeAdapter());
Hive.registerAdapter(TemplateAdapter());
Hive.registerAdapter(ExchangeTemplateAdapter());
if (!Hive.isAdapterRegistered(Contact.typeId)) {
Hive.registerAdapter(ContactAdapter());
}
if (!Hive.isAdapterRegistered(Node.typeId)) {
Hive.registerAdapter(NodeAdapter());
}
if (!Hive.isAdapterRegistered(TransactionDescription.typeId)) {
Hive.registerAdapter(TransactionDescriptionAdapter());
}
if (!Hive.isAdapterRegistered(Trade.typeId)) {
Hive.registerAdapter(TradeAdapter());
}
if (!Hive.isAdapterRegistered(WalletInfo.typeId)) {
Hive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(walletTypeTypeId)) {
Hive.registerAdapter(WalletTypeAdapter());
}
if (!Hive.isAdapterRegistered(Template.typeId)) {
Hive.registerAdapter(TemplateAdapter());
}
if (!Hive.isAdapterRegistered(ExchangeTemplate.typeId)) {
Hive.registerAdapter(ExchangeTemplateAdapter());
}
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
@ -57,11 +80,11 @@ void main() async {
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades =
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates =
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
@ -72,7 +95,8 @@ void main() async {
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
initialMigrationVersion: 5);
secureStorage: secureStorage,
initialMigrationVersion: 9);
runApp(App());
} catch (e) {
runApp(MaterialApp(
@ -80,7 +104,7 @@ void main() async {
home: Scaffold(
body: Container(
margin:
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Text(
'Error:\n${e.toString()}',
style: TextStyle(fontSize: 22),
@ -88,17 +112,20 @@ void main() async {
}
}
Future<void> initialSetup({@required SharedPreferences sharedPreferences,
@required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource,
@required Box<Trade> tradesSource,
// @required FiatConvertationService fiatConvertationService,
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
int initialMigrationVersion = 6}) async {
Future<void> initialSetup(
{@required SharedPreferences sharedPreferences,
@required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource,
@required Box<Trade> tradesSource,
// @required FiatConvertationService fiatConvertationService,
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
FlutterSecureStorage secureStorage,
int initialMigrationVersion = 9}) async {
await defaultSettingsMigration(
secureStorage: secureStorage,
version: initialMigrationVersion,
sharedPreferences: sharedPreferences,
walletInfoSource: walletInfoSource,
@ -113,7 +140,7 @@ Future<void> initialSetup({@required SharedPreferences sharedPreferences,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptionBox: transactionDescriptions);
bootstrap(navigatorKey);
await bootstrap(navigatorKey);
monero_wallet.onStartup();
}
@ -125,16 +152,14 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
final settingsStore = getIt
.get<AppStore>()
.settingsStore;
final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>();
final initialRoute = authenticationStore.state == AuthenticationState.denied
? Routes.disclaimer
: Routes.login;
return Observer(builder: (BuildContext context) {
final settingsStore = getIt.get<AppStore>().settingsStore;
final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>();
final initialRoute =
authenticationStore.state == AuthenticationState.denied
? Routes.disclaimer
: Routes.login;
final currentTheme = settingsStore.currentTheme;
final statusBarBrightness = currentTheme.type == ThemeType.dark
? Brightness.light

View file

@ -19,14 +19,12 @@ Future<void> bootstrap(GlobalKey<NavigatorState> navigatorKey) async {
final settingsStore = getIt.get<SettingsStore>();
final fiatConversionStore = getIt.get<FiatConversionStore>();
if (authenticationStore.state == AuthenticationState.uninitialized) {
final currentWalletName = getIt
.get<SharedPreferences>()
.getString(PreferencesKey.currentWalletName);
authenticationStore.state = currentWalletName == null
? AuthenticationState.denied
: AuthenticationState.installed;
}
final currentWalletName = getIt
.get<SharedPreferences>()
.getString(PreferencesKey.currentWalletName);
authenticationStore.state = currentWalletName == null
? AuthenticationState.denied
: AuthenticationState.installed;
startAuthenticationStateChange(authenticationStore, navigatorKey);
startCurrentWalletChangeReaction(

View file

@ -106,9 +106,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
param2: false));
case Routes.restoreOptions:
final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<RestoreOptionsPage>(param1: type));
builder: (_) => getIt.get<RestoreOptionsPage>());
case Routes.restoreWalletOptions:
final type = WalletType.monero; //settings.arguments as WalletType;

View file

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
@ -34,7 +36,7 @@ class BackupPage extends BasePage {
Center(
child: Container(
padding: EdgeInsets.only(left: 20, right: 20),
height: 225,
height: 300,
child: Column(children: [
Text(
'Backup password:',
@ -43,9 +45,21 @@ class BackupPage extends BasePage {
Padding(
padding: EdgeInsets.only(top: 20, bottom: 10),
child: Observer(
builder: (_) => Text(
backupViewModelBase.backupPassword,
style: TextStyle(fontSize: 26),
builder: (_) => GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(
text:
backupViewModelBase.backupPassword));
showBar<void>(
context,
S.of(context).transaction_details_copied(
'Backup password'));
},
child: Text(
backupViewModelBase.backupPassword,
style: TextStyle(fontSize: 26),
textAlign: TextAlign.center,
),
))),
Padding(
padding: EdgeInsets.all(20),

View file

@ -84,5 +84,6 @@ class RestoreFromBackupPage extends BasePage {
}
await restoreFromBackupViewModel.import(textEditingController.text);
textEditingController.text = '';
}
}

View file

@ -1,4 +1,3 @@
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
import 'package:flutter/cupertino.dart';
@ -7,10 +6,9 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
class RestoreOptionsPage extends BasePage {
RestoreOptionsPage({@required this.type});
RestoreOptionsPage();
static const _aspectRatioImage = 2.086;
final WalletType type;
@override
String get title => S.current.restore_restore_wallet;
@ -21,33 +19,30 @@ class RestoreOptionsPage extends BasePage {
@override
Widget body(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.all(24),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
RestoreButton(
onPressed: () =>
Navigator.pushNamed(
context, Routes.restoreWalletOptionsFromWelcome),
image: imageSeedKeys,
title: S.of(context).restore_title_from_seed_keys,
description: S.of(context).restore_description_from_seed_keys
),
Padding(
padding: EdgeInsets.only(top: 24),
child: RestoreButton(
onPressed: () => Navigator.pushNamed(
context, Routes.restoreFromBackup),
image: imageBackup,
title: S.of(context).restore_title_from_backup,
description: S.of(context).restore_description_from_backup
),
)
],
),
)
);
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.all(24),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
RestoreButton(
onPressed: () =>
Navigator.pushNamed(context, Routes.restoreWalletOptionsFromWelcome),
image: imageSeedKeys,
title: S.of(context).restore_title_from_seed_keys,
description:
S.of(context).restore_description_from_seed_keys),
Padding(
padding: EdgeInsets.only(top: 24),
child: RestoreButton(
onPressed: () =>
Navigator.pushNamed(context, Routes.restoreFromBackup),
image: imageBackup,
title: S.of(context).restore_title_from_backup,
description: S.of(context).restore_description_from_backup),
)
],
),
));
}
}

View file

@ -160,7 +160,7 @@ class WelcomePage extends BasePage {
child: PrimaryImageButton(
onPressed: () =>
Navigator.pushNamed(context,
Routes.restoreWalletOptionsFromWelcome),
Routes.restoreOptions),
image: restoreWalletImage,
text: S
.of(context)

View file

@ -18,7 +18,7 @@ class TrailButton extends StatelessWidget {
caption,
style: TextStyle(
color:
Theme.of(context).accentTextTheme.display4.decorationColor,
Theme.of(context).accentTextTheme.bodyText2.color,
fontWeight: FontWeight.w500,
fontSize: 14),
),

View file

@ -85,10 +85,10 @@ abstract class SettingsStoreBase with Store {
(String languageCode) => sharedPreferences.setString(
PreferencesKey.currentLanguageCode, languageCode));
reaction((_) => balanceDisplayMode,
(BalanceDisplayMode mode) => sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey,
mode.serialize()));
reaction(
(_) => balanceDisplayMode,
(BalanceDisplayMode mode) => sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey, mode.serialize()));
this
.nodes
@ -158,7 +158,7 @@ abstract class SettingsStoreBase with Store {
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
false;
final legacyTheme =
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
? ThemeType.dark.index
: ThemeType.bright.index;
final savedTheme = ThemeList.deserialize(
@ -203,6 +203,29 @@ abstract class SettingsStoreBase with Store {
initialLanguageCode: savedLanguageCode);
}
Future<void> reload(
{@required Box<Node> nodeSource,
FiatCurrency initialFiatCurrency = FiatCurrency.usd,
TransactionPriority initialTransactionPriority = TransactionPriority.slow,
BalanceDisplayMode initialBalanceDisplayMode =
BalanceDisplayMode.availableBalance}) async {
final settings = await SettingsStoreBase.load(
nodeSource: nodeSource,
initialBalanceDisplayMode: initialBalanceDisplayMode,
initialFiatCurrency: initialFiatCurrency,
initialTransactionPriority: initialTransactionPriority);
fiatCurrency = settings.fiatCurrency;
actionlistDisplayMode = settings.actionlistDisplayMode;
transactionPriority = settings.transactionPriority;
balanceDisplayMode = settings.balanceDisplayMode;
shouldSaveRecipientAddress = settings.shouldSaveRecipientAddress;
allowBiometricalAuthentication = settings.allowBiometricalAuthentication;
currentTheme = settings.currentTheme;
pinCodeLength = settings.pinCodeLength;
languageCode = settings.languageCode;
appVersion = settings.appVersion;
}
Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
switch (walletType) {
case WalletType.bitcoin:

View file

@ -1,4 +1,4 @@
import 'package:cake_wallet/core/backup.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/store/secret_store.dart';
@ -52,7 +52,7 @@ abstract class BackupViewModelBase with Store {
Future<BackupExportFile> exportBackup() async {
try {
state = IsExecutingState();
final backupContent = await backupService.exportBackup('', nonce: '');
final backupContent = await backupService.exportBackup(backupPassword);
state = ExecutedSuccessfullyState();
return BackupExportFile(backupContent.toList(),
@ -60,6 +60,7 @@ abstract class BackupViewModelBase with Store {
} catch (e) {
print(e.toString());
state = FailureState(e.toString());
return null;
}
}

View file

@ -1,11 +1,17 @@
import 'dart:io';
import 'package:cake_wallet/core/backup.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
part 'restore_from_backup_view_model.g.dart';
class RestoreFromBackupViewModel = RestoreFromBackupViewModelBase with _$RestoreFromBackupViewModel;
class RestoreFromBackupViewModel = RestoreFromBackupViewModelBase
with _$RestoreFromBackupViewModel;
abstract class RestoreFromBackupViewModelBase with Store {
RestoreFromBackupViewModelBase(this.backupService);
@ -15,15 +21,36 @@ abstract class RestoreFromBackupViewModelBase with Store {
final BackupService backupService;
@action
void reset() => filePath = '';
Future<void> import(String password) async {
if (filePath?.isEmpty ?? true) {
// FIXME: throw exception;
return;
try {
if (filePath?.isEmpty ?? true) {
// FIXME: throw exception;
return;
}
final file = File(filePath);
final data = await file.readAsBytes();
await backupService.importBackup(data, password);
await main();
final store = getIt.get<AppStore>();
ReactionDisposer reaction;
await store.settingsStore.reload(nodeSource: getIt.get<Box<Node>>());
reaction = autorun((_) {
final wallet = store.wallet;
if (wallet != null) {
store.authenticationStore.state = AuthenticationState.allowed;
reaction?.reaction?.dispose();
}
});
} catch (e) {
print(e.toString());
}
final file = File(filePath);
final data = await file.readAsBytes();
await backupService.importBackup(data, password, nonce: null);
}
}
}

View file

@ -122,6 +122,19 @@ abstract class SettingsViewModelBase with Store {
onItemSelected: (ThemeBase theme) =>
_settingsStore.currentTheme = theme)
],
[
RegularListItem(
title: 'Backup',
handler: (BuildContext context) {
Navigator.of(context).pushNamed(Routes.auth, arguments:
(bool isAuthenticatedSuccessfully, AuthPageState auth) {
auth.close();
if (isAuthenticatedSuccessfully) {
Navigator.of(context).pushNamed(Routes.backup);
}
});
}),
],
[
LinkListItem(
title: 'Email',