lots of fixes + finish up seed derivation

This commit is contained in:
fosse 2023-08-16 10:24:20 -04:00
parent 2fb43e8b28
commit cd7f636558
9 changed files with 209 additions and 144 deletions

View file

@ -232,25 +232,26 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
@override
Future<NanoWallet> 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

View file

@ -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();

View file

@ -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();

View file

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

View file

@ -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<WalletRestoreFromKeysFrom> {
@ -44,6 +46,15 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
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<WalletRestoreFromKeysFrom> {
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<WalletRestoreFromKeysFrom> {
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<WalletRestoreFromKeysFrom> {
],
),
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)),
]),
));
}

View file

@ -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<void> _confirmForm(BuildContext context) async {
// Dismissing all visible keyboard to provide context for navigation
FocusManager.instance.primaryFocus?.unfocus();
late BuildContext? formContext;
late GlobalKey<FormState>? 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<DerivationType> 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<void> showNameExistsAlert(BuildContext context) {

View file

@ -33,33 +33,43 @@ abstract class WalletRestoreChooseDerivationViewModelBase with Store {
Future<List<Derivation>> get derivations async {
var list = <Derivation>[];
switch ((await getIt.get<AppStore>().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:

View file

@ -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<WalletRestoreMode> 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;
}

View file

@ -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();