Merge pull request #173 from cake-tech/backups

Backups
This commit is contained in:
M 2021-01-15 19:42:52 +02:00
commit ab5077e5f5
32 changed files with 1139 additions and 141 deletions

View file

@ -26,8 +26,42 @@ PODS:
- Flutter
- devicelocale (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.2):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.2)
- DKImagePickerController/PhotoGallery (4.3.2):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.2)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.17):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- esys_flutter_share (0.0.1):
- Flutter
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_secure_storage (3.3.1):
- Flutter
@ -39,11 +73,15 @@ PODS:
- path_provider (0.0.1):
- Flutter
- Reachability (3.2)
- SDWebImage (5.9.1):
- SDWebImage/Core (= 5.9.1)
- SDWebImage/Core (5.9.1)
- share (0.0.1):
- Flutter
- shared_preferences (0.0.1):
- Flutter
- SwiftProtobuf (1.12.0)
- SwiftyGif (5.3.0)
- url_launcher (0.0.1):
- Flutter
@ -54,6 +92,7 @@ DEPENDENCIES:
- cw_monero (from `.symlinks/plugins/cw_monero/ios`)
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
- esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- local_auth (from `.symlinks/plugins/local_auth/ios`)
@ -66,9 +105,13 @@ DEPENDENCIES:
SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
- CryptoSwift
- DKImagePickerController
- DKPhotoGallery
- MTBBarcodeScanner
- Reachability
- SDWebImage
- SwiftProtobuf
- SwiftyGif
EXTERNAL SOURCES:
barcode_scan:
@ -81,6 +124,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/devicelocale/ios"
esys_flutter_share:
:path: ".symlinks/plugins/esys_flutter_share/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_secure_storage:
@ -104,7 +149,10 @@ SPEC CHECKSUMS:
CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060
cw_monero: 2e1f79929880cc2293b5bc1b25e28152e4d84649
devicelocale: feebbe5e7a30adb8c4f83185de1b50ff19b44f00
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd
@ -112,9 +160,11 @@ SPEC CHECKSUMS:
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
PODFILE CHECKSUM: ba3d2157523e2f4dc333b987efdac6635da8125d

View file

