CW-934 Implement passphrase creation for zano (#2026)

* CW-934 Implement passphrase creation for zano * Update monero_c dependency to latest commit Fix issue with zano keys not showing during sync Fix delays when invoking read only commands in zano Fix extra padding above passphrase Reduced lag during app use

cyan committed Feb 18, 2025 at 23:28 UTC dd8ccee1ba3cdb07ced2aabe3acd218b2bc6271b
18 files changed +143 -41
cw_monero/pubspec.lock
+2 -2
@@ -511,8 +511,8 @@ packages:
511 dependency: "direct main"
512 description:
513 path: "impls/monero.dart"
514 - ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7"
515 - resolved-ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7"
514 + ref: "65608c09e9093f1cd42c6afd8e9131016c82574b"
515 + resolved-ref: "65608c09e9093f1cd42c6afd8e9131016c82574b"
516 url: "https://github.com/mrcyjanek/monero_c"
517 source: git
518 version: "0.0.0"
cw_monero/pubspec.yaml
+1 -1
@@ -25,7 +25,7 @@ dependencies:
25 monero:
26 git:
27 url: https://github.com/mrcyjanek/monero_c
28 - ref: 629fa4a346ca29d5ed18a2b44895b8858ba7c9f7
28 + ref: 65608c09e9093f1cd42c6afd8e9131016c82574b
29 path: impls/monero.dart
30 mutex: ^3.1.0
31 ledger_flutter_plus: ^1.4.1
cw_wownero/pubspec.lock
+2 -2
@@ -471,8 +471,8 @@ packages:
471 dependency: "direct main"
472 description:
473 path: "impls/monero.dart"
474 - ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7"
475 - resolved-ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7"
474 + ref: "65608c09e9093f1cd42c6afd8e9131016c82574b"
475 + resolved-ref: "65608c09e9093f1cd42c6afd8e9131016c82574b"
476 url: "https://github.com/mrcyjanek/monero_c"
477 source: git
478 version: "0.0.0"
cw_wownero/pubspec.yaml
+1 -1
@@ -25,7 +25,7 @@ dependencies:
25 monero:
26 git:
27 url: https://github.com/mrcyjanek/monero_c
28 - ref: 629fa4a346ca29d5ed18a2b44895b8858ba7c9f7 # monero_c hash
28 + ref: 65608c09e9093f1cd42c6afd8e9131016c82574b # monero_c hash
29 path: impls/monero.dart
30 mutex: ^3.1.0
31
cw_zano/lib/api/model/create_wallet_result.dart
+17 -4
@@ -1,27 +1,34 @@
1 import 'package:cw_zano/api/model/recent_history.dart';
2 import 'package:cw_zano/api/model/wi.dart';
3 +import 'package:cw_zano/zano_wallet.dart';
4
5 class CreateWalletResult {
6 final String name;
7 final String pass;
8 final RecentHistory recentHistory;
9 final bool recovered;
9 - final String seed;
10 final int walletFileSize;
11 final int walletId;
12 final int walletLocalBcSize;
13 final Wi wi;
14 + final String privateSpendKey;
15 + final String privateViewKey;
16 + final String publicSpendKey;
17 + final String publicViewKey;
18
19 CreateWalletResult(
20 {required this.name,
21 required this.pass,
22 required this.recentHistory,
23 required this.recovered,
20 - required this.seed,
24 required this.walletFileSize,
25 required this.walletId,
26 required this.walletLocalBcSize,
24 - required this.wi});
27 + required this.wi,
28 + required this.privateSpendKey,
29 + required this.privateViewKey,
30 + required this.publicSpendKey,
31 + required this.publicViewKey});
32
33 factory CreateWalletResult.fromJson(Map<String, dynamic> json) =>
34 CreateWalletResult(
@@ -30,10 +37,16 @@ class CreateWalletResult {
37 recentHistory: RecentHistory.fromJson(
38 json['recent_history'] as Map<String, dynamic>? ?? {}),
39 recovered: json['recovered'] as bool? ?? false,
33 - seed: json['seed'] as String? ?? '',
40 walletFileSize: json['wallet_file_size'] as int? ?? 0,
41 walletId: json['wallet_id'] as int? ?? 0,
42 walletLocalBcSize: json['wallet_local_bc_size'] as int? ?? 0,
43 wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
44 + privateSpendKey: json['private_spend_key'] as String? ?? '',
45 + privateViewKey: json['private_view_key'] as String? ?? '',
46 + publicSpendKey: json['public_spend_key'] as String? ?? '',
47 + publicViewKey: json['public_view_key'] as String? ?? '',
48 );
49 + Future<String> seed(ZanoWalletBase api) {
50 + return api.getSeed();
51 + }
52 }
cw_zano/lib/api/model/wi_extended.dart
+7 -3
@@ -1,17 +1,21 @@
1 +import 'package:cw_zano/zano_wallet.dart';
2 +
3 class WiExtended {
2 - final String seed;
4 final String spendPrivateKey;
5 final String spendPublicKey;
6 final String viewPrivateKey;
7 final String viewPublicKey;
8
8 - WiExtended({required this.seed, required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey});
9 + WiExtended({required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey});
10
11 factory WiExtended.fromJson(Map<String, dynamic> json) => WiExtended(
11 - seed: json['seed'] as String? ?? '',
12 spendPrivateKey: json['spend_private_key'] as String? ?? '',
13 spendPublicKey: json['spend_public_key'] as String? ?? '',
14 viewPrivateKey: json['view_private_key'] as String? ?? '',
15 viewPublicKey: json['view_public_key'] as String? ?? '',
16 );
17 +
18 + Future<String> seed(ZanoWalletBase api) {
19 + return api.getSeed();
20 + }
21 }
\ No newline at end of file
cw_zano/lib/zano_wallet.dart
+23 -2
@@ -82,6 +82,9 @@ abstract class ZanoWalletBase
82 @override
83 String seed = '';
84
85 + @override
86 + String? passphrase = '';
87 +
88 @override
89 ZanoWalletKeys keys = ZanoWalletKeys(
90 privateSpendKey: '', privateViewKey: '', publicSpendKey: '', publicViewKey: '');
@@ -133,6 +136,11 @@ abstract class ZanoWalletBase
136 final createWalletResult = await wallet.createWallet(path, credentials.password!);
137 await wallet.initWallet();
138 await wallet.parseCreateWalletResult(createWalletResult);
139 + if (credentials.passphrase != null) {
140 + await wallet.setPassphrase(credentials.passphrase!);
141 + wallet.seed = await createWalletResult.seed(wallet);
142 + wallet.passphrase = await wallet.getPassphrase();
143 + }
144 await wallet.init(createWalletResult.wi.address);
145 return wallet;
146 }
@@ -146,6 +154,11 @@ abstract class ZanoWalletBase
154 path, credentials.password!, credentials.mnemonic, credentials.passphrase);
155 await wallet.initWallet();
156 await wallet.parseCreateWalletResult(createWalletResult);
157 + if (credentials.passphrase != null) {
158 + await wallet.setPassphrase(credentials.passphrase!);
159 + wallet.seed = await createWalletResult.seed(wallet);
160 + wallet.passphrase = await wallet.getPassphrase();
161 + }
162 await wallet.init(createWalletResult.wi.address);
163 return wallet;
164 }
@@ -172,7 +185,15 @@ abstract class ZanoWalletBase
185
186 Future<void> parseCreateWalletResult(CreateWalletResult result) async {
187 hWallet = result.walletId;
175 - seed = result.seed;
188 + seed = await result.seed(this);
189 + keys = ZanoWalletKeys(
190 + privateSpendKey: result.privateSpendKey,
191 + privateViewKey: result.privateViewKey,
192 + publicSpendKey: result.publicSpendKey,
193 + publicViewKey: result.publicViewKey,
194 + );
195 + passphrase = await getPassphrase();
196 +
197 printV('setting hWallet = ${result.walletId}');
198 walletAddresses.address = result.wi.address;
199 await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries);
@@ -511,7 +532,7 @@ abstract class ZanoWalletBase
532 // we can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready)
533 if (!walletStatus.isInLongRefresh && walletStatus.walletState == 2) {
534 final walletInfo = await getWalletInfo();
514 - seed = walletInfo.wiExtended.seed;
535 + seed = await walletInfo.wiExtended.seed(this);
536 keys = ZanoWalletKeys(
537 privateSpendKey: walletInfo.wiExtended.spendPrivateKey,
538 privateViewKey: walletInfo.wiExtended.viewPrivateKey,
cw_zano/lib/zano_wallet_api.dart
+73 -13
@@ -26,6 +26,7 @@ import 'package:ffi/ffi.dart';
26 import 'package:json_bigint/json_bigint.dart';
27 import 'package:monero/zano.dart' as zano;
28 import 'package:monero/src/generated_bindings_zano.g.dart' as zanoapi;
29 +import 'package:path/path.dart' as p;
30
31 mixin ZanoWalletApi {
32 static const _maxReopenAttempts = 5;
@@ -45,7 +46,7 @@ mixin ZanoWalletApi {
46 void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password);
47
48 void closeWallet(int? walletToClose, {bool force = false}) async {
48 - printV('close_wallet ${walletToClose ?? hWallet}');
49 + printV('close_wallet ${walletToClose ?? hWallet}: $force');
50 if (Platform.isWindows || force) {
51 final result = await _closeWallet(walletToClose ?? hWallet);
52 printV('close_wallet result $result');
@@ -53,10 +54,9 @@ mixin ZanoWalletApi {
54 }
55 }
56
56 - bool isInit = false;
57 + static bool isInit = false;
58
59 Future<bool> initWallet() async {
59 - // pathForWallet(name: , type: type)
60 if (isInit) return true;
61 final result = zano.PlainWallet_init("", "", 0);
62 isInit = true;
@@ -68,6 +68,68 @@ mixin ZanoWalletApi {
68 return true;
69 }
70
71 + Future<Directory> getWalletDir() async {
72 + final walletInfoResult = await getWalletInfo();
73 + return Directory(p.dirname(walletInfoResult.wi.path));
74 + }
75 +
76 + Future<File> _getWalletSecretsFile() async {
77 + final dir = await getWalletDir();
78 + final file = File(p.join(dir.path, "zano-secrets.json.bin"));
79 + return file;
80 + }
81 +
82 + Future<Map<String, dynamic>> _getSecrets() async {
83 + final file = await _getWalletSecretsFile();
84 + if (!file.existsSync()) {
85 + return {};
86 + }
87 + final data = file.readAsBytesSync();
88 + final b64 = convert.base64.encode(data);
89 + final respStr = await invokeMethod("decrypt_data", {"buff": "$b64"});
90 + final resp = convert.json.decode(respStr);
91 + final dataBytes = convert.base64.decode(resp["result"]["res_buff"] as String);
92 + final dataStr = convert.utf8.decode(dataBytes);
93 + final dataObject = convert.json.decode(dataStr);
94 + return dataObject as Map<String, dynamic>;
95 + }
96 +
97 + Future<void> _setSecrets(Map<String, dynamic> data) async {
98 + final dataStr = convert.json.encode(data);
99 + final b64 = convert.base64.encode(convert.utf8.encode(dataStr));
100 + final respStr = await invokeMethod("encrypt_data", {"buff": "$b64"});
101 + final resp = convert.json.decode(respStr);
102 + final dataBytes = convert.base64.decode(resp["result"]["res_buff"] as String);
103 + final file = await _getWalletSecretsFile();
104 + file.writeAsBytesSync(dataBytes);
105 + }
106 +
107 + Future<String?> _getWalletSecret(String key) async {
108 + final secrets = await _getSecrets();
109 + return secrets[key] as String?;
110 + }
111 +
112 + Future<void> _setWalletSecret(String key, String value) async {
113 + final secrets = await _getSecrets();
114 + secrets[key] = value;
115 + await _setSecrets(secrets);
116 + }
117 +
118 + Future<String?> getPassphrase() async {
119 + return await _getWalletSecret("passphrase");
120 + }
121 +
122 + Future<void> setPassphrase(String passphrase) {
123 + return _setWalletSecret("passphrase", passphrase);
124 + }
125 +
126 + Future<String> getSeed() async {
127 + final passphrase = await getPassphrase();
128 + final respStr = await invokeMethod("get_restore_info", {"seed_password": passphrase??""});
129 + final resp = convert.json.decode(respStr);
130 + return resp["result"]["seed_phrase"] as String;
131 + }
132 +
133 Future<GetWalletInfoResult> getWalletInfo() async {
134 final json = await _getWalletInfo(hWallet);
135 final result = GetWalletInfoResult.fromJson(jsonDecode(json));
@@ -192,7 +254,7 @@ mixin ZanoWalletApi {
254
255 Future<StoreResult?> store() async {
256 try {
195 - final json = await invokeMethod('store', '{}');
257 + final json = await invokeMethod('store', {});
258 final map = jsonDecode(json) as Map<String, dynamic>?;
259 _checkForErrors(map);
260 return StoreResult.fromJson(map!['result'] as Map<String, dynamic>);
@@ -247,12 +309,12 @@ mixin ZanoWalletApi {
309 }
310 final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
311 openWalletCache[path] = result;
250 - printV('create_wallet ${result.name} ${result.seed}');
312 + printV('create_wallet ${result.name}');
313 return result;
314 }
315
316 Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed, String? passphrase) async {
255 - printV('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}');
317 + printV('restore_wallet path $path');
318 final json = zano.PlainWallet_restore(seed, path, password, passphrase??'');
319 final map = jsonDecode(json) as Map<String, dynamic>?;
320 if (map?['error'] != null) {
@@ -274,8 +336,8 @@ mixin ZanoWalletApi {
336 return result;
337 }
338
277 - Future<CreateWalletResult>loadWallet(String path, String password, [int attempt = 0]) async {
278 - printV('load_wallet1 path $path password ${_shorten(password)}');
339 + Future<CreateWalletResult> loadWallet(String path, String password, [int attempt = 0]) async {
340 + printV('load_wallet1 path $path');
341 final String json;
342 try {
343 json = zano.PlainWallet_open(path, password);
@@ -283,7 +345,7 @@ mixin ZanoWalletApi {
345 printV('error in loadingWallet $e');
346 rethrow;
347 }
286 - // printV('load_wallet2: $json');
348 +
349 final map = jsonDecode(json) as Map<String, dynamic>?;
350 if (map?['error'] != null) {
351 final code = map?['error']!['code'] ?? '';
@@ -435,10 +497,8 @@ Future<String> _getWalletInfo(int hWallet) async {
497 }
498
499 Future<String> _setupNode(int hWallet, String nodeUrl) async {
438 - final resp = await callSyncMethod("reset_connection_url", hWallet, nodeUrl);
439 - printV(resp);
440 - final resp2 = await callSyncMethod("run_wallet", hWallet, "");
441 - printV(resp2);
500 + await callSyncMethod("reset_connection_url", hWallet, nodeUrl);
501 + await callSyncMethod("run_wallet", hWallet, "");
502 return "OK";
503 }
504
cw_zano/lib/zano_wallet_service.dart
+1 -1
@@ -14,7 +14,7 @@ import 'package:hive/hive.dart';
14 import 'package:monero/zano.dart' as zano;
15
16 class ZanoNewWalletCredentials extends WalletCredentials {
17 - ZanoNewWalletCredentials({required String name, String? password}) : super(name: name, password: password);
17 + ZanoNewWalletCredentials({required String name, String? password, required String? passphrase}) : super(name: name, password: password, passphrase: passphrase);
18 }
19
20 class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials {
cw_zano/pubspec.lock
+2 -2
@@ -476,8 +476,8 @@ packages:
476 dependency: "direct main"
477 description:
478 path: "impls/monero.dart"
479 - ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7"
480 - resolved-ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7"
479 + ref: "65608c09e9093f1cd42c6afd8e9131016c82574b"
480 + resolved-ref: "65608c09e9093f1cd42c6afd8e9131016c82574b"
481 url: "https://github.com/mrcyjanek/monero_c"
482 source: git
483 version: "0.0.0"
cw_zano/pubspec.yaml
+1 -1
@@ -26,7 +26,7 @@ dependencies:
26 monero:
27 git:
28 url: https://github.com/mrcyjanek/monero_c
29 - ref: 629fa4a346ca29d5ed18a2b44895b8858ba7c9f7 # monero_c hash
29 + ref: 65608c09e9093f1cd42c6afd8e9131016c82574b # monero_c hash
30 path: impls/monero.dart
31 dev_dependencies:
32 flutter_test:
lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+1 -1
@@ -202,7 +202,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
202 );
203 return Container();
204 }),
205 - if (widget.privacySettingsViewModel.hasPassphraseOption(widget.isFromRestore))
205 + if (widget.privacySettingsViewModel.hasPassphraseOption)
206 Padding(
207 padding: EdgeInsets.all(24),
208 child: Form(
lib/src/screens/wallet_keys/wallet_keys_page.dart
+5 -2
@@ -160,8 +160,11 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
160 Widget _buildSeedTab(BuildContext context, bool isLegacySeed) {
161 return Column(
162 children: [
163 - if (isLegacySeedOnly || isLegacySeed) _buildHeightBox(),
164 - const SizedBox(height: 20),
163 + if (isLegacySeedOnly || isLegacySeed)
164 + ...[
165 + _buildHeightBox(),
166 + const SizedBox(height: 20),
167 + ],
168 (_buildPassphraseBox() ?? Container()),
169 if (widget.walletKeysViewModel.passphrase.isNotEmpty) const SizedBox(height: 20),
170 Expanded(
lib/view_model/advanced_privacy_settings_view_model.dart
+2 -2
@@ -71,7 +71,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
71
72 bool get isNanoSeedTypeOptionsEnabled => [WalletType.nano].contains(type);
73
74 - bool hasPassphraseOption(bool isRestore) => [
74 + bool get hasPassphraseOption => [
75 WalletType.bitcoin,
76 WalletType.litecoin,
77 WalletType.bitcoinCash,
@@ -80,7 +80,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
80 WalletType.tron,
81 WalletType.monero,
82 WalletType.wownero,
83 - if (isRestore) WalletType.zano,
83 + WalletType.zano,
84 ].contains(type);
85
86 @computed
lib/view_model/wallet_new_vm.dart
+1
@@ -175,6 +175,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
175 return zano!.createZanoNewWalletCredentials(
176 name: name,
177 password: walletPassword,
178 + passphrase: passphrase,
179 );
180 case WalletType.none:
181 throw Exception('Unexpected type: ${type.toString()}');
lib/zano/cw_zano.dart
+2 -2
@@ -53,8 +53,8 @@ class CWZano extends Zano {
53 }
54
55 @override
56 - WalletCredentials createZanoNewWalletCredentials({required String name, required String? password}) {
57 - return ZanoNewWalletCredentials(name: name, password: password);
56 + WalletCredentials createZanoNewWalletCredentials({required String name, required String? password, required String? passphrase}) {
57 + return ZanoNewWalletCredentials(name: name, password: password, passphrase: passphrase);
58 }
59
60 @override
scripts/prepare_moneroc.sh
+1 -1
@@ -8,7 +8,7 @@ if [[ ! -d "monero_c/.git" ]];
8 then
9 git clone https://github.com/mrcyjanek/monero_c --branch master monero_c
10 cd monero_c
11 - git checkout 629fa4a346ca29d5ed18a2b44895b8858ba7c9f7
11 + git checkout 65608c09e9093f1cd42c6afd8e9131016c82574b
12 git reset --hard
13 git submodule update --init --force --recursive
14 ./apply_patches.sh monero
tool/configure.dart
+1 -1
@@ -1443,7 +1443,7 @@ abstract class Zano {
1443 List<String> getWordList(String language);
1444
1445 WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
1446 - WalletCredentials createZanoNewWalletCredentials({required String name, required String? password});
1446 + WalletCredentials createZanoNewWalletCredentials({required String name, required String? password, required String? passphrase});
1447 Map<String, String> getKeys(Object wallet);
1448 Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency});
1449 double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});