sign working for a few wallet types

This commit is contained in:
Matthew Fosse 2024-04-09 11:52:52 -07:00
parent d4e3e505cf
commit 82dc036a18
11 changed files with 355 additions and 131 deletions

View file

@ -1221,7 +1221,7 @@ abstract class ElectrumWalletBase
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
@override @override
String signMessage(String message, {String? address = null}) { Future<String> signMessage(String message, {String? address = null}) async {
final index = address != null final index = address != null
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
: null; : null;

View file

@ -166,7 +166,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
} }
@override @override
String signMessage(String message, {String? address = null}) { Future<String> signMessage(String message, {String? address = null}) async {
final index = address != null final index = address != null
? walletAddresses.allAddresses ? walletAddresses.allAddresses
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))

View file

@ -88,7 +88,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
Future<void> renameWalletFiles(String newWalletName); Future<void> renameWalletFiles(String newWalletName);
String signMessage(String message, {String? address = null}) => throw UnimplementedError(); Future<String> signMessage(String message, {String? address = null}) => throw UnimplementedError();
bool? isTestnet; bool? isTestnet;
} }

View file

@ -524,7 +524,7 @@ abstract class EVMChainWalletBase
} }
@override @override
String signMessage(String message, {String? address}) => Future<String> signMessage(String message, {String? address}) async =>
bytesToHex(_evmChainPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); bytesToHex(_evmChainPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
Web3Client? getWeb3Client() => _client.getWeb3Client(); Web3Client? getWeb3Client() => _client.getWeb3Client();

View file

@ -686,7 +686,7 @@ abstract class MoneroWalletBase
void setExceptionHandler(void Function(FlutterErrorDetails) e) => onError = e; void setExceptionHandler(void Function(FlutterErrorDetails) e) => onError = e;
@override @override
String signMessage(String message, {String? address}) { Future<String> signMessage(String message, {String? address}) async {
final useAddress = address ?? ""; final useAddress = address ?? "";
return monero_wallet.signMessage(message, address: useAddress); return monero_wallet.signMessage(message, address: useAddress);
} }

View file

@ -522,5 +522,10 @@ abstract class SolanaWalletBase
return hex; return hex;
} }
@override
Future<String> signMessage(String message, {String? address}) async {
return signSolanaMessage(message);
}
SolanaClient? get solanaClient => _client.getSolanaClient; SolanaClient? get solanaClient => _client.getSolanaClient;
} }

View file