@ -0,0 +1,301 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cryptography/cryptography.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:archive/archive_io.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/key_service.dart';
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._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 Box<WalletInfo> _walletInfoSource;
final KeyService _keyService;
Future<void> importBackup(Uint8List data, String password,
{String nonce = secrets.backupSalt}) async {
final version = getVersion(data);
final backupBytes = data.toList()..removeAt(0);
final backupData = Uint8List.fromList(backupBytes);
switch (version) {
case _v1:
await _importBackupV1(backupData, password, nonce: nonce);
break;
default:
break;
}
}
Future<Uint8List> exportBackup(String password,
{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 preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
if (tmpDir.existsSync()) {
tmpDir.deleteSync(recursive: true);
}
tmpDir.createSync();
zipEncoder.create(archivePath);
fileEntities.forEach((entity) {
if (entity.path == archivePath || entity.path == tmpDir.path) {
return;
}
if (entity.statSync().type == FileSystemEntityType.directory) {
zipEncoder.addDirectory(Directory(entity.path));
} else {
zipEncoder.addFile(File(entity.path));
}
});
await keychainDumpFile.writeAsBytes(keychainDump.toList());
await preferencesDumpFile.writeAsString(preferencesDump);
zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump');
zipEncoder.addFile(keychainDumpFile, '~_keychain_dump');
zipEncoder.close();
final content = File(archivePath).readAsBytesSync();
tmpDir.deleteSync(recursive: true);
final encryptedData = await _encrypt(content, password, nonce);
return setVersion(encryptedData, currentVersion);
}
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);
final wallets =
await Future.wait(_walletInfoSource.values.map((walletInfo) async {
return {
'name': walletInfo.name,
'type': walletInfo.type.toString(),
'password':
await _keyService.getWalletPassword(walletName: walletInfo.name)
};
}));
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 {
const defaultSettingsMigrationVersionKey =
'current_default_settings_migration_version';
final preferences = <String, Object>{
PreferencesKey.currentWalletName:
_sharedPreferences.getString(PreferencesKey.currentWalletName),
PreferencesKey.currentNodeIdKey:
_sharedPreferences.getInt(PreferencesKey.currentNodeIdKey),
PreferencesKey.currentBalanceDisplayModeKey: _sharedPreferences
.getInt(PreferencesKey.currentBalanceDisplayModeKey),
PreferencesKey.currentWalletType:
_sharedPreferences.getInt(PreferencesKey.currentWalletType),
PreferencesKey.currentFiatCurrencyKey:
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences
.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
PreferencesKey.isDarkThemeLegacy:
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy),
PreferencesKey.currentPinLength:
_sharedPreferences.getInt(PreferencesKey.currentPinLength),
PreferencesKey.currentTransactionPriorityKey: _sharedPreferences
.getInt(PreferencesKey.currentTransactionPriorityKey),
PreferencesKey.allowBiometricalAuthenticationKey: _sharedPreferences
.getBool(PreferencesKey.allowBiometricalAuthenticationKey),
PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey),
PreferencesKey.currentLanguageCode:
_sharedPreferences.getString(PreferencesKey.currentLanguageCode),
PreferencesKey.displayActionListModeKey:
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
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));
final secretKey = SecretKey(secretKeyHash.bytes);
final nonce = Nonce(base64.decode(nonceBase64));
return await _cipher.encrypt(data, secretKey: secretKey, nonce: nonce);
}
Future<Uint8List> _decrypt(
Uint8List data, String secretKeySource, String nonceBase64) async {
final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource));
final secretKey = SecretKey(secretKeyHash.bytes);
final nonce = Nonce(base64.decode(nonceBase64));
return await _cipher.decrypt(data, secretKey: secretKey, nonce: nonce);
}
}

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.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';
@ -10,6 +11,8 @@ import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@ -21,6 +24,8 @@ import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
@ -34,6 +39,7 @@ import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
import 'package:cake_wallet/store/node_list_store.dart';
import 'package:cake_wallet/store/secret_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/key_service.dart';
@ -48,13 +54,16 @@ import 'package:cake_wallet/src/screens/send/send_page.dart';
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/store/wallet_list_store.dart';
import 'package:cake_wallet/view_model/backup_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/rescan_view_model.dart';
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
@ -96,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,
@ -104,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());
@ -122,14 +154,19 @@ 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>());
getIt.registerSingleton<SecretStore>(secretStore);
getIt.registerFactory<KeyService>(
() => KeyService(getIt.get<FlutterSecureStorage>()));
@ -143,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
@ -153,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);
});
@ -163,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);
});
@ -248,7 +285,7 @@ Future setup(
getIt.get<AppStore>().settingsStore,
getIt.get<SendTemplateStore>(),
getIt.get<FiatConversionStore>(),
transactionDescriptionBox));
_transactionDescriptionBox));
getIt.registerFactory(
() => SendPage(sendViewModel: getIt.get<SendViewModel>()));
@ -257,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)));
@ -328,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>(),
@ -344,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>()));
@ -379,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, __) {
@ -414,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, _) =>
@ -424,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>(
@ -441,8 +478,36 @@ 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>(),
_walletInfoSource,
getIt.get<KeyService>(),
getIt.get<SharedPreferences>()));
getIt.registerFactory(() => BackupViewModel(getIt.get<FlutterSecureStorage>(),
getIt.get<SecretStore>(), getIt.get<BackupService>()));
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
getIt.registerFactory(() => EditBackupPasswordViewModel(
getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>())
..init());
getIt.registerFactory(
() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
getIt.registerFactory(() => RestoreOptionsPage());
getIt.registerFactory(
() => RestoreFromBackupViewModel(getIt.get<BackupService>()));
getIt.registerFactory(
() => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
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

@ -1,9 +1,13 @@
enum SecretStoreKey { moneroWalletPassword, pinCodePassword }
enum SecretStoreKey { moneroWalletPassword, pinCodePassword, backupPassword }
const moneroWalletPassword = "MONERO_WALLET_PASSWORD";
const pinCodePassword = "PIN_CODE_PASSWORD";
const backupPassword = "BACKUP_CODE_PASSWORD";
String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
String generateStoreKeyFor({
SecretStoreKey key,
String walletName = "",
}) {
var _key = "";
switch (key) {
@ -19,6 +23,12 @@ String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
}
break;
case SecretStoreKey.backupPassword:
{
_key = backupPassword;
}
break;
default:
{}
}

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,5 +1,3 @@
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';
@ -10,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';
@ -30,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);
@ -55,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,
@ -70,7 +95,8 @@ void main() async {
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
initialMigrationVersion: 5);
secureStorage: secureStorage,
initialMigrationVersion: 9);
runApp(App());
} catch (e) {
runApp(MaterialApp(
@ -78,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),
@ -86,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,
@ -111,7 +140,7 @@ Future<void> initialSetup({@required SharedPreferences sharedPreferences,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptionBox: transactionDescriptions);
bootstrap(navigatorKey);
await bootstrap(navigatorKey);
monero_wallet.onStartup();
}
@ -123,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

@ -1,6 +1,9 @@
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
@ -103,9 +106,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
param2: false));
case Routes.restoreOptions:
final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>(
builder: (_) => RestoreOptionsPage(type: type));
builder: (_) => getIt.get<RestoreOptionsPage>());
case Routes.restoreWalletOptions:
final type = WalletType.monero; //settings.arguments as WalletType;
@ -317,6 +319,18 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) =>
getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
case Routes.backup:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
case Routes.editBackupPassword:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<EditBackupPasswordPage>());
case Routes.restoreFromBackup:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<RestoreFromBackupPage>());
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(

View file

@ -48,4 +48,7 @@ class Routes {
static const restoreWalletType = '/restore_wallet_type';
static const restoreWallet = '/restore_wallet';
static const preSeed = '/pre_seed';
static const backup = '/backup';
static const editBackupPassword = '/edit_backup_passowrd';
static const restoreFromBackup = '/restore_from_backup';
}

View file

@ -0,0 +1,107 @@
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';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/backup_view_model.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class BackupPage extends BasePage {
BackupPage(this.backupViewModelBase);
final BackupViewModelBase backupViewModelBase;
@override
String get title => 'Backup';
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).edit,
onPressed: () =>
Navigator.of(context).pushNamed(Routes.editBackupPassword));
@override
Widget body(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
Center(
child: Container(
padding: EdgeInsets.only(left: 20, right: 20),
height: 300,
child: Column(children: [
Text(
'Backup password:',
style: TextStyle(fontSize: 30),
),
Padding(
padding: EdgeInsets.only(top: 20, bottom: 10),
child: Observer(
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),
child: Text(
'Please write down your Backup Password. Backup Password uses for import of backup files.',
style: TextStyle(fontSize: 14, color: Colors.grey),
textAlign: TextAlign.center,
))
]))),
Positioned(
child: Observer(
builder: (_) => LoadingPrimaryButton(
isLoading: backupViewModelBase.state is IsExecutingState,
onPressed: () => onExportBackup(context),
text: 'Export backup',
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white)),
bottom: 30,
left: 20,
right: 20,
)
],
);
}
void onExportBackup(BuildContext context) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: 'Export backup',
alertContent:
'Please be sure that you have saved your Backup Password.You will be no available to import backup files without Backup Password.\n\nHave you written it down?',
rightButtonText: S.of(context).seed_alert_yes,
leftButtonText: S.of(context).seed_alert_back,
actionRightButton: () async {
Navigator.of(dialogContext).pop();
final backup = await backupViewModelBase.exportBackup();
await Share.file(
'Backup file', backup.name, backup.content, 'text');
},
actionLeftButton: () => Navigator.of(dialogContext).pop());
});
}
}

