Skip to content

Keystone integration #1872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/images/hardware_wallet/keystone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/keystone_scan_title.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions cw_core/lib/wallet_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans

bool get isHardwareWallet => walletInfo.isHardwareWallet;

bool get isLedger => walletInfo.isLedger;

Future<void> connectToNode({required Node node});

// there is a default definition here because only coins with a pow node (nano based) need to override this
Expand Down
8 changes: 8 additions & 0 deletions cw_core/lib/wallet_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ enum DerivationType {
enum HardwareWalletType {
@HiveField(0)
ledger,
@HiveField(1)
keystone,
}

@HiveType(typeId: DerivationInfo.typeId)
Expand Down Expand Up @@ -217,6 +219,12 @@ class WalletInfo extends HiveObject {

bool get isHardwareWallet => hardwareWalletType != null;

bool get isConnectableHardwareWallet => isLedger;

bool get isLedger => hardwareWalletType == HardwareWalletType.ledger;

bool get isKeystone => hardwareWalletType == HardwareWalletType.keystone;

DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);

Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
Expand Down
9 changes: 5 additions & 4 deletions cw_monero/lib/monero_wallet_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
required this.address,
required this.viewKey,
required this.spendKey,
int height = 0})
: super(name: name, password: password, height: height);
int height = 0,
HardwareWalletType? hardwareWalletType})
: super(name: name, password: password, height: height, hardwareWalletType: hardwareWalletType);

final String language;
final String address;
Expand Down Expand Up @@ -156,7 +157,7 @@ class MoneroWalletService extends WalletService<
password: password);
final isValid = wallet.walletAddresses.validate();