@ -1,79 +1,37 @@
import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sign_form.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/verify_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class SignPage extends BasePage { class SignPage extends BasePage {
SignPage(this.signViewModel) SignPage(this.signViewModel)
: walletRestoreFromSeedFormKey = GlobalKey<WalletRestoreFromSeedFormState>(), : signFormKey = GlobalKey<SignFormState>(),
walletRestoreFromKeysFormKey = GlobalKey<WalletRestoreFromKeysFromState>(), verifyFormKey = GlobalKey<VerifyFormState>(),
_pages = [], _pages = [],
_blockHeightFocusNode = FocusNode(),
_controller = PageController(initialPage: 0) { _controller = PageController(initialPage: 0) {
// walletRestoreViewModel.availableModes.forEach((mode) { _pages.add(SignForm(
// switch (mode) { key: signFormKey,
// case WalletRestoreMode.seed: type: signViewModel.wallet.type,
// _pages.add(WalletRestoreFromSeedForm( includeAddress: signViewModel.signIncludesAddress,
// seedTypeViewModel: seedTypeViewModel, ));
// displayBlockHeightSelector: _pages.add(VerifyForm(
// walletRestoreViewModel.hasBlockchainHeightLanguageSelector, key: verifyFormKey,
// displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector, type: signViewModel.wallet.type,
// type: walletRestoreViewModel.type, ));
// key: walletRestoreFromSeedFormKey,
// blockHeightFocusNode: _blockHeightFocusNode,
// onHeightOrDateEntered: (value) {
// },
// onSeedChange: (String seed) {
// final isPolyseed =
// walletRestoreViewModel.type == WalletType.monero && Polyseed.isValidSeed(seed);
// _validateOnChange(isPolyseed: isPolyseed);
// },
// onLanguageChange: (String language) {
// final isPolyseed = language.startsWith("POLYSEED_");
// _validateOnChange(isPolyseed: isPolyseed);
// }));
// break;
// case WalletRestoreMode.keys:
// _pages.add(WalletRestoreFromKeysFrom(
// key: walletRestoreFromKeysFormKey,
// walletRestoreViewModel: walletRestoreViewModel,
// onPrivateKeyChange: (String seed) {
// if (walletRestoreViewModel.type == WalletType.nano ||
// walletRestoreViewModel.type == WalletType.banano) {
// }
// },
// displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
// onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
// break;
// default:
// break;
// }
// });
} }
@override @override
@ -91,29 +49,45 @@ class SignPage extends BasePage {
final SignViewModel signViewModel; final SignViewModel signViewModel;
final PageController _controller; final PageController _controller;
final List<Widget> _pages; final List<Widget> _pages;
final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey; final GlobalKey<SignFormState> signFormKey;
final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey; final GlobalKey<VerifyFormState> verifyFormKey;
final FocusNode _blockHeightFocusNode;
DerivationType derivationType = DerivationType.unknown;
String? derivationPath = null;
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
// reaction((_) => walletRestoreViewModel.state, (ExecutionState state) { reaction((_) => signViewModel.state, (ExecutionState state) {
// if (state is FailureState) { if (state is FailureState) {
// WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
// showPopUp<void>( showPopUp<void>(
// context: context, context: context,
// builder: (_) { builder: (_) {
// return AlertWithOneAction( return AlertWithOneAction(
// alertTitle: S.current.new_wallet, alertTitle: S.current.error,
// alertContent: state.error, alertContent: state.error,
// buttonText: S.of(context).ok, buttonText: S.of(context).ok,
// buttonAction: () => Navigator.of(context).pop()); buttonAction: () => Navigator.of(context).pop(),
// }); );
// }); });
// } });
// }); }
if (state is ExecutedSuccessfullyState) {
if (signViewModel.isSigning) {
signFormKey.currentState!.signatureController.text = state.payload as String;
} else {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (_) {
return AlertWithOneAction(
alertTitle: S.current.successful,
alertContent: "T: The message was successfully verified",
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
});
});
}
}
});
// reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) { // reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) {
// walletRestoreViewModel.isButtonEnabled = false; // walletRestoreViewModel.isButtonEnabled = false;
@ -138,7 +112,7 @@ class SignPage extends BasePage {
nextFocus: false, nextFocus: false,
actions: [ actions: [
KeyboardActionsItem( KeyboardActionsItem(
focusNode: _blockHeightFocusNode, focusNode: FocusNode(),
toolbarButtons: [(_) => KeyboardDoneButton()], toolbarButtons: [(_) => KeyboardDoneButton()],
) )
], ],
@ -156,8 +130,7 @@ class SignPage extends BasePage {
Expanded( Expanded(
child: PageView.builder( child: PageView.builder(
onPageChanged: (page) { onPageChanged: (page) {
// walletRestoreViewModel.mode = signViewModel.isSigning = page == 0;
// page == 0 ? WalletRestoreMode.seed : WalletRestoreMode.keys;
}, },
controller: _controller, controller: _controller,
itemCount: _pages.length, itemCount: _pages.length,
@ -190,30 +163,30 @@ class SignPage extends BasePage {
onPressed: () async { onPressed: () async {
await _confirmForm(context); await _confirmForm(context);
}, },
text: S.of(context).restore_recover, text: signViewModel.isSigning ? "T: Sign Message" : "T: Verify Message",
color: Theme.of(context) color: Theme.of(context)
.extension<WalletListTheme>()! .extension<WalletListTheme>()!
.createNewWalletButtonBackgroundColor, .createNewWalletButtonBackgroundColor,
textColor: Theme.of(context) textColor: Theme.of(context)
.extension<WalletListTheme>()! .extension<WalletListTheme>()!
.restoreWalletButtonTextColor, .restoreWalletButtonTextColor,
// isLoading: walletRestoreViewModel.state is IsExecutingState, isLoading: signViewModel.state is IsExecutingState,
// isDisabled: !walletRestoreViewModel.isButtonEnabled, // isDisabled: !signViewModel.isButtonEnabled,
); );
}, },
), ),
const SizedBox(height: 25), // const SizedBox(height: 25),
GestureDetector( // GestureDetector(
onTap: () { // onTap: () {
// Navigator.of(context) // // Navigator.of(context)
// .pushNamed(Routes.advancedPrivacySettings, arguments: { // // .pushNamed(Routes.advancedPrivacySettings, arguments: {
// 'type': walletRestoreViewModel.type, // // 'type': walletRestoreViewModel.type,
// 'useTestnet': walletRestoreViewModel.useTestnet, // // 'useTestnet': walletRestoreViewModel.useTestnet,
// 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet // // 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
// }); // // });
}, // },
child: Text(S.of(context).advanced_settings), // child: Text(S.of(context).advanced_settings),
), // ),
], ],
), ),
) )
@ -225,39 +198,28 @@ class SignPage extends BasePage {
); );
} }
void _validateOnChange({bool isPolyseed = false}) {}
Future<void> _confirmForm(BuildContext context) async { Future<void> _confirmForm(BuildContext context) async {
// // Dismissing all visible keyboard to provide context for navigation FocusManager.instance.primaryFocus?.unfocus();
// FocusManager.instance.primaryFocus?.unfocus();
// late BuildContext? formContext;
// late GlobalKey<FormState>? formKey;
// late String name;
// if (walletRestoreViewModel.mode == WalletRestoreMode.seed) {
// formContext = walletRestoreFromSeedFormKey.currentContext;
// formKey = walletRestoreFromSeedFormKey.currentState!.formKey;
// name = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text;
// } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
// formContext = walletRestoreFromKeysFormKey.currentContext;
// formKey = walletRestoreFromKeysFormKey.currentState!.formKey;
// name = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text;
// }
// if (!formKey!.currentState!.validate()) { // if (!formKey!.currentState!.validate()) {
// return; // return;
// } // }
}
Future<void> showNameExistsAlert(BuildContext context) { if (signViewModel.isSigning) {
return showPopUp<void>( String message = signFormKey.currentState!.messageController.text;
context: context, String? address;
builder: (_) { if (signViewModel.signIncludesAddress) {
return AlertWithOneAction( address = signFormKey.currentState!.addressController.text;
alertTitle: '', }
alertContent: S.of(context).wallet_name_exists, await signViewModel.sign(message, address: address);
buttonText: S.of(context).ok, } else {
buttonAction: () => Navigator.of(context).pop()); String message = verifyFormKey.currentState!.messageController.text;
}); String signature = verifyFormKey.currentState!.signatureController.text;
String? address;
// if (signViewModel.signIncludesAddress) {
// address = signFormKey.currentState!.addressController.text;
// }
await signViewModel.verify(message, signature, address: address);
}
} }
} }