View file

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
class EditBackupPasswordPage extends BasePage {
EditBackupPasswordPage(this.editBackupPasswordViewModel)
: textEditingController = TextEditingController() {
textEditingController.text = editBackupPasswordViewModel.backupPassword;
textEditingController.addListener(() => editBackupPasswordViewModel
.backupPassword = textEditingController.text);
}
final EditBackupPasswordViewModel editBackupPasswordViewModel;
final TextEditingController textEditingController;
@override
String get title => 'Edit Backup Password';
@override
Widget body(BuildContext context) {
return Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Observer(
builder: (_) => TextFormField(
autofocus: true,
enableSuggestions: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
controller: textEditingController,
style: TextStyle(fontSize: 26, color: Colors.black)))),
Positioned(
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () => onSave(context),
text: 'Save',
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isDisabled: !editBackupPasswordViewModel.canSave)),
bottom: 30,
left: 0,
right: 0)
],
));
}
void onSave(BuildContext context) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: 'Save backup password',
alertContent:
'Your previous backup files will be not available to import with new backup password. New backup password will be used only for new backup files. Are you sure that you want to change backup password ?',
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () async {
await editBackupPasswordViewModel.save();
Navigator.of(dialogContext)..pop()..pop();
},
actionLeftButton: () => Navigator.of(dialogContext).pop());
});
}
}

View file

@ -0,0 +1,89 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:file_picker/file_picker.dart';
class RestoreFromBackupPage extends BasePage {
RestoreFromBackupPage(this.restoreFromBackupViewModel)
: textEditingController = TextEditingController();
final RestoreFromBackupViewModel restoreFromBackupViewModel;
final TextEditingController textEditingController;
@override
String get title => 'Restore from backup';
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(bottom: 30, left: 25, right: 25),
child: Column(children: [
Expanded(
child: Container(
child: Center(
child: TextFormField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(
hintText: 'Enter backup password here'),
keyboardType: TextInputType.visiblePassword,
controller: textEditingController,
style: TextStyle(fontSize: 26, color: Colors.black))),
),
),
Container(
child: Row(children: [
Expanded(
child: PrimaryButton(
onPressed: () => presentFilePicker(),
text: 'Select backup file',
color: Colors.grey,
textColor: Colors.white)),
SizedBox(width: 20),
Expanded(
child: PrimaryButton(
onPressed: () => onImportHandler(context),
text: 'Import',
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white))
])),
]));
}
Future<void> presentFilePicker() async {
final result = await FilePicker.platform.pickFiles();
if (result?.files?.isEmpty ?? true) {
return;
}
restoreFromBackupViewModel.filePath = result.files.first.path;
}
Future<void> onImportHandler(BuildContext context) async {
if (textEditingController.text.isEmpty ||
(restoreFromBackupViewModel.filePath.isEmpty ?? true)) {
await showPopUp<void>(
context: context,
builder: (_) {
return AlertWithOneAction(
alertTitle: S.current.error,
alertContent:
'Please select backup file and enter backup password.',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
return;
}
await restoreFromBackupViewModel.import(textEditingController.text);
textEditingController.text = '';
}
}

View file

@ -1,6 +1,4 @@
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
@ -8,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;
@ -22,32 +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: () {},
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

@ -1,34 +1,28 @@
import 'package:flutter/material.dart';
class TrailButton extends StatelessWidget {
TrailButton({
@required this.caption,
@required this.onPressed
});
TrailButton({@required this.caption, @required this.onPressed});
final String caption;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return ButtonTheme(
minWidth: double.minPositive,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: FlatButton(
padding: EdgeInsets.all(0),
child: Text(
caption,
style: TextStyle(
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 14),
),
onPressed: onPressed),
padding: EdgeInsets.all(0),
child: Text(
caption,
style: TextStyle(
color:
Theme.of(context).accentTextTheme.bodyText2.color,
fontWeight: FontWeight.w500,
fontSize: 14),
),
onPressed: onPressed),
);
}
}

View file

