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});