View file

@ -0,0 +1,116 @@
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/seed_language_picker.dart';
import 'package:cake_wallet/src/widgets/seed_widget.dart';
import 'package:cake_wallet/themes/extensions/address_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
class SignForm extends StatefulWidget {
SignForm({
Key? key,
required this.type,
required this.includeAddress,
}) : super(key: key);
final WalletType type;
final bool includeAddress;
@override
SignFormState createState() => SignFormState();
}
class SignFormState extends State<SignForm> {
SignFormState()
: formKey = GlobalKey<FormState>(),
messageController = TextEditingController(),
addressController = TextEditingController(),
signatureController = TextEditingController();
final TextEditingController messageController;
final TextEditingController addressController;
final TextEditingController signatureController;
final GlobalKey<FormState> formKey;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: Column(
children: [
Form(
key: formKey,
child: Column(
children: [
AddressTextField(
controller: messageController,
placeholder: S.current.message,
options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).hintColor,
),
if (widget.includeAddress) ...[
const SizedBox(height: 20),
AddressTextField(
controller: addressController,
options: [AddressTextFieldOption.paste, AddressTextFieldOption.addressBook],
buttonColor: Theme.of(context).hintColor,
onSelectedContact: (contact) {
addressController.text = contact.address;
},
selectedCurrency: walletTypeToCryptoCurrency(widget.type),
),
],
],
)),
const SizedBox(height: 20),
GestureDetector(
onTap: () async {
final text = signatureController.text;
Clipboard.setData(ClipboardData(text: text));
showBar<void>(context, S.of(context).transaction_details_copied(text));
},
child: BaseTextFormField(
enabled: false,
controller: signatureController,
hintText: "T: Signature",
),
),
],
),
);
}
Widget get expandIcon => Container(
padding: EdgeInsets.all(18),
width: 24,
height: 24,
child: Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
height: 8,
color: Theme.of(context).hintColor,
),
);
}

