Skip to content
Draft
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
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 @@ -62,6 +62,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans

bool get isHardwareWallet => walletInfo.isHardwareWallet;

bool get isLedger => walletInfo.isLedger;

bool get hasRescan => false;

Future<void> connectToNode({required Node node});
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 @@ -226,6 +228,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 @@ -79,8 +79,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 @@ -189,7 +190,7 @@ class MoneroWalletService extends WalletService<
unspentCoinsInfo: unspentCoinsInfoSource,
password: password);

if (wallet.isHardwareWallet) {
if (wallet.isLedger) {
wallet.setLedgerConnection(gLedger!);
gLedger = null;
}
Expand Down Expand Up @@ -548,7 +549,7 @@ class MoneroWalletService extends WalletService<
return walletInfoSource.values
.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(name, getType()))
?.isHardwareWallet ??
?.isConnectableHardwareWallet ??
false;
}
}
Expand Down
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 @@ -335,7 +335,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 @@ -157,7 +157,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 @@ -66,6 +66,7 @@ import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
import 'package:cake_wallet/src/screens/payjoin_details/payjoin_details_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 @@ -816,7 +817,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.nonMweb,
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
getIt.get<FeesViewModel>(),
Expand Down Expand Up @@ -1079,12 +1080,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(
appStore: getIt.get<AppStore>(),
Expand Down Expand Up @@ -1334,6 +1335,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 @@ -73,6 +73,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 @@ -673,6 +674,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 handleRouteWithPlatformAwareness(
(context) => 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 @@ -59,6 +59,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 @@ -13,11 +13,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 @@ -51,7 +53,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;
_focusNode = FocusNode();
WidgetsBinding.instance.addPostFrameCallback((_) {
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,
);
}
}
9 changes: 9 additions & 0 deletions lib/src/screens/restore/restore_options_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
),
),
if (DeviceInfo.instance.isMobile)
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_keystone_button_key'),
onPressed: () => _onScanQRCode(context),
image: imageRestoreQR,
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 @@ -435,7 +435,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,
arguments: ConnectDevicePageParams(
Expand Down
33 changes: 24 additions & 9 deletions lib/src/screens/ur/animated_ur_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/animated_ur_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';

class AnimatedURPage extends BasePage {
Expand Down Expand Up @@ -51,11 +52,21 @@ 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: 32.0),
child: Image.asset('assets/images/hardware_wallet/keystone_scan_title.png',
width: 160, height: 36),
),
Padding(
padding: const EdgeInsets.all(32.0),
child: URQR(
Expand All @@ -66,15 +77,19 @@ class AnimatedURPage extends BasePage {
if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header]
.contains(urQrType)) ...{
Spacer(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: double.maxFinite,
child: PrimaryButton(
onPressed: () => _continue(context),
text: "Scan QR Code",
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
SafeArea(
top: false,
minimum: const EdgeInsets.symmetric(horizontal: 32.0),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: double.maxFinite,
child: PrimaryButton(
onPressed: () => _continue(context),
text: "Scan QR Code",
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
)
),
),
),
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 @@ -54,6 +57,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