if (wallet.isHardwareWallet) {
if (wallet.isLedger) {
wallet.setLedgerConnection(gLedger!);
gLedger = null;
}
Expand Down Expand Up @@ -446,7 +447,7 @@ class MoneroWalletService extends WalletService<
return walletInfoSource.values
.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(name, getType()))
?.isHardwareWallet ??
?.isConnectableHardwareWallet ??
false;
}
}
2 changes: 1 addition & 1 deletion lib/buy/dfx/dfx_buy_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ class DFXBuyProvider extends BuyProvider {
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
if (wallet.isLedger) {
if (!ledgerVM!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
Expand Down
2 changes: 1 addition & 1 deletion lib/buy/robinhood/robinhood_buy_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class RobinhoodBuyProvider extends BuyProvider {
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
if (wallet.isLedger) {
if (!ledgerVM!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
Expand Down
10 changes: 7 additions & 3 deletions lib/di.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import 'package:cake_wallet/entities/wallet_manager.dart';
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_keystone_private_mode_page.dart';
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
Expand Down Expand Up @@ -747,7 +748,7 @@ Future<void> setup({
getIt.get<BalanceViewModel>(),
getIt.get<ContactListViewModel>(),
_transactionDescriptionBox,
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
getIt.get<AppStore>().wallet!.isLedger ? getIt.get<LedgerViewModel>() : null,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
),
Expand Down Expand Up @@ -998,12 +999,12 @@ Future<void> setup({
getIt.registerFactory<RobinhoodBuyProvider>(() => RobinhoodBuyProvider(
wallet: getIt.get<AppStore>().wallet!,
ledgerVM:
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
getIt.get<AppStore>().wallet!.isLedger ? getIt.get<LedgerViewModel>() : null));

getIt.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(
wallet: getIt.get<AppStore>().wallet!,
ledgerVM:
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
getIt.get<AppStore>().wallet!.isLedger ? getIt.get<LedgerViewModel>() : null));

getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
Expand Down Expand Up @@ -1206,6 +1207,9 @@ Future<void> setup({

getIt.registerFactory(() => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));

getIt.registerFactoryParam<RestoreFromKeystonePrivateModePage, String, void>(
(String code, _) => RestoreFromKeystonePrivateModePage(code));

getIt.registerFactoryParam<TradeDetailsPage, Trade, void>(
(Trade trade, _) => TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));

Expand Down
6 changes: 4 additions & 2 deletions lib/monero/cw_monero.dart
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,17 @@ class CWMonero extends Monero {
required String address,
required String password,
required String language,
required int height}) =>
required int height,
HardwareWalletType? hardwareWalletType}) =>
MoneroRestoreWalletFromKeysCredentials(
name: name,
spendKey: spendKey,
viewKey: viewKey,
address: address,
password: password,
language: language,
height: height);
height: height,
hardwareWalletType: hardwareWalletType);

@override
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({
Expand Down
6 changes: 6 additions & 0 deletions lib/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
import 'package:cake_wallet/src/screens/receive/receive_page.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_from_keystone_private_mode_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
Expand Down Expand Up @@ -608,6 +609,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<RestoreFromBackupPage>());

case Routes.restoreFromKeystonePrivateMode:
final args = settings.arguments as String;
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<RestoreFromKeystonePrivateModePage>(param1: args));

case Routes.support:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SupportPage>());
Expand Down
1 change: 1 addition & 0 deletions lib/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class Routes {
static const backup = '/backup';
static const editBackupPassword = '/edit_backup_passowrd';
static const restoreFromBackup = '/restore_from_backup';
static const restoreFromKeystonePrivateMode = '/restore_from_keystone_private_mode';
static const support = '/support';
static const supportLiveChat = '/support/live_chat';
static const supportOtherLinks = '/support/other';
Expand Down
4 changes: 3 additions & 1 deletion lib/src/screens/pin_code/pin_code_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ class PinCodeWidget extends StatefulWidget {
required this.onChangedPin,
required this.hasLengthSwitcher,
this.onChangedPinLength,
this.title,
}) : super(key: key);

final void Function(String pin, PinCodeState state) onFullPin;
final void Function(String pin) onChangedPin;
final void Function(int length)? onChangedPinLength;
final String? title;
final bool hasLengthSwitcher;
final int initialPinLength;

Expand Down Expand Up @@ -52,7 +54,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
super.initState();
pinLength = widget.initialPinLength;
pin = '';
title = S.current.enter_your_pin;
title = widget.title ?? S.current.enter_your_pin;
_aspectRatio = 0;
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:eth_sig_util/util/keccak.dart';
import 'package:flutter/material.dart';
import 'package:hex/hex.dart' as Hex;
import 'package:cryptography/cryptography.dart';

class RestoreFromKeystonePrivateModePage extends BasePage {
@override
String get title => '';

RestoreFromKeystonePrivateModePage(this.code)
: pinCodeStateKey = GlobalKey<PinCodeState>();

final GlobalKey<PinCodeState> pinCodeStateKey;
final String code;
static const sixPinLength = 6;

Future<SecretKey> _deriveKeyFromPin(Uint8List pin) async {
final hash = keccak256(pin);
return SecretKey(hash.buffer.asUint8List());
}

Future<String> _decryptData(Uint8List cipherText, SecretKey secretKey) async {
final algorithm = Chacha20(macAlgorithm: MacAlgorithm.empty);
final secretBox = SecretBox(
cipherText,
nonce: Uint8List(12),
mac: Mac.empty,
);
final text = await algorithm.decrypt(
secretBox,
secretKey: secretKey,
);

return utf8.decode(text);
}

@override
Widget body(BuildContext context) {
return PinCodeWidget(
key: pinCodeStateKey,
title: S.of(context).restore_from_private_mode_title,
onFullPin: (pin, state) async {
try {
Uint8List pinDigits = Uint8List.fromList(
pin.split('').map((char) => int.parse(char)).toList());
final secretKey = await _deriveKeyFromPin(pinDigits);

final restoreJson = json.decode(code);

restoreJson['primaryAddress'] = await _decryptData(
Uint8List.fromList(
Hex.HEX.decode(restoreJson['primaryAddress'] as String)),
secretKey);

restoreJson['privateViewKey'] = await _decryptData(
Uint8List.fromList(
Hex.HEX.decode(restoreJson['privateViewKey'] as String)),
secretKey);

final res = json.encode(restoreJson);
Navigator.of(context).pop(res);
} catch (_) {
pinCodeStateKey.currentState?.reset();
showBar<void>(
context,
S
.of(context)
.error_text_failed_to_resotre_from_keystone_private_mode);
}
},
initialPinLength: sixPinLength,
onChangedPin: (String pin) {},
hasLengthSwitcher: false,
);
}
}
10 changes: 10 additions & 0 deletions lib/src/screens/restore/restore_options_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor;
final imageColor = widget.themeType == ThemeType.bright ? brightImageColor : mainImageColor;
final imageLedger = Image.asset('assets/images/hardware_wallet/ledger_nano_x.png', width: 40, color: imageColor);
final imageKeystone = Image.asset('assets/images/hardware_wallet/keystone.png', width: 40, color: imageColor);
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);

Expand Down Expand Up @@ -112,6 +113,15 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
description: S.of(context).restore_description_from_hardware_wallet,
),
),
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_keystone_button_key'),
onPressed: () => _onScanQRCode(context),
image: imageKeystone,
title: S.of(context).restore_title_from_keystone,
description: S.of(context).restore_description_from_keystone),
),
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
Expand Down
2 changes: 1 addition & 1 deletion lib/src/screens/send/send_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ class SendPage extends BasePage {
return;
}

if (sendViewModel.wallet.isHardwareWallet) {
if (sendViewModel.wallet.isLedger) {
if (!sendViewModel.ledgerViewModel!.isConnected) {
await Navigator.of(context).pushNamed(
Routes.connectDevices,
Expand Down
15 changes: 14 additions & 1 deletion lib/src/screens/ur/animated_ur_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

Expand Down Expand Up @@ -43,13 +44,25 @@ class AnimatedURPage extends BasePage {
return first.split('/')[0];
}

bool get isKeystone =>
animatedURmodel.wallet.walletInfo.hardwareWalletType ==
HardwareWalletType.keystone;

@override
Widget body(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (isKeystone)
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Image.asset('assets/images/keystone_scan_title.png',
width: 160, height: 36),
),
Padding(
padding: const EdgeInsets.only(top: 64.0),
padding: isKeystone
? const EdgeInsets.symmetric(horizontal: 16.0)
: const EdgeInsets.symmetric(horizontal: 64.0),
child: URQR(
frames: urQr.trim().split("\n"),
),
Expand Down
3 changes: 2 additions & 1 deletion lib/view_model/restore/restore_from_qr_vm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
address: restoreWallet.address ?? '',
viewKey: restoreWallet.viewKey ?? '',
spendKey: restoreWallet.spendKey ?? '',
height: restoreWallet.height ?? 0);
height: restoreWallet.height ?? 0,
hardwareWalletType: restoreWallet.source == "Keystone" ? HardwareWalletType.keystone : null);
case WalletType.wownero:
return wownero!.createWowneroRestoreWalletFromKeysCredentials(
name: name,
Expand Down
6 changes: 5 additions & 1 deletion lib/view_model/restore/restore_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class RestoredWallet {
this.txDescription,
this.recipientName,
this.height,
this.privateKey});
this.privateKey,
this.source});

final WalletRestoreMode restoreMode;
final WalletType type;
Expand All @@ -32,6 +33,7 @@ class RestoredWallet {
final String? recipientName;
final int? height;
final String? privateKey;
final String? source;

factory RestoredWallet.fromKey(Map<String, dynamic> json) {
try {
Expand All @@ -40,6 +42,7 @@ class RestoredWallet {
json['address'] = codeParsed["primaryAddress"];
json['view_key'] = codeParsed["privateViewKey"];
json['height'] = codeParsed["restoreHeight"].toString();
json['source'] = codeParsed["source"] ?? '';
}
} catch (e) {
// fine, we don't care, it is only for monero anyway
Expand All @@ -53,6 +56,7 @@ class RestoredWallet {
viewKey: json['view_key'] as String?,
height: height != null ? int.tryParse(height)??0 : 0,
privateKey: json['private_key'] as String?,
source: json['source'] as String?,
);
}

Expand Down
Loading
Loading