View file

@ -0,0 +1,109 @@
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/seed_language_picker.dart';
import 'package:cake_wallet/src/widgets/seed_widget.dart';
import 'package:cake_wallet/themes/extensions/address_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
class VerifyForm extends StatefulWidget {
VerifyForm({
Key? key,
required this.type,
}) : super(key: key);
final WalletType type;
@override
VerifyFormState createState() => VerifyFormState();
}
class VerifyFormState extends State<VerifyForm> {
VerifyFormState()
: formKey = GlobalKey<FormState>(),
messageController = TextEditingController(),
addressController = TextEditingController(),
signatureController = TextEditingController();
final TextEditingController messageController;
final TextEditingController addressController;
final TextEditingController signatureController;
final GlobalKey<FormState> formKey;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: Form(
key: formKey,
child: Column(
children: [
AddressTextField(
controller: messageController,
placeholder: S.current.message,
options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).hintColor,
),
const SizedBox(height: 20),
AddressTextField(
controller: addressController,
options: [AddressTextFieldOption.paste, AddressTextFieldOption.addressBook],
buttonColor: Theme.of(context).hintColor,
onSelectedContact: (contact) {
addressController.text = contact.address;
},
selectedCurrency: walletTypeToCryptoCurrency(widget.type),
),
const SizedBox(height: 20),
GestureDetector(
onTap: () async {
final text = signatureController.text;
Clipboard.setData(ClipboardData(text: text));
showBar<void>(context, S.of(context).transaction_details_copied(text));
},
child: BaseTextFormField(
enabled: false,
controller: signatureController,
hintText: "T: Signature",
),
),
],
),
),
);
}
Widget get expandIcon => Container(
padding: EdgeInsets.all(18),
width: 24,
height: 24,
child: Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
height: 8,
color: Theme.of(context).hintColor,
),
);
}

View file

@ -371,7 +371,7 @@ abstract class DashboardViewModelBase with Store {
bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano; bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
@computed @computed
bool get hasSignMessages => [WalletType.monero].contains(wallet.type); bool get hasSignMessages => [WalletType.monero, WalletType.solana].contains(wallet.type);
Future<void> reconnect() async { Future<void> reconnect() async {
final node = appStore.settingsStore.getCurrentNode(wallet.type); final node = appStore.settingsStore.getCurrentNode(wallet.type);

View file

@ -1,5 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/execution_state.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -9,7 +8,40 @@ part 'sign_view_model.g.dart';
class SignViewModel = SignViewModelBase with _$SignViewModel; class SignViewModel = SignViewModelBase with _$SignViewModel;
abstract class SignViewModelBase with Store { abstract class SignViewModelBase with Store {
SignViewModelBase(this._wallet) {} SignViewModelBase(this.wallet) : state = InitialExecutionState();
final WalletBase _wallet; final WalletBase wallet;
@observable
ExecutionState state;
@observable
bool isSigning = true;
bool get signIncludesAddress => [
WalletType.monero,
WalletType.haven,
].contains(wallet.type);
@action
Future<void> sign(String message, {String? address}) async {
state = IsExecutingState();
try {
final signature = await wallet.signMessage(message, address: address);
state = ExecutedSuccessfullyState(payload: signature);
} catch (e) {
state = FailureState(e.toString());
}
}
@action
Future<void> verify(String message, String signature, {String? address}) async {
// state = IsExecutingState();
// try {
// final signature = await wallet.verifyMessage(message, address: address);
// state = ExecutedSuccessfullyState(payload: signature);
// } catch (e) {
// state = FailureState(e.toString());
// }
}
} }