From cd7f636558efc48fbc2feb57123c9bb38c752931 Mon Sep 17 00:00:00 2001 From: fosse Date: Wed, 16 Aug 2023 10:24:20 -0400 Subject: [PATCH] lots of fixes + finish up seed derivation --- cw_nano/lib/nano_wallet_service.dart | 35 ++++---- lib/nano/cw_nano.dart | 25 ++++++ lib/nano/nano.dart | 10 ++- .../wallet_restore_choose_derivation.dart | 2 +- .../wallet_restore_from_keys_form.dart | 81 +++++++++++-------- .../screens/restore/wallet_restore_page.dart | 69 +++++++--------- ..._restore_choose_derivation_view_model.dart | 56 +++++++------ lib/view_model/wallet_restore_view_model.dart | 65 +++++++++------ tool/configure.dart | 10 ++- 9 files changed, 209 insertions(+), 144 deletions(-) diff --git a/cw_nano/lib/nano_wallet_service.dart b/cw_nano/lib/nano_wallet_service.dart index c9a49666a..f542e6de1 100644 --- a/cw_nano/lib/nano_wallet_service.dart +++ b/cw_nano/lib/nano_wallet_service.dart @@ -232,25 +232,26 @@ class NanoWalletService extends WalletService restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async { - throw UnimplementedError("restoreFromKeys"); + if (credentials.seedKey.contains(' ')) { + throw Exception("Invalid key!"); + } else { + if (credentials.seedKey.length != 64 && credentials.seedKey.length != 128) { + throw Exception("Invalid key length!"); + } + } - // TODO: mnemonic can't be derived from the seedKey in the nano standard derivation - // which complicates things - // DerivationType derivationType = credentials.derivationType ?? await compareDerivationMethods(seedKey: credentials.seedKey); - // String? mnemonic; - // final nanoWalletInfo = NanoWalletInfo( - // walletInfo: credentials.walletInfo!, - // derivationType: derivationType, - // ); - // final wallet = await NanoWallet( - // password: credentials.password!, - // mnemonic: mnemonic ?? "", // we can't derive the mnemonic from the key in all cases - // walletInfo: nanoWalletInfo, - // ); - // await wallet.init(); - // await wallet.save(); - // return wallet; + DerivationType derivationType = credentials.derivationType ?? DerivationType.nano; + credentials.walletInfo!.derivationType = derivationType; + + final wallet = await NanoWallet( + password: credentials.password!, + mnemonic: credentials.seedKey,// we can't derive the mnemonic from the key in all cases + walletInfo: credentials.walletInfo!, + ); + await wallet.init(); + await wallet.save(); + return wallet; } @override diff --git a/lib/nano/cw_nano.dart b/lib/nano/cw_nano.dart index ea1fa2fbf..5355ebdbd 100644 --- a/lib/nano/cw_nano.dart +++ b/lib/nano/cw_nano.dart @@ -127,6 +127,31 @@ class CWNano extends Nano { ); } + @override + WalletCredentials createNanoRestoreWalletFromKeysCredentials({ + required String name, + required String password, + required String seedKey, + DerivationType? derivationType, + }) { + + if (derivationType == null) { + // figure out the derivation type as best we can, otherwise set it to "unknown" + if (seedKey.length == 64) { + derivationType = DerivationType.nano; + } else { + derivationType = DerivationType.unknown; + } + } + + return NanoRestoreWalletFromKeysCredentials( + name: name, + password: password, + seedKey: seedKey, + derivationType: derivationType, + ); + } + @override TransactionHistoryBase getTransactionHistory(Object wallet) { throw UnimplementedError(); diff --git a/lib/nano/nano.dart b/lib/nano/nano.dart index c22ce60df..15a39ca0d 100644 --- a/lib/nano/nano.dart +++ b/lib/nano/nano.dart @@ -12,9 +12,6 @@ import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/output_info.dart'; import 'package:hive/hive.dart'; -import 'package:cw_nano/api/wallet.dart' as nano_wallet_api; -import 'package:cw_nano/nano_balance.dart'; -import 'package:cw_nano/nano_wallet_creation_credentials.dart'; import 'package:cw_nano/nano_transaction_credentials.dart'; part 'cw_nano.dart'; @@ -44,6 +41,13 @@ abstract class Nano { DerivationType? derivationType, }); + WalletCredentials createNanoRestoreWalletFromKeysCredentials({ + required String name, + required String password, + required String seedKey, + DerivationType? derivationType, + }); + String getTransactionAddress(Object wallet, int accountIndex, int addressIndex); void onStartup(); diff --git a/lib/src/screens/restore/wallet_restore_choose_derivation.dart b/lib/src/screens/restore/wallet_restore_choose_derivation.dart index 3953f0643..08664bc28 100644 --- a/lib/src/screens/restore/wallet_restore_choose_derivation.dart +++ b/lib/src/screens/restore/wallet_restore_choose_derivation.dart @@ -38,7 +38,7 @@ class WalletRestoreChooseDerivationPage extends BasePage { } else if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return Text('No derivations available'); + return Text('Error! No derivations available!'); } else { return ListView.separated( shrinkWrap: true, diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index 732bd650e..5945e9dd5 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/src/widgets/seed_widget.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/services.dart'; @@ -14,16 +15,17 @@ import 'package:cake_wallet/entities/generate_name.dart'; class WalletRestoreFromKeysFrom extends StatefulWidget { WalletRestoreFromKeysFrom({ required this.walletRestoreViewModel, + required this.onSpendKeyChange, Key? key, - this.onHeightOrDateEntered,}) - : super(key: key); + this.onHeightOrDateEntered, + }) : super(key: key); final Function(bool)? onHeightOrDateEntered; final WalletRestoreViewModel walletRestoreViewModel; + final void Function(String)? onSpendKeyChange; @override - WalletRestoreFromKeysFromState createState() => - WalletRestoreFromKeysFromState(); + WalletRestoreFromKeysFromState createState() => WalletRestoreFromKeysFromState(); } class WalletRestoreFromKeysFromState extends State { @@ -44,6 +46,15 @@ class WalletRestoreFromKeysFromState extends State { final TextEditingController spendKeyController; final TextEditingController nameTextEditingController; + @override + void initState() { + super.initState(); + + spendKeyController.addListener(() { + widget.onSpendKeyChange?.call(spendKeyController.text); + }); + } + @override void dispose() { nameController.dispose(); @@ -74,9 +85,8 @@ class WalletRestoreFromKeysFromState extends State { setState(() { nameTextEditingController.text = rName; - nameTextEditingController.selection = - TextSelection.fromPosition(TextPosition( - offset: nameTextEditingController.text.length)); + nameTextEditingController.selection = TextSelection.fromPosition( + TextPosition(offset: nameTextEditingController.text.length)); }); }, icon: Container( @@ -89,10 +99,7 @@ class WalletRestoreFromKeysFromState extends State { height: 34, child: Image.asset( 'assets/images/refresh_icon.png', - color: Theme.of(context) - .primaryTextTheme! - .headlineMedium! - .decorationColor!, + color: Theme.of(context).primaryTextTheme!.headlineMedium!.decorationColor!, ), ), ), @@ -100,28 +107,36 @@ class WalletRestoreFromKeysFromState extends State { ], ), Container(height: 20), - BaseTextFormField( - controller: addressController, - keyboardType: TextInputType.multiline, - maxLines: null, - hintText: S.of(context).restore_address), - Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: viewKeyController, - hintText: S.of(context).restore_view_key_private, - maxLines: null)), - Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: spendKeyController, - hintText: S.of(context).restore_spend_key_private, - maxLines: null)), - BlockchainHeightWidget( - key: blockchainHeightKey, - hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, - onHeightChange: (_) => null, - onHeightOrDateEntered: widget.onHeightOrDateEntered) + if (widget.walletRestoreViewModel.hasMultipleKeys) ...[ + BaseTextFormField( + controller: addressController, + keyboardType: TextInputType.multiline, + maxLines: null, + hintText: S.of(context).restore_address), + Container( + padding: EdgeInsets.only(top: 20.0), + child: BaseTextFormField( + controller: viewKeyController, + hintText: S.of(context).restore_view_key_private, + maxLines: null)), + Container( + padding: EdgeInsets.only(top: 20.0), + child: BaseTextFormField( + controller: spendKeyController, + hintText: S.of(context).restore_spend_key_private, + maxLines: null)), + BlockchainHeightWidget( + key: blockchainHeightKey, + hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, + onHeightChange: (_) => null, + onHeightOrDateEntered: widget.onHeightOrDateEntered) + ] else + Container( + padding: EdgeInsets.only(top: 20.0), + child: BaseTextFormField( + controller: spendKeyController, + hintText: S.of(context).restore_spend_key_private, + maxLines: null)), ]), )); } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 36366b91b..881386cbb 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -73,21 +73,13 @@ class WalletRestorePage extends BasePage { } })); break; - case WalletRestoreMode.seedKey: - _pages.add(WalletRestoreFromSeedKeyForm( - displayBlockHeightSelector: walletRestoreViewModel.hasBlockchainHeightLanguageSelector, - displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector, - type: walletRestoreViewModel.type, - key: walletRestoreFromSeedKeyFormKey, - onSeedChange: (String seed) { - walletRestoreViewModel.isButtonEnabled = _isValidSeedKey(); - }, - )); - break; case WalletRestoreMode.keys: _pages.add(WalletRestoreFromKeysFrom( key: walletRestoreFromKeysFormKey, walletRestoreViewModel: walletRestoreViewModel, + onSpendKeyChange: (String seed) { + walletRestoreViewModel.isButtonEnabled = _isValidSeedKey(); + }, onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); break; default: @@ -176,14 +168,8 @@ class WalletRestorePage extends BasePage { Expanded( child: PageView.builder( onPageChanged: (page) { - if (walletRestoreViewModel.type == WalletType.nano || - walletRestoreViewModel.type == WalletType.banano) { - walletRestoreViewModel.mode = - page == 0 ? WalletRestoreMode.seed : WalletRestoreMode.seedKey; - } else { - walletRestoreViewModel.mode = - page == 0 ? WalletRestoreMode.seed : WalletRestoreMode.keys; - } + walletRestoreViewModel.mode = + page == 0 ? WalletRestoreMode.seed : WalletRestoreMode.keys; }, controller: _controller, itemCount: _pages.length, @@ -254,8 +240,7 @@ class WalletRestorePage extends BasePage { } bool _isValidSeedKey() { - final seedKey = - walletRestoreFromSeedKeyFormKey.currentState!.seedWidgetStateKey.currentState!.text; + final seedKey = walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; if (seedKey.length != 64 && seedKey.length != 128) { return false; @@ -279,16 +264,20 @@ class WalletRestorePage extends BasePage { credentials['name'] = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text; } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) { - credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; - credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; - credentials['spendKey'] = walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; - credentials['height'] = - walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height; - credentials['name'] = - walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; - } else if (walletRestoreViewModel.mode == WalletRestoreMode.seedKey) { - credentials['seedKey'] = - walletRestoreFromSeedKeyFormKey.currentState!.seedWidgetStateKey.currentState!.text; + if (!walletRestoreViewModel.hasMultipleKeys) { + credentials['name'] = + walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; + credentials['seedKey'] = walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; + } else { + credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; + credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; + credentials['spendKey'] = + walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; + credentials['height'] = + walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height; + credentials['name'] = + walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; + } } credentials['derivationType'] = this.derivationType; @@ -299,7 +288,7 @@ class WalletRestorePage extends BasePage { Future _confirmForm(BuildContext context) async { // Dismissing all visible keyboard to provide context for navigation FocusManager.instance.primaryFocus?.unfocus(); - + late BuildContext? formContext; late GlobalKey? formKey; late String name; @@ -311,10 +300,6 @@ class WalletRestorePage extends BasePage { formContext = walletRestoreFromKeysFormKey.currentContext; formKey = walletRestoreFromKeysFormKey.currentState!.formKey; name = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text; - } else if (walletRestoreViewModel.mode == WalletRestoreMode.seedKey) { - formContext = walletRestoreFromSeedKeyFormKey.currentContext; - formKey = walletRestoreFromSeedKeyFormKey.currentState!.formKey; - name = walletRestoreFromSeedKeyFormKey.currentState!.nameTextEditingController.value.text; } if (!formKey!.currentState!.validate()) { @@ -330,7 +315,8 @@ class WalletRestorePage extends BasePage { List derivationTypes = await walletRestoreViewModel.getDerivationType(_credentials()); - if (derivationTypes[0] == DerivationType.unknown || derivationTypes.length > 0) { + + if (derivationTypes[0] == DerivationType.unknown || derivationTypes.length > 1) { // push screen to choose the derivation type: var derivationType = await Navigator.of(context) .pushNamed(Routes.restoreWalletChooseDerivation, arguments: _credentials()) @@ -340,12 +326,19 @@ class WalletRestorePage extends BasePage { return; } this.derivationType = derivationType; + } else { + this.derivationType = derivationTypes[0]; } walletRestoreViewModel.state = InitialExecutionState(); // todo: re-enable - walletRestoreViewModel.create(options: _credentials()); + try { + walletRestoreViewModel.create(options: _credentials()); + } catch (e) { + print(e); + rethrow; + } } Future showNameExistsAlert(BuildContext context) { diff --git a/lib/view_model/wallet_restore_choose_derivation_view_model.dart b/lib/view_model/wallet_restore_choose_derivation_view_model.dart index 713fcda76..c99ec7218 100644 --- a/lib/view_model/wallet_restore_choose_derivation_view_model.dart +++ b/lib/view_model/wallet_restore_choose_derivation_view_model.dart @@ -33,33 +33,43 @@ abstract class WalletRestoreChooseDerivationViewModelBase with Store { Future> get derivations async { var list = []; - switch ((await getIt.get().wallet!.type)) { case WalletType.nano: - var seed = credentials['seed'] as String; - var bip39Info = - await NanoWalletService.getInfoFromSeedOrMnemonic(DerivationType.bip39, mnemonic: seed); - var standardInfo = - await NanoWalletService.getInfoFromSeedOrMnemonic(DerivationType.nano, mnemonic: seed); - - list.add(Derivation( - NanoUtil.getRawAsUsableString(standardInfo["balance"] as String, NanoUtil.rawPerNano), - standardInfo["address"] as String, - DerivationType.nano, - int.parse( - standardInfo["confirmation_height"] as String, - ), - )); - - list.add(Derivation( - NanoUtil.getRawAsUsableString(bip39Info["balance"] as String, NanoUtil.rawPerNano), - bip39Info["address"] as String, + String? mnemonic = credentials['seed'] as String?; + String? seedKey = credentials['seedKey'] as String?; + var bip39Info = await NanoWalletService.getInfoFromSeedOrMnemonic( DerivationType.bip39, - int.parse( - bip39Info["confirmation_height"] as String, - ), - )); + mnemonic: mnemonic, + seedKey: seedKey, + ); + var standardInfo = await NanoWalletService.getInfoFromSeedOrMnemonic( + DerivationType.nano, + mnemonic: mnemonic, + seedKey: seedKey, + ); + if (standardInfo["address"] != null) { + list.add(Derivation( + NanoUtil.getRawAsUsableString(standardInfo["balance"] as String, NanoUtil.rawPerNano), + standardInfo["address"] as String, + DerivationType.nano, + int.parse( + standardInfo["confirmation_height"] as String, + ), + )); + } + + if (bip39Info["balance"] != null) { + list.add(Derivation( + NanoUtil.getRawAsUsableString(bip39Info["balance"] as String, NanoUtil.rawPerNano), + bip39Info["address"] as String, + DerivationType.bip39, + int.tryParse( + bip39Info["confirmation_height"] as String? ?? "", + ) ?? + 0, + )); + } break; default: diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index c57b3d1df..49f698577 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -28,10 +28,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { : availableModes = (type == WalletType.monero || type == WalletType.haven) ? [WalletRestoreMode.seed, WalletRestoreMode.keys, WalletRestoreMode.txids] : (type == WalletType.nano || type == WalletType.banano) - ? [WalletRestoreMode.seed, WalletRestoreMode.seedKey] + ? [WalletRestoreMode.seed, WalletRestoreMode.keys] : [WalletRestoreMode.seed], hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven, + hasMultipleKeys = type != WalletType.nano || type == WalletType.banano, isButtonEnabled = false, mode = WalletRestoreMode.seed, super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) { @@ -46,6 +47,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { final List availableModes; final bool hasSeedLanguageSelector; final bool hasBlockchainHeightLanguageSelector; + final bool hasMultipleKeys; @observable WalletRestoreMode mode; @@ -61,7 +63,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { if (mode == WalletRestoreMode.seed) { final seed = options['seed'] as String; - switch (type) { case WalletType.monero: return monero!.createMoneroRestoreWalletFromSeedCredentials( @@ -83,7 +84,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { name: name, mnemonic: seed, password: password, - derivationType: null, + derivationType: options["derivationType"] as DerivationType, ); default: break; @@ -91,30 +92,41 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { } if (mode == WalletRestoreMode.keys) { - final viewKey = options['viewKey'] as String; - final spendKey = options['spendKey'] as String; - final address = options['address'] as String; + if (hasMultipleKeys) { + final viewKey = options['viewKey'] as String; + final spendKey = options['spendKey'] as String; + final address = options['address'] as String; - if (type == WalletType.monero) { - return monero!.createMoneroRestoreWalletFromKeysCredentials( - name: name, - height: height, - spendKey: spendKey, - viewKey: viewKey, - address: address, - password: password, - language: 'English'); - } + if (type == WalletType.monero) { + return monero!.createMoneroRestoreWalletFromKeysCredentials( + name: name, + height: height, + spendKey: spendKey, + viewKey: viewKey, + address: address, + password: password, + language: 'English'); + } - if (type == WalletType.haven) { - return haven!.createHavenRestoreWalletFromKeysCredentials( - name: name, - height: height, - spendKey: spendKey, - viewKey: viewKey, - address: address, - password: password, - language: 'English'); + if (type == WalletType.haven) { + return haven!.createHavenRestoreWalletFromKeysCredentials( + name: name, + height: height, + spendKey: spendKey, + viewKey: viewKey, + address: address, + password: password, + language: 'English'); + } + } else { + if (type == WalletType.nano) { + return nano!.createNanoRestoreWalletFromKeysCredentials( + name: name, + password: password, + seedKey: options['seedKey'] as String, + derivationType: options["derivationType"] as DerivationType, + ); + } } } @@ -134,7 +146,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { // return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( // name: name, mnemonic: seed, password: password); case WalletType.nano: - return await NanoWalletService.compareDerivationMethods(mnemonic: mnemonic, seedKey: seedKey); + return await NanoWalletService.compareDerivationMethods( + mnemonic: mnemonic, seedKey: seedKey); default: break; } diff --git a/tool/configure.dart b/tool/configure.dart index 19f75e45e..29140897c 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -592,9 +592,6 @@ import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/output_info.dart'; import 'package:hive/hive.dart'; -import 'package:cw_nano/api/wallet.dart' as nano_wallet_api; -import 'package:cw_nano/nano_balance.dart'; -import 'package:cw_nano/nano_wallet_creation_credentials.dart'; import 'package:cw_nano/nano_transaction_credentials.dart'; """; const nanoCwPart = "part 'cw_nano.dart';"; @@ -622,6 +619,13 @@ abstract class Nano { DerivationType? derivationType, }); + WalletCredentials createNanoRestoreWalletFromKeysCredentials({ + required String name, + required String password, + required String seedKey, + DerivationType? derivationType, + }); + String getTransactionAddress(Object wallet, int accountIndex, int addressIndex); void onStartup();