This commit is contained in:
M 2021-01-13 18:43:34 +02:00
parent c20d57e9a9
commit c09a41b090
18 changed files with 826 additions and 24 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

244
lib/core/backup.dart Normal file
View file

@ -0,0 +1,244 @@
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';
class BackupService {
BackupService(this._flutterSecureStorage, this._authService,
this._walletInfoSource, this._keyService, this._sharedPreferences)
: _cipher = chacha20Poly1305Aead;
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);
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;
}
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 {
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);
return await _encrypt(content, password, nonce);
}
Future<Uint8List> exportKeychainDump(String password,
{@required String nonce}) 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 data =
utf8.encode(json.encode({'pin': decodedPin, 'wallets': wallets}));
final encrypted = await _encrypt(Uint8List.fromList(data), password, nonce);
return encrypted;
}
Future<String> exportPreferencesJSON() async {
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.currentDarkTheme:
_sharedPreferences.getBool(PreferencesKey.currentDarkTheme),
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),
'currentTheme': _sharedPreferences.getInt('current_theme')
// FIX-ME: Unnamed constant.
};
return json.encode(preferences);
}
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.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';
@ -20,6 +23,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';
@ -32,6 +37,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';
@ -46,13 +52,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/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/auth_view_model.dart';
@ -127,6 +136,11 @@ Future setup(
getIt.registerSingleton<ExchangeTemplateStore>(
ExchangeTemplateStore(templateSource: exchangeTemplates));
final secretStore =
await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
getIt.registerSingleton<SecretStore>(secretStore);
getIt.registerFactory<KeyService>(
() => KeyService(getIt.get<FlutterSecureStorage>()));
@ -291,17 +305,16 @@ Future setup(
getIt.get<MoneroAccountEditOrCreateViewModel>()));*/
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
AccountListItem, void>(
(AccountListItem account, _) =>
MoneroAccountEditOrCreateViewModel((
getIt.get<AppStore>().wallet as MoneroWallet).accountList,
accountListItem: account));
AccountListItem, void>(
(AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
(getIt.get<AppStore>().wallet as MoneroWallet).accountList,
accountListItem: account));
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage,
AccountListItem, void>((AccountListItem account, _) =>
MoneroAccountEditOrCreatePage(
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem,
void>(
(AccountListItem account, _) => MoneroAccountEditOrCreatePage(
moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactory(() {
final appStore = getIt.get<AppStore>();
@ -422,4 +435,32 @@ Future setup(
transactionDescriptionBox));
getIt.registerFactory(() => PreSeedPage());
getIt.registerFactory(() => BackupService(
getIt.get<FlutterSecureStorage>(),
getIt.get<AuthService>(),
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.registerFactoryParam<RestoreOptionsPage, WalletType, void>(
(WalletType type, _) => RestoreOptionsPage(type: type));
getIt.registerFactory(
() => RestoreFromBackupViewModel(getIt.get<BackupService>()));
getIt.registerFactory(
() => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
}

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,9 +23,15 @@ String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
}
break;
case SecretStoreKey.backupPassword:
{
_key = backupPassword;
}
break;
default:
{}
}
return _key;
}
}

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/core/backup.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';

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';
@ -65,15 +68,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<SetupPinCodePage>(param1:
(PinCodeState<PinCodeWidget> context, dynamic _) async {
try {
context.changeProcessText(
S.current.creating_new_wallet);
context.changeProcessText(S.current.creating_new_wallet);
final newWalletVM =
getIt.get<WalletNewVM>(param1: WalletType.monero);
await newWalletVM.create(
options: 'English'); // FIXME: Unnamed constant
context.hideProgressText();
await Navigator.of(context.context)
.pushNamed(Routes.preSeed);
await Navigator.of(context.context).pushNamed(Routes.preSeed);
} catch (e) {
context.changeProcessText('Error: ${e.toString()}');
}
@ -115,7 +116,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreOptions:
final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>(
builder: (_) => RestoreOptionsPage(type: type));
builder: (_) => getIt.get<RestoreOptionsPage>(param1: type));
case Routes.restoreWalletOptions:
final type = WalletType.monero; //settings.arguments as WalletType;
@ -264,7 +265,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.accountCreation:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
param1: settings.arguments as AccountListItem));
param1: settings.arguments as AccountListItem));
case Routes.addressBook:
return MaterialPageRoute<void>(
@ -326,8 +327,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<LanguageListPage>());
case Routes.preSeed:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<PreSeedPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<PreSeedPage>());
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>(

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,93 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:esys_flutter_share/esys_flutter_share.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: 225,
child: Column(children: [
Text(
'Backup password:',
style: TextStyle(fontSize: 30),
),
Padding(
padding: EdgeInsets.only(top: 20, bottom: 10),
child: Observer(
builder: (_) => Text(
backupViewModelBase.backupPassword,
style: TextStyle(fontSize: 26),
))),
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,88 @@
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);
}
}

View file

@ -1,6 +1,5 @@
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';
@ -39,7 +38,8 @@ class RestoreOptionsPage extends BasePage {
Padding(
padding: EdgeInsets.only(top: 24),
child: RestoreButton(
onPressed: () {},
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

@ -21,7 +21,7 @@ class TrailButton extends StatelessWidget {
child: Text(
caption,
style: TextStyle(
color: Theme.of(context).textTheme.subhead.decorationColor,
color: Theme.of(context).textTheme.display1.color,
fontWeight: FontWeight.w500,
fontSize: 14),
),

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

@ -0,0 +1,68 @@
import 'package:cake_wallet/core/backup.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('', nonce: '');
state = ExecutedSuccessfullyState();
return BackupExportFile(backupContent.toList(),
name: 'backup_${DateTime.now().toString()}.zip');
} catch (e) {
print(e.toString());
state = FailureState(e.toString());
}
}
@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,29 @@
import 'dart:io';
import 'package:cake_wallet/core/backup.dart';
import 'package:mobx/mobx.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;
Future<void> import(String password) async {
if (filePath?.isEmpty ?? true) {
// FIXME: throw exception;
return;
}
final file = File(filePath);
final data = await file.readAsBytes();
await backupService.importBackup(data, password, nonce: null);
}
}

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"
@ -253,6 +253,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
cryptography:
dependency: "direct main"
description:
name: cryptography
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.1"
cupertino_icons:
dependency: "direct main"
description:
@ -344,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

@ -67,6 +67,9 @@ dependencies:
connectivity: ^0.4.9+2
keyboard_actions: ^3.3.0
flushbar: ^1.10.4
archive: ^2.0.13
cryptography: ^1.4.0
file_picker: ^2.1.4
dev_dependencies:
flutter_test: