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 <omarh.ismail1@gmail.com>

cyan committed Feb 1, 2025 at 02:43 UTC 8b56c52dc6248eca62b7833471e67718ca8933da
15 files changed +138 -20
cw_monero/lib/api/wallet.dart
+3 -4
@@ -45,12 +45,11 @@ String getSeed() {
45 if (cakepolyseed != "") {
46 if (cakepassphrase != "") {
47 try {
48 - final lang = PolyseedLang.getByPhrase(cakepassphrase);
48 + final lang = PolyseedLang.getByPhrase(cakepolyseed);
49 final coin = PolyseedCoin.POLYSEED_MONERO;
50 final ps = Polyseed.decode(cakepolyseed, lang, coin);
51 - final passphrase = getPassphrase();
52 - if (ps.isEncrypted || passphrase == "") return ps.encode(lang, coin);
53 - ps.crypt(getPassphrase());
51 + if (ps.isEncrypted || cakepassphrase == "") return ps.encode(lang, coin);
52 + ps.crypt(cakepassphrase);
53 return ps.encode(lang, coin);
54 } catch (e) {
55 printV(e);
cw_monero/lib/api/wallet_manager.dart
+6 -1
@@ -70,6 +70,7 @@ void createWalletSync(
70 {required String path,
71 required String password,
72 required String language,
73 + required String passphrase,
74 int nettype = 0}) {
75 txhistory = null;
76 final newWptr = monero.WalletManager_createWallet(wmPtr,
@@ -80,6 +81,7 @@ void createWalletSync(
81 throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
82 }
83 wptr = newWptr;
84 + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
85 monero.Wallet_store(wptr!, path: path);
86 openedWalletsByPath[path] = wptr!;
87 _lastOpenedWallet = path;
@@ -390,8 +392,9 @@ void _createWallet(Map<String, dynamic> args) {
392 final path = args['path'] as String;
393 final password = args['password'] as String;
394 final language = args['language'] as String;
395 + final passphrase = args['passphrase'] as String;
396
394 - createWalletSync(path: path, password: password, language: language);
397 + createWalletSync(path: path, password: password, language: language, passphrase: passphrase);
398 }
399
400 void _restoreFromSeed(Map<String, dynamic> args) {
@@ -459,11 +462,13 @@ Future<void> createWallet(
462 {required String path,
463 required String password,
464 required String language,
465 + required String passphrase,
466 int nettype = 0}) async =>
467 _createWallet({
468 'path': path,
469 'password': password,
470 'language': language,
471 + 'passphrase': passphrase,
472 'nettype': nettype
473 });
474
cw_monero/lib/monero_wallet.dart
+1 -1
@@ -135,7 +135,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
135 passphrase: monero_wallet.getPassphrase());
136
137 int? get restoreHeight =>
138 - transactionHistory.transactions.values.firstOrNull?.height;
138 + transactionHistory.transactions.values.firstOrNull?.height ?? monero.Wallet_getRefreshFromBlockHeight(wptr!);
139
140 monero_wallet.SyncListener? _listener;
141 ReactionDisposer? _onAccountChangeReaction;
cw_monero/lib/monero_wallet_service.dart
+8 -3
@@ -26,11 +26,12 @@ import 'package:polyseed/polyseed.dart';
26
27 class MoneroNewWalletCredentials extends WalletCredentials {
28 MoneroNewWalletCredentials(
29 - {required String name, required this.language, required this.isPolyseed, String? password})
29 + {required String name, required this.language, required this.isPolyseed, String? password, this.passphrase})
30 : super(name: name, password: password);
31
32 final String language;
33 final bool isPolyseed;
34 + final String? passphrase;
35 }
36
37 class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
@@ -92,7 +93,7 @@ class MoneroWalletService extends WalletService<
93 @override
94 WalletType getType() => WalletType.monero;
95
95 - @override
96 + @override
97 Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
98 try {
99 final path = await pathForWallet(name: credentials.name, type: getType());
@@ -112,7 +113,7 @@ class MoneroWalletService extends WalletService<
113 }
114
115 await monero_wallet_manager.createWallet(
115 - path: path, password: credentials.password!, language: credentials.language);
116 + path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??"");
117 final wallet = MoneroWallet(
118 walletInfo: credentials.walletInfo!,
119 unspentCoinsInfo: unspentCoinsInfoSource,
@@ -382,6 +383,10 @@ class MoneroWalletService extends WalletService<
383 restoreHeight: height,
384 spendKey: spendKey);
385
386 +
387 + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
388 + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase??'');
389 +
390 final wallet = MoneroWallet(
391 walletInfo: walletInfo,
392 unspentCoinsInfo: unspentCoinsInfoSource,
cw_wownero/lib/api/wallet.dart
+1 -1
@@ -44,7 +44,7 @@ String getSeed() {
44 if (cakepolyseed != "") {
45 if (cakepassphrase != "") {
46 try {
47 - final lang = PolyseedLang.getByPhrase(cakepassphrase);
47 + final lang = PolyseedLang.getByPhrase(cakepolyseed);
48 final coin = PolyseedCoin.POLYSEED_WOWNERO;
49 final ps = Polyseed.decode(cakepolyseed, lang, coin);
50 final passphrase = getPassphrase();
cw_wownero/lib/api/wallet_manager.dart
+7 -1
@@ -66,6 +66,7 @@ void createWalletSync(
66 {required String path,
67 required String password,
68 required String language,
69 + required String passphrase,
70 int nettype = 0}) {
71 txhistory = null;
72 final newWptr = wownero.WalletManager_createWallet(wmPtr,
@@ -76,6 +77,8 @@ void createWalletSync(
77 throw WalletCreationException(message: wownero.Wallet_errorString(newWptr));
78 }
79 wptr = newWptr;
80 + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
81 +
82 wownero.Wallet_store(wptr!, path: path);
83 openedWalletsByPath[path] = wptr!;
84
@@ -362,8 +365,9 @@ void _createWallet(Map<String, dynamic> args) {
365 final path = args['path'] as String;
366 final password = args['password'] as String;
367 final language = args['language'] as String;
368 + final passphrase = args['passphrase'] as String;
369
366 - createWalletSync(path: path, password: password, language: language);
370 + createWalletSync(path: path, password: password, language: language, passphrase: passphrase);
371 }
372
373 void _restoreFromSeed(Map<String, dynamic> args) {
@@ -431,11 +435,13 @@ Future<void> createWallet(
435 {required String path,
436 required String password,
437 required String language,
438 + required String passphrase,
439 int nettype = 0}) async =>
440 _createWallet({
441 'path': path,
442 'password': password,
443 'language': language,
444 + 'passphrase': passphrase,
445 'nettype': nettype
446 });
447
cw_wownero/lib/wownero_wallet.dart
+5
@@ -20,6 +20,7 @@ import 'package:cw_core/wallet_base.dart';
20 import 'package:cw_core/wallet_info.dart';
21 import 'package:cw_core/wownero_amount_format.dart';
22 import 'package:cw_core/wownero_balance.dart';
23 +import 'package:cw_wownero/api/account_list.dart';
24 import 'package:cw_wownero/api/coins_info.dart';
25 import 'package:cw_wownero/api/structs/pending_transaction.dart';
26 import 'package:cw_wownero/api/transaction_history.dart' as transaction_history;
@@ -131,6 +132,10 @@ abstract class WowneroWalletBase
132 publicViewKey: wownero_wallet.getPublicViewKey(),
133 passphrase: wownero_wallet.getPassphrase());
134
135 + int? get restoreHeight =>
136 + transactionHistory.transactions.values.firstOrNull?.height ?? wownero.Wallet_getRefreshFromBlockHeight(wptr!);
137 +
138 +
139 wownero_wallet.SyncListener? _listener;
140 ReactionDisposer? _onAccountChangeReaction;
141 ReactionDisposer? _onTxHistoryChangeReaction;
cw_wownero/lib/wownero_wallet_service.dart
+7 -2
@@ -10,6 +10,7 @@ import 'package:cw_core/wallet_info.dart';
10 import 'package:cw_core/wallet_service.dart';
11 import 'package:cw_core/wallet_type.dart';
12 import 'package:cw_core/get_height_by_date.dart';
13 +import 'package:cw_wownero/api/account_list.dart';
14 import 'package:cw_wownero/api/exceptions/wallet_opening_exception.dart';
15 import 'package:cw_wownero/api/wallet_manager.dart' as wownero_wallet_manager;
16 import 'package:cw_wownero/api/wallet_manager.dart';
@@ -21,11 +22,12 @@ import 'package:monero/wownero.dart' as wownero;
22
23 class WowneroNewWalletCredentials extends WalletCredentials {
24 WowneroNewWalletCredentials(
24 - {required String name, required this.language, required this.isPolyseed, String? password})
25 + {required String name, required this.language, required this.isPolyseed, this.passphrase, String? password})
26 : super(name: name, password: password);
27
28 final String language;
29 final bool isPolyseed;
30 + final String? passphrase;
31 }
32
33 class WowneroRestoreWalletFromSeedCredentials extends WalletCredentials {
@@ -95,7 +97,7 @@ class WowneroWalletService extends WalletService<
97 }
98
99 await wownero_wallet_manager.createWallet(
98 - path: path, password: credentials.password!, language: credentials.language);
100 + path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??'');
101 final wallet = WowneroWallet(
102 walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!);
103 await wallet.init();
@@ -345,6 +347,9 @@ class WowneroWalletService extends WalletService<
347 restoreHeight: height,
348 spendKey: spendKey);
349
350 + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
351 + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase??'');
352 +
353 final wallet = WowneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password);
354 await wallet.init();
355
lib/monero/cw_monero.dart
+2 -1
@@ -253,9 +253,10 @@ class CWMonero extends Monero {
253 required String name,
254 required String language,
255 required bool isPolyseed,
256 + required String? passphrase,
257 String? password}) =>
258 MoneroNewWalletCredentials(
258 - name: name, password: password, language: language, isPolyseed: isPolyseed);
259 + name: name, password: password, language: language, isPolyseed: isPolyseed, passphrase: passphrase);
260
261 @override
262 Map<String, String> getKeys(Object wallet) {
lib/src/screens/wallet_keys/wallet_keys_page.dart
+64
@@ -1,3 +1,5 @@
1 +import 'dart:math';
2 +
3 import 'package:cake_wallet/entities/qr_view_data.dart';
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/routes.dart';
@@ -15,6 +17,8 @@ import 'package:cake_wallet/utils/show_bar.dart';
17 import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
18 import 'package:flutter/material.dart';
19 import 'package:flutter/services.dart';
20 +import 'package:flutter_mobx/flutter_mobx.dart';
21 +import 'package:mobx/mobx.dart';
22 import 'package:qr_flutter/qr_flutter.dart';
23
24 class WalletKeysPage extends BasePage {
@@ -162,6 +166,8 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
166 children: [
167 if (isLegacySeedOnly || isLegacySeed) _buildHeightBox(),
168 const SizedBox(height: 20),
169 + (_buildPassphraseBox() ?? Container()),
170 + if (widget.walletKeysViewModel.passphrase.isNotEmpty) const SizedBox(height: 20),
171 Expanded(
172 child: SeedPhraseGridWidget(
173 list: isLegacySeed
@@ -262,6 +268,64 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
268 ),
269 );
270 }
271 +
272 +
273 + Widget? _buildPassphraseBox() {
274 + if (widget.walletKeysViewModel.passphrase.isEmpty) return null;
275 + return Container(
276 + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 14),
277 + decoration: BoxDecoration(
278 + borderRadius: BorderRadius.circular(8),
279 + color: Theme.of(context).cardColor,
280 + ),
281 + child: Row(
282 + mainAxisAlignment: MainAxisAlignment.center,
283 + children: [
284 + Text(
285 + S.of(context).passphrase_view_keys,
286 + textAlign: TextAlign.center,
287 + style: TextStyle(
288 + fontSize: 14,
289 + fontWeight: FontWeight.w500,
290 + color: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor.withOpacity(0.5),
291 + ),
292 + ),
293 + const SizedBox(width: 6),
294 + Expanded(
295 + child: Row(
296 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
297 + children: [
298 + Observer(builder: (BuildContext context) {
299 + return Text(
300 + (widget.walletKeysViewModel.obscurePassphrase) ?
301 + "*****" :
302 + widget.walletKeysViewModel.passphrase,
303 + style: TextStyle(
304 + fontSize: 14,
305 + fontWeight: FontWeight.w500,
306 + color: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
307 + ),
308 + );
309 + }),
310 + Observer(builder: (BuildContext context) {
311 + return GestureDetector(
312 + onTap: () {
313 + widget.walletKeysViewModel.obscurePassphrase = !widget.walletKeysViewModel.obscurePassphrase;
314 + },
315 + child: Icon(
316 + widget.walletKeysViewModel.obscurePassphrase ? Icons.visibility_off : Icons.visibility,
317 + size: 16,
318 + color: Theme.of(context).textTheme.bodyLarge?.color?.withOpacity(0.7),
319 + )
320 + );
321 + }),
322 + ],
323 + ),
324 + ),
325 + ],
326 + ),
327 + );
328 + }
329
330
331 Widget _buildBottomActionPanel({
lib/view_model/advanced_privacy_settings_view_model.dart
+2 -2
@@ -78,8 +78,8 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
78 WalletType.ethereum,
79 WalletType.polygon,
80 WalletType.tron,
81 - if (isRestore) WalletType.monero,
82 - if (isRestore) WalletType.wownero,
81 + WalletType.monero,
82 + WalletType.wownero,
83 if (isRestore) WalletType.zano,
84 ].contains(type);
85
lib/view_model/wallet_keys_view_model.dart
+18
@@ -86,9 +86,21 @@ abstract class WalletKeysViewModelBase with Store {
86 if (_wallet.type == WalletType.monero) {
87 return monero!.getRestoreHeight(_wallet)?.toString() ?? '';
88 }
89 + if (_wallet.type == WalletType.wownero) {
90 + return wownero!.getRestoreHeight(_wallet)?.toString() ?? '';
91 + }
92 return '';
93 }
94
95 +
96 +
97 + @observable
98 + bool obscurePassphrase = true;
99 +
100 + String get passphrase {
101 + return _wallet.passphrase ?? '';
102 + }
103 +
104 /// The Regex split the words based on any whitespace character.
105 ///
106 /// 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 {
321 }
322
323 Future<String?> get restoreHeight async {
324 + if (_wallet.type == WalletType.monero) {
325 + return monero!.getRestoreHeight(_wallet)?.toString();
326 + }
327 + if (_wallet.type == WalletType.wownero) {
328 + return wownero!.getRestoreHeight(_wallet)?.toString();
329 + }
330 if (_restoreHeightByTransactions != 0)
331 return getRoundedRestoreHeight(_restoreHeightByTransactions);
332 if (_restoreHeight != 0) return _restoreHeight.toString();
lib/view_model/wallet_new_vm.dart
+2
@@ -100,6 +100,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
100 name: name,
101 language: options!.first as String,
102 password: walletPassword,
103 + passphrase: passphrase,
104 isPolyseed: options.last as bool);
105 case WalletType.bitcoin:
106 case WalletType.litecoin:
@@ -168,6 +169,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
169 language: options!.first as String,
170 isPolyseed: options.last as bool,
171 password: walletPassword,
172 + passphrase: passphrase,
173 );
174 case WalletType.zano:
175 return zano!.createZanoNewWalletCredentials(
lib/wownero/cw_wownero.dart
+9 -2
@@ -233,9 +233,10 @@ class CWWownero extends Wownero {
233 {required String name,
234 required String language,
235 required bool isPolyseed,
236 - String? password}) =>
236 + String? password,
237 + String? passphrase}) =>
238 WowneroNewWalletCredentials(
238 - name: name, password: password, language: language, isPolyseed: isPolyseed);
239 + name: name, password: password, language: language, isPolyseed: isPolyseed, passphrase: passphrase);
240
241 @override
242 Map<String, String> getKeys(Object wallet) {
@@ -250,6 +251,12 @@ class CWWownero extends Wownero {
251 };
252 }
253
254 + @override
255 + int? getRestoreHeight(Object wallet) {
256 + final wowneroWallet = wallet as WowneroWallet;
257 + return wowneroWallet.restoreHeight;
258 + }
259 +
260 @override
261 Object createWowneroTransactionCreationCredentials(
262 {required List<Output> outputs, required TransactionPriority priority}) =>
tool/configure.dart
+3 -2
@@ -406,7 +406,7 @@ abstract class Monero {
406 required int height});
407 WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
408 WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
409 - WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password});
409 + WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, required String? passphrase, String? password});
410 Map<String, String> getKeys(Object wallet);
411 int? getRestoreHeight(Object wallet);
412 Object createMoneroTransactionCreationCredentials({required List<Output> outputs, required TransactionPriority priority});
@@ -595,7 +595,8 @@ abstract class Wownero {
595 required String language,
596 required int height});
597 WalletCredentials createWowneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
598 - WalletCredentials createWowneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password});
598 + WalletCredentials createWowneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password, String? passphrase});
599 + int? getRestoreHeight(Object wallet);
600 Map<String, String> getKeys(Object wallet);
601 Object createWowneroTransactionCreationCredentials({required List<Output> outputs, required TransactionPriority priority});
602 Object createWowneroTransactionCreationCredentialsRaw({required List<OutputInfo> outputs, required TransactionPriority priority});