@ -0,0 +1,28 @@
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mobx/mobx.dart';
part 'secret_store.g.dart';
class SecretStore = SecretStoreBase with _$SecretStore;
abstract class SecretStoreBase with Store {
static Future<SecretStore> load(FlutterSecureStorage storage) async {
final secretStore = SecretStore();
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = await storage.read(key: backupPasswordKey);
secretStore.write(key: backupPasswordKey, value: backupPassword);
return secretStore;
}
SecretStoreBase() : values = ObservableMap<String, String>();
ObservableMap values;
String read(String key) => values[key] as String;
String write({@required String key, @required String value}) =>
values[key] = value;
}

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

@ -0,0 +1,69 @@
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';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mobx/mobx.dart';
part 'backup_view_model.g.dart';
class BackupExportFile {
BackupExportFile(this.content, {@required this.name});
final String name;
final List<int> content;
}
class BackupViewModel = BackupViewModelBase with _$BackupViewModel;
abstract class BackupViewModelBase with Store {
BackupViewModelBase(this.secureStorage, this.secretStore, this.backupService)
: isBackupPasswordVisible = false {
state = InitialExecutionState();
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
secretStore.values.observe((change) {
if (change.key == key) {
backupPassword = secretStore.read(key);
}
}, fireImmediately: true);
}
final FlutterSecureStorage secureStorage;
final SecretStore secretStore;
final BackupService backupService;
@observable
ExecutionState state;
@observable
bool isBackupPasswordVisible;
@observable
String backupPassword;
@action
Future<void> init() async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
backupPassword = await secureStorage.read(key: key);
}
@action
Future<BackupExportFile> exportBackup() async {
try {
state = IsExecutingState();
final backupContent = await backupService.exportBackup(backupPassword);
state = ExecutedSuccessfullyState();
return BackupExportFile(backupContent.toList(),
name: 'backup_${DateTime.now().toString()}.zip');
} catch (e) {
print(e.toString());
state = FailureState(e.toString());
return null;
}
}
@action
void showMasterPassword() => isBackupPasswordVisible = true;
}

View file

@ -0,0 +1,44 @@
import 'package:mobx/mobx.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/store/secret_store.dart';
part 'edit_backup_password_view_model.g.dart';
class EditBackupPasswordViewModel = EditBackupPasswordViewModelBase
with _$EditBackupPasswordViewModel;
abstract class EditBackupPasswordViewModelBase with Store {
EditBackupPasswordViewModelBase(this.secureStorage, this.secretStore) {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
backupPassword = secretStore.read(key);
}
final FlutterSecureStorage secureStorage;
final SecretStore secretStore;
@observable
String backupPassword;
@computed
bool get canSave {
return !(_originalPassword == backupPassword);
}
String _originalPassword;
@action
Future<void> init() async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final password = await secureStorage.read(key: key);
_originalPassword = password;
backupPassword = password;
}
@action
Future<void> save() async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
await secureStorage.write(key: key, value: backupPassword);
secretStore.write(key: key, value: backupPassword);
}
}

View file

@ -0,0 +1,56 @@
import 'dart:io';
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;
abstract class RestoreFromBackupViewModelBase with Store {
RestoreFromBackupViewModelBase(this.backupService);
@observable
String filePath;
final BackupService backupService;
@action
void reset() => filePath = '';
Future<void> import(String password) async {
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());
}
}
}

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',

View file

@ -16,7 +16,7 @@ packages:
source: hosted
version: "0.40.6"
archive:
dependency: transitive
dependency: "direct main"
description:
name: archive
url: "https://pub.dartlang.org"
@ -351,6 +351,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.1"
file_picker:
dependency: "direct main"
description:
name: file_picker
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
fixnum:
dependency: transitive
description:

View file

@ -68,8 +68,10 @@ dependencies:
connectivity: ^0.4.9+2
keyboard_actions: ^3.3.0
flushbar: ^1.10.4
unorm_dart: ^0.1.2
archive: ^2.0.13
cryptography: ^1.4.0
file_picker: ^2.1.4
unorm_dart: ^0.1.2
dev_dependencies:
flutter_test: