From 8b56c52dc6248eca62b7833471e67718ca8933da Mon Sep 17 00:00:00 2001 From: cyan Date: Sat, 1 Feb 2025 02:43:01 +0100 Subject: [PATCH] CW-711 passphrase for XMR/WOWcreation (#1992) * add monero passphrase add wownero passphrase add passphrase to seed screen * obscure passphrase by default disable passphrase create for zano * Update lib/view_model/wallet_keys_view_model.dart [skip ci] * Update lib/src/screens/wallet_keys/wallet_keys_page.dart [skip ci] * Update lib/view_model/advanced_privacy_settings_view_model.dart * dynamic passphrase icon * fix polyseed not being encrypted by passphrase --------- Co-authored-by: Omar Hatem --- cw_monero/lib/api/wallet.dart | 7 +- cw_monero/lib/api/wallet_manager.dart | 7 +- cw_monero/lib/monero_wallet.dart | 2 +- cw_monero/lib/monero_wallet_service.dart | 11 +++- cw_wownero/lib/api/wallet.dart | 2 +- cw_wownero/lib/api/wallet_manager.dart | 8 ++- cw_wownero/lib/wownero_wallet.dart | 5 ++ cw_wownero/lib/wownero_wallet_service.dart | 9 ++- lib/monero/cw_monero.dart | 3 +- .../screens/wallet_keys/wallet_keys_page.dart | 64 +++++++++++++++++++ .../advanced_privacy_settings_view_model.dart | 4 +- lib/view_model/wallet_keys_view_model.dart | 18 ++++++ lib/view_model/wallet_new_vm.dart | 2 + lib/wownero/cw_wownero.dart | 11 +++- tool/configure.dart | 5 +- 15 files changed, 138 insertions(+), 20 deletions(-) diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index ec694d4d56..588ae9750b 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -45,12 +45,11 @@ String getSeed() { if (cakepolyseed != "") { if (cakepassphrase != "") { try { - final lang = PolyseedLang.getByPhrase(cakepassphrase); + final lang = PolyseedLang.getByPhrase(cakepolyseed); final coin = PolyseedCoin.POLYSEED_MONERO; final ps = Polyseed.decode(cakepolyseed, lang, coin); - final passphrase = getPassphrase(); - if (ps.isEncrypted || passphrase == "") return ps.encode(lang, coin); - ps.crypt(getPassphrase()); + if (ps.isEncrypted || cakepassphrase == "") return ps.encode(lang, coin); + ps.crypt(cakepassphrase); return ps.encode(lang, coin); } catch (e) { printV(e); diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 258c506694..6a7ecdb5d6 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -70,6 +70,7 @@ void createWalletSync( {required String path, required String password, required String language, + required String passphrase, int nettype = 0}) { txhistory = null; final newWptr = monero.WalletManager_createWallet(wmPtr, @@ -80,6 +81,7 @@ void createWalletSync( throw WalletCreationException(message: monero.Wallet_errorString(newWptr)); } wptr = newWptr; + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase); monero.Wallet_store(wptr!, path: path); openedWalletsByPath[path] = wptr!; _lastOpenedWallet = path; @@ -390,8 +392,9 @@ void _createWallet(Map args) { final path = args['path'] as String; final password = args['password'] as String; final language = args['language'] as String; + final passphrase = args['passphrase'] as String; - createWalletSync(path: path, password: password, language: language); + createWalletSync(path: path, password: password, language: language, passphrase: passphrase); } void _restoreFromSeed(Map args) { @@ -459,11 +462,13 @@ Future createWallet( {required String path, required String password, required String language, + required String passphrase, int nettype = 0}) async => _createWallet({ 'path': path, 'password': password, 'language': language, + 'passphrase': passphrase, 'nettype': nettype }); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index e9c4d1c398..c2899db9bd 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -135,7 +135,7 @@ abstract class MoneroWalletBase extends WalletBase - transactionHistory.transactions.values.firstOrNull?.height; + transactionHistory.transactions.values.firstOrNull?.height ?? monero.Wallet_getRefreshFromBlockHeight(wptr!); monero_wallet.SyncListener? _listener; ReactionDisposer? _onAccountChangeReaction; diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 173e9a09fd..648c797022 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -26,11 +26,12 @@ import 'package:polyseed/polyseed.dart'; class MoneroNewWalletCredentials extends WalletCredentials { MoneroNewWalletCredentials( - {required String name, required this.language, required this.isPolyseed, String? password}) + {required String name, required this.language, required this.isPolyseed, String? password, this.passphrase}) : super(name: name, password: password); final String language; final bool isPolyseed; + final String? passphrase; } class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials { @@ -92,7 +93,7 @@ class MoneroWalletService extends WalletService< @override WalletType getType() => WalletType.monero; - @override + @override Future create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async { try { final path = await pathForWallet(name: credentials.name, type: getType()); @@ -112,7 +113,7 @@ class MoneroWalletService extends WalletService< } await monero_wallet_manager.createWallet( - path: path, password: credentials.password!, language: credentials.language); + path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??""); final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, @@ -382,6 +383,10 @@ class MoneroWalletService extends WalletService< restoreHeight: height, spendKey: spendKey); + + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase??''); + final wallet = MoneroWallet( walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, diff --git a/cw_wownero/lib/api/wallet.dart b/cw_wownero/lib/api/wallet.dart index 6a7c3be955..e124e29791 100644 --- a/cw_wownero/lib/api/wallet.dart +++ b/cw_wownero/lib/api/wallet.dart @@ -44,7 +44,7 @@ String getSeed() { if (cakepolyseed != "") { if (cakepassphrase != "") { try { - final lang = PolyseedLang.getByPhrase(cakepassphrase); + final lang = PolyseedLang.getByPhrase(cakepolyseed); final coin = PolyseedCoin.POLYSEED_WOWNERO; final ps = Polyseed.decode(cakepolyseed, lang, coin); final passphrase = getPassphrase(); diff --git a/cw_wownero/lib/api/wallet_manager.dart b/cw_wownero/lib/api/wallet_manager.dart index 190026918e..8d745ef8eb 100644 --- a/cw_wownero/lib/api/wallet_manager.dart +++ b/cw_wownero/lib/api/wallet_manager.dart @@ -66,6 +66,7 @@ void createWalletSync( {required String path, required String password, required String language, + required String passphrase, int nettype = 0}) { txhistory = null; final newWptr = wownero.WalletManager_createWallet(wmPtr, @@ -76,6 +77,8 @@ void createWalletSync( throw WalletCreationException(message: wownero.Wallet_errorString(newWptr)); } wptr = newWptr; + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase); + wownero.Wallet_store(wptr!, path: path); openedWalletsByPath[path] = wptr!; @@ -362,8 +365,9 @@ void _createWallet(Map args) { final path = args['path'] as String; final password = args['password'] as String; final language = args['language'] as String; + final passphrase = args['passphrase'] as String; - createWalletSync(path: path, password: password, language: language); + createWalletSync(path: path, password: password, language: language, passphrase: passphrase); } void _restoreFromSeed(Map args) { @@ -431,11 +435,13 @@ Future createWallet( {required String path, required String password, required String language, + required String passphrase, int nettype = 0}) async => _createWallet({ 'path': path, 'password': password, 'language': language, + 'passphrase': passphrase, 'nettype': nettype }); diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index ac2fc95564..67a9bbb45f 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -20,6 +20,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wownero_amount_format.dart'; import 'package:cw_core/wownero_balance.dart'; +import 'package:cw_wownero/api/account_list.dart'; import 'package:cw_wownero/api/coins_info.dart'; import 'package:cw_wownero/api/structs/pending_transaction.dart'; import 'package:cw_wownero/api/transaction_history.dart' as transaction_history; @@ -131,6 +132,10 @@ abstract class WowneroWalletBase publicViewKey: wownero_wallet.getPublicViewKey(), passphrase: wownero_wallet.getPassphrase()); + int? get restoreHeight => + transactionHistory.transactions.values.firstOrNull?.height ?? wownero.Wallet_getRefreshFromBlockHeight(wptr!); + + wownero_wallet.SyncListener? _listener; ReactionDisposer? _onAccountChangeReaction; ReactionDisposer? _onTxHistoryChangeReaction; diff --git a/cw_wownero/lib/wownero_wallet_service.dart b/cw_wownero/lib/wownero_wallet_service.dart index 06cb640ebe..72eefabd24 100644 --- a/cw_wownero/lib/wownero_wallet_service.dart +++ b/cw_wownero/lib/wownero_wallet_service.dart @@ -10,6 +10,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/get_height_by_date.dart'; +import 'package:cw_wownero/api/account_list.dart'; import 'package:cw_wownero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_wownero/api/wallet_manager.dart' as wownero_wallet_manager; import 'package:cw_wownero/api/wallet_manager.dart'; @@ -21,11 +22,12 @@ import 'package:monero/wownero.dart' as wownero; class WowneroNewWalletCredentials extends WalletCredentials { WowneroNewWalletCredentials( - {required String name, required this.language, required this.isPolyseed, String? password}) + {required String name, required this.language, required this.isPolyseed, this.passphrase, String? password}) : super(name: name, password: password); final String language; final bool isPolyseed; + final String? passphrase; } class WowneroRestoreWalletFromSeedCredentials extends WalletCredentials { @@ -95,7 +97,7 @@ class WowneroWalletService extends WalletService< } await wownero_wallet_manager.createWallet( - path: path, password: credentials.password!, language: credentials.language); + path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??''); final wallet = WowneroWallet( walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); await wallet.init(); @@ -345,6 +347,9 @@ class WowneroWalletService extends WalletService< restoreHeight: height, spendKey: spendKey); + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase??''); + final wallet = WowneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password); await wallet.init(); diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 8b16e0ffa1..aa6fafc6be 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -253,9 +253,10 @@ class CWMonero extends Monero { required String name, required String language, required bool isPolyseed, + required String? passphrase, String? password}) => MoneroNewWalletCredentials( - name: name, password: password, language: language, isPolyseed: isPolyseed); + name: name, password: password, language: language, isPolyseed: isPolyseed, passphrase: passphrase); @override Map getKeys(Object wallet) { diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 844678b900..f9b2f50153 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -15,6 +17,8 @@ import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/view_model/wallet_keys_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; import 'package:qr_flutter/qr_flutter.dart'; class WalletKeysPage extends BasePage { @@ -162,6 +166,8 @@ class _WalletKeysPageBodyState extends State children: [ if (isLegacySeedOnly || isLegacySeed) _buildHeightBox(), const SizedBox(height: 20), + (_buildPassphraseBox() ?? Container()), + if (widget.walletKeysViewModel.passphrase.isNotEmpty) const SizedBox(height: 20), Expanded( child: SeedPhraseGridWidget( list: isLegacySeed @@ -262,6 +268,64 @@ class _WalletKeysPageBodyState extends State ), ); } + + + Widget? _buildPassphraseBox() { + if (widget.walletKeysViewModel.passphrase.isEmpty) return null; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 14), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Theme.of(context).cardColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + S.of(context).passphrase_view_keys, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.buttonTextColor.withOpacity(0.5), + ), + ), + const SizedBox(width: 6), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Observer(builder: (BuildContext context) { + return Text( + (widget.walletKeysViewModel.obscurePassphrase) ? + "*****" : + widget.walletKeysViewModel.passphrase, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.buttonTextColor, + ), + ); + }), + Observer(builder: (BuildContext context) { + return GestureDetector( + onTap: () { + widget.walletKeysViewModel.obscurePassphrase = !widget.walletKeysViewModel.obscurePassphrase; + }, + child: Icon( + widget.walletKeysViewModel.obscurePassphrase ? Icons.visibility_off : Icons.visibility, + size: 16, + color: Theme.of(context).textTheme.bodyLarge?.color?.withOpacity(0.7), + ) + ); + }), + ], + ), + ), + ], + ), + ); + } Widget _buildBottomActionPanel({ diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index e920660965..b6a9d58f9c 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -78,8 +78,8 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { WalletType.ethereum, WalletType.polygon, WalletType.tron, - if (isRestore) WalletType.monero, - if (isRestore) WalletType.wownero, + WalletType.monero, + WalletType.wownero, if (isRestore) WalletType.zano, ].contains(type); diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index f93a95d2f4..edeacfcd61 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -86,9 +86,21 @@ abstract class WalletKeysViewModelBase with Store { if (_wallet.type == WalletType.monero) { return monero!.getRestoreHeight(_wallet)?.toString() ?? ''; } + if (_wallet.type == WalletType.wownero) { + return wownero!.getRestoreHeight(_wallet)?.toString() ?? ''; + } return ''; } + + + @observable + bool obscurePassphrase = true; + + String get passphrase { + return _wallet.passphrase ?? ''; + } + /// The Regex split the words based on any whitespace character. /// /// Either standard ASCII space (U+0020) or the full-width space character (U+3000) used by the Japanese. @@ -309,6 +321,12 @@ abstract class WalletKeysViewModelBase with Store { } Future get restoreHeight async { + if (_wallet.type == WalletType.monero) { + return monero!.getRestoreHeight(_wallet)?.toString(); + } + if (_wallet.type == WalletType.wownero) { + return wownero!.getRestoreHeight(_wallet)?.toString(); + } if (_restoreHeightByTransactions != 0) return getRoundedRestoreHeight(_restoreHeightByTransactions); if (_restoreHeight != 0) return _restoreHeight.toString(); diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index e79e73684e..f09f972cd3 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -100,6 +100,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, language: options!.first as String, password: walletPassword, + passphrase: passphrase, isPolyseed: options.last as bool); case WalletType.bitcoin: case WalletType.litecoin: @@ -168,6 +169,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { language: options!.first as String, isPolyseed: options.last as bool, password: walletPassword, + passphrase: passphrase, ); case WalletType.zano: return zano!.createZanoNewWalletCredentials( diff --git a/lib/wownero/cw_wownero.dart b/lib/wownero/cw_wownero.dart index 039ab0f704..e20b6fbbfe 100644 --- a/lib/wownero/cw_wownero.dart +++ b/lib/wownero/cw_wownero.dart @@ -233,9 +233,10 @@ class CWWownero extends Wownero { {required String name, required String language, required bool isPolyseed, - String? password}) => + String? password, + String? passphrase}) => WowneroNewWalletCredentials( - name: name, password: password, language: language, isPolyseed: isPolyseed); + name: name, password: password, language: language, isPolyseed: isPolyseed, passphrase: passphrase); @override Map getKeys(Object wallet) { @@ -250,6 +251,12 @@ class CWWownero extends Wownero { }; } + @override + int? getRestoreHeight(Object wallet) { + final wowneroWallet = wallet as WowneroWallet; + return wowneroWallet.restoreHeight; + } + @override Object createWowneroTransactionCreationCredentials( {required List outputs, required TransactionPriority priority}) => diff --git a/tool/configure.dart b/tool/configure.dart index f363dba15b..c8ed958a99 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -406,7 +406,7 @@ abstract class Monero { required int height}); WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic}); WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection}); - WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password}); + WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, required String? passphrase, String? password}); Map getKeys(Object wallet); int? getRestoreHeight(Object wallet); Object createMoneroTransactionCreationCredentials({required List outputs, required TransactionPriority priority}); @@ -595,7 +595,8 @@ abstract class Wownero { required String language, required int height}); WalletCredentials createWowneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic}); - WalletCredentials createWowneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password}); + WalletCredentials createWowneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password, String? passphrase}); + int? getRestoreHeight(Object wallet); Map getKeys(Object wallet); Object createWowneroTransactionCreationCredentials({required List outputs, required TransactionPriority priority}); Object createWowneroTransactionCreationCredentialsRaw({required List outputs, required TransactionPriority priority});