From dd8ccee1ba3cdb07ced2aabe3acd218b2bc6271b Mon Sep 17 00:00:00 2001 From: cyan <cyjan@mrcyjanek.net> Date: Tue, 18 Feb 2025 23:28:27 +0100 Subject: [PATCH] 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 --- cw_monero/pubspec.lock | 4 +- cw_monero/pubspec.yaml | 2 +- cw_wownero/pubspec.lock | 4 +- cw_wownero/pubspec.yaml | 2 +- .../lib/api/model/create_wallet_result.dart | 21 ++++- cw_zano/lib/api/model/wi_extended.dart | 10 ++- cw_zano/lib/zano_wallet.dart | 25 +++++- cw_zano/lib/zano_wallet_api.dart | 86 ++++++++++++++++--- cw_zano/lib/zano_wallet_service.dart | 2 +- cw_zano/pubspec.lock | 4 +- cw_zano/pubspec.yaml | 2 +- .../advanced_privacy_settings_page.dart | 2 +- .../screens/wallet_keys/wallet_keys_page.dart | 7 +- .../advanced_privacy_settings_view_model.dart | 4 +- lib/view_model/wallet_new_vm.dart | 1 + lib/zano/cw_zano.dart | 4 +- scripts/prepare_moneroc.sh | 2 +- tool/configure.dart | 2 +- 18 files changed, 143 insertions(+), 41 deletions(-) diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 9a700609c..f1510b4bc 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -511,8 +511,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7" - resolved-ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7" + ref: "65608c09e9093f1cd42c6afd8e9131016c82574b" + resolved-ref: "65608c09e9093f1cd42c6afd8e9131016c82574b" url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 37835d1ff..6a96e41cc 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: 629fa4a346ca29d5ed18a2b44895b8858ba7c9f7 + ref: 65608c09e9093f1cd42c6afd8e9131016c82574b path: impls/monero.dart mutex: ^3.1.0 ledger_flutter_plus: ^1.4.1 diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 7e520fd50..44182ed9f 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -471,8 +471,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7" - resolved-ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7" + ref: "65608c09e9093f1cd42c6afd8e9131016c82574b" + resolved-ref: "65608c09e9093f1cd42c6afd8e9131016c82574b" url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml index f5a83ce48..a57c2f25b 100644 --- a/cw_wownero/pubspec.yaml +++ b/cw_wownero/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: 629fa4a346ca29d5ed18a2b44895b8858ba7c9f7 # monero_c hash + ref: 65608c09e9093f1cd42c6afd8e9131016c82574b # monero_c hash path: impls/monero.dart mutex: ^3.1.0 diff --git a/cw_zano/lib/api/model/create_wallet_result.dart b/cw_zano/lib/api/model/create_wallet_result.dart index 91b6fc00b..236911db1 100644 --- a/cw_zano/lib/api/model/create_wallet_result.dart +++ b/cw_zano/lib/api/model/create_wallet_result.dart @@ -1,27 +1,34 @@ import 'package:cw_zano/api/model/recent_history.dart'; import 'package:cw_zano/api/model/wi.dart'; +import 'package:cw_zano/zano_wallet.dart'; class CreateWalletResult { final String name; final String pass; final RecentHistory recentHistory; final bool recovered; - final String seed; final int walletFileSize; final int walletId; final int walletLocalBcSize; final Wi wi; + final String privateSpendKey; + final String privateViewKey; + final String publicSpendKey; + final String publicViewKey; CreateWalletResult( {required this.name, required this.pass, required this.recentHistory, required this.recovered, - required this.seed, required this.walletFileSize, required this.walletId, required this.walletLocalBcSize, - required this.wi}); + required this.wi, + required this.privateSpendKey, + required this.privateViewKey, + required this.publicSpendKey, + required this.publicViewKey}); factory CreateWalletResult.fromJson(Map<String, dynamic> json) => CreateWalletResult( @@ -30,10 +37,16 @@ class CreateWalletResult { recentHistory: RecentHistory.fromJson( json['recent_history'] as Map<String, dynamic>? ?? {}), recovered: json['recovered'] as bool? ?? false, - seed: json['seed'] as String? ?? '', walletFileSize: json['wallet_file_size'] as int? ?? 0, walletId: json['wallet_id'] as int? ?? 0, walletLocalBcSize: json['wallet_local_bc_size'] as int? ?? 0, wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}), + privateSpendKey: json['private_spend_key'] as String? ?? '', + privateViewKey: json['private_view_key'] as String? ?? '', + publicSpendKey: json['public_spend_key'] as String? ?? '', + publicViewKey: json['public_view_key'] as String? ?? '', ); + Future<String> seed(ZanoWalletBase api) { + return api.getSeed(); + } } diff --git a/cw_zano/lib/api/model/wi_extended.dart b/cw_zano/lib/api/model/wi_extended.dart index ab7e8efbd..ef2745992 100644 --- a/cw_zano/lib/api/model/wi_extended.dart +++ b/cw_zano/lib/api/model/wi_extended.dart @@ -1,17 +1,21 @@ +import 'package:cw_zano/zano_wallet.dart'; + class WiExtended { - final String seed; final String spendPrivateKey; final String spendPublicKey; final String viewPrivateKey; final String viewPublicKey; - WiExtended({required this.seed, required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey}); + WiExtended({required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey}); factory WiExtended.fromJson(Map<String, dynamic> json) => WiExtended( - seed: json['seed'] as String? ?? '', spendPrivateKey: json['spend_private_key'] as String? ?? '', spendPublicKey: json['spend_public_key'] as String? ?? '', viewPrivateKey: json['view_private_key'] as String? ?? '', viewPublicKey: json['view_public_key'] as String? ?? '', ); + + Future<String> seed(ZanoWalletBase api) { + return api.getSeed(); + } } \ No newline at end of file diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart index b2283b082..7f68b71e6 100644 --- a/cw_zano/lib/zano_wallet.dart +++ b/cw_zano/lib/zano_wallet.dart @@ -82,6 +82,9 @@ abstract class ZanoWalletBase @override String seed = ''; + @override + String? passphrase = ''; + @override ZanoWalletKeys keys = ZanoWalletKeys( privateSpendKey: '', privateViewKey: '', publicSpendKey: '', publicViewKey: ''); @@ -133,6 +136,11 @@ abstract class ZanoWalletBase final createWalletResult = await wallet.createWallet(path, credentials.password!); await wallet.initWallet(); await wallet.parseCreateWalletResult(createWalletResult); + if (credentials.passphrase != null) { + await wallet.setPassphrase(credentials.passphrase!); + wallet.seed = await createWalletResult.seed(wallet); + wallet.passphrase = await wallet.getPassphrase(); + } await wallet.init(createWalletResult.wi.address); return wallet; } @@ -146,6 +154,11 @@ abstract class ZanoWalletBase path, credentials.password!, credentials.mnemonic, credentials.passphrase); await wallet.initWallet(); await wallet.parseCreateWalletResult(createWalletResult); + if (credentials.passphrase != null) { + await wallet.setPassphrase(credentials.passphrase!); + wallet.seed = await createWalletResult.seed(wallet); + wallet.passphrase = await wallet.getPassphrase(); + } await wallet.init(createWalletResult.wi.address); return wallet; } @@ -172,7 +185,15 @@ abstract class ZanoWalletBase Future<void> parseCreateWalletResult(CreateWalletResult result) async { hWallet = result.walletId; - seed = result.seed; + seed = await result.seed(this); + keys = ZanoWalletKeys( + privateSpendKey: result.privateSpendKey, + privateViewKey: result.privateViewKey, + publicSpendKey: result.publicSpendKey, + publicViewKey: result.publicViewKey, + ); + passphrase = await getPassphrase(); + printV('setting hWallet = ${result.walletId}'); walletAddresses.address = result.wi.address; await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries); @@ -511,7 +532,7 @@ abstract class ZanoWalletBase // we can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready) if (!walletStatus.isInLongRefresh && walletStatus.walletState == 2) { final walletInfo = await getWalletInfo(); - seed = walletInfo.wiExtended.seed; + seed = await walletInfo.wiExtended.seed(this); keys = ZanoWalletKeys( privateSpendKey: walletInfo.wiExtended.spendPrivateKey, privateViewKey: walletInfo.wiExtended.viewPrivateKey, diff --git a/cw_zano/lib/zano_wallet_api.dart b/cw_zano/lib/zano_wallet_api.dart index 4947ff3b5..f2c5469c4 100644 --- a/cw_zano/lib/zano_wallet_api.dart +++ b/cw_zano/lib/zano_wallet_api.dart @@ -26,6 +26,7 @@ import 'package:ffi/ffi.dart'; import 'package:json_bigint/json_bigint.dart'; import 'package:monero/zano.dart' as zano; import 'package:monero/src/generated_bindings_zano.g.dart' as zanoapi; +import 'package:path/path.dart' as p; mixin ZanoWalletApi { static const _maxReopenAttempts = 5; @@ -45,7 +46,7 @@ mixin ZanoWalletApi { void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password); void closeWallet(int? walletToClose, {bool force = false}) async { - printV('close_wallet ${walletToClose ?? hWallet}'); + printV('close_wallet ${walletToClose ?? hWallet}: $force'); if (Platform.isWindows || force) { final result = await _closeWallet(walletToClose ?? hWallet); printV('close_wallet result $result'); @@ -53,10 +54,9 @@ mixin ZanoWalletApi { } } - bool isInit = false; + static bool isInit = false; Future<bool> initWallet() async { - // pathForWallet(name: , type: type) if (isInit) return true; final result = zano.PlainWallet_init("", "", 0); isInit = true; @@ -68,6 +68,68 @@ mixin ZanoWalletApi { return true; } + Future<Directory> getWalletDir() async { + final walletInfoResult = await getWalletInfo(); + return Directory(p.dirname(walletInfoResult.wi.path)); + } + + Future<File> _getWalletSecretsFile() async { + final dir = await getWalletDir(); + final file = File(p.join(dir.path, "zano-secrets.json.bin")); + return file; + } + + Future<Map<String, dynamic>> _getSecrets() async { + final file = await _getWalletSecretsFile(); + if (!file.existsSync()) { + return {}; + } + final data = file.readAsBytesSync(); + final b64 = convert.base64.encode(data); + final respStr = await invokeMethod("decrypt_data", {"buff": "$b64"}); + final resp = convert.json.decode(respStr); + final dataBytes = convert.base64.decode(resp["result"]["res_buff"] as String); + final dataStr = convert.utf8.decode(dataBytes); + final dataObject = convert.json.decode(dataStr); + return dataObject as Map<String, dynamic>; + } + + Future<void> _setSecrets(Map<String, dynamic> data) async { + final dataStr = convert.json.encode(data); + final b64 = convert.base64.encode(convert.utf8.encode(dataStr)); + final respStr = await invokeMethod("encrypt_data", {"buff": "$b64"}); + final resp = convert.json.decode(respStr); + final dataBytes = convert.base64.decode(resp["result"]["res_buff"] as String); + final file = await _getWalletSecretsFile(); + file.writeAsBytesSync(dataBytes); + } + + Future<String?> _getWalletSecret(String key) async { + final secrets = await _getSecrets(); + return secrets[key] as String?; + } + + Future<void> _setWalletSecret(String key, String value) async { + final secrets = await _getSecrets(); + secrets[key] = value; + await _setSecrets(secrets); + } + + Future<String?> getPassphrase() async { + return await _getWalletSecret("passphrase"); + } + + Future<void> setPassphrase(String passphrase) { + return _setWalletSecret("passphrase", passphrase); + } + + Future<String> getSeed() async { + final passphrase = await getPassphrase(); + final respStr = await invokeMethod("get_restore_info", {"seed_password": passphrase??""}); + final resp = convert.json.decode(respStr); + return resp["result"]["seed_phrase"] as String; + } + Future<GetWalletInfoResult> getWalletInfo() async { final json = await _getWalletInfo(hWallet); final result = GetWalletInfoResult.fromJson(jsonDecode(json)); @@ -192,7 +254,7 @@ mixin ZanoWalletApi { Future<StoreResult?> store() async { try { - final json = await invokeMethod('store', '{}'); + final json = await invokeMethod('store', {}); final map = jsonDecode(json) as Map<String, dynamic>?; _checkForErrors(map); return StoreResult.fromJson(map!['result'] as Map<String, dynamic>); @@ -247,12 +309,12 @@ mixin ZanoWalletApi { } final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>); openWalletCache[path] = result; - printV('create_wallet ${result.name} ${result.seed}'); + printV('create_wallet ${result.name}'); return result; } Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed, String? passphrase) async { - printV('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}'); + printV('restore_wallet path $path'); final json = zano.PlainWallet_restore(seed, path, password, passphrase??''); final map = jsonDecode(json) as Map<String, dynamic>?; if (map?['error'] != null) { @@ -274,8 +336,8 @@ mixin ZanoWalletApi { return result; } - Future<CreateWalletResult>loadWallet(String path, String password, [int attempt = 0]) async { - printV('load_wallet1 path $path password ${_shorten(password)}'); + Future<CreateWalletResult> loadWallet(String path, String password, [int attempt = 0]) async { + printV('load_wallet1 path $path'); final String json; try { json = zano.PlainWallet_open(path, password); @@ -283,7 +345,7 @@ mixin ZanoWalletApi { printV('error in loadingWallet $e'); rethrow; } - // printV('load_wallet2: $json'); + final map = jsonDecode(json) as Map<String, dynamic>?; if (map?['error'] != null) { final code = map?['error']!['code'] ?? ''; @@ -435,10 +497,8 @@ Future<String> _getWalletInfo(int hWallet) async { } Future<String> _setupNode(int hWallet, String nodeUrl) async { - final resp = await callSyncMethod("reset_connection_url", hWallet, nodeUrl); - printV(resp); - final resp2 = await callSyncMethod("run_wallet", hWallet, ""); - printV(resp2); + await callSyncMethod("reset_connection_url", hWallet, nodeUrl); + await callSyncMethod("run_wallet", hWallet, ""); return "OK"; } diff --git a/cw_zano/lib/zano_wallet_service.dart b/cw_zano/lib/zano_wallet_service.dart index 5bb7ed266..879402cff 100644 --- a/cw_zano/lib/zano_wallet_service.dart +++ b/cw_zano/lib/zano_wallet_service.dart @@ -14,7 +14,7 @@ import 'package:hive/hive.dart'; import 'package:monero/zano.dart' as zano; class ZanoNewWalletCredentials extends WalletCredentials { - ZanoNewWalletCredentials({required String name, String? password}) : super(name: name, password: password); + ZanoNewWalletCredentials({required String name, String? password, required String? passphrase}) : super(name: name, password: password, passphrase: passphrase); } class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials { diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock index 7ea7e6f86..74a87487e 100644 --- a/cw_zano/pubspec.lock +++ b/cw_zano/pubspec.lock @@ -476,8 +476,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7" - resolved-ref: "629fa4a346ca29d5ed18a2b44895b8858ba7c9f7" + ref: "65608c09e9093f1cd42c6afd8e9131016c82574b" + resolved-ref: "65608c09e9093f1cd42c6afd8e9131016c82574b" url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml index 136f38fde..111731f4f 100644 --- a/cw_zano/pubspec.yaml +++ b/cw_zano/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: 629fa4a346ca29d5ed18a2b44895b8858ba7c9f7 # monero_c hash + ref: 65608c09e9093f1cd42c6afd8e9131016c82574b # monero_c hash path: impls/monero.dart dev_dependencies: flutter_test: diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index a942be00d..e5853570e 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -202,7 +202,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo ); return Container(); }), - if (widget.privacySettingsViewModel.hasPassphraseOption(widget.isFromRestore)) + if (widget.privacySettingsViewModel.hasPassphraseOption) Padding( padding: EdgeInsets.all(24), child: Form( diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index a8a35096a..ab6762f8d 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -160,8 +160,11 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody> Widget _buildSeedTab(BuildContext context, bool isLegacySeed) { return Column( children: [ - if (isLegacySeedOnly || isLegacySeed) _buildHeightBox(), - const SizedBox(height: 20), + if (isLegacySeedOnly || isLegacySeed) + ...[ + _buildHeightBox(), + const SizedBox(height: 20), + ], (_buildPassphraseBox() ?? Container()), if (widget.walletKeysViewModel.passphrase.isNotEmpty) const SizedBox(height: 20), Expanded( diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index b6a9d58f9..14d7ad566 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -71,7 +71,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { bool get isNanoSeedTypeOptionsEnabled => [WalletType.nano].contains(type); - bool hasPassphraseOption(bool isRestore) => [ + bool get hasPassphraseOption => [ WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, @@ -80,7 +80,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { WalletType.tron, WalletType.monero, WalletType.wownero, - if (isRestore) WalletType.zano, + WalletType.zano, ].contains(type); @computed diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index f09f972cd..aa933eadc 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -175,6 +175,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { return zano!.createZanoNewWalletCredentials( name: name, password: walletPassword, + passphrase: passphrase, ); case WalletType.none: throw Exception('Unexpected type: ${type.toString()}'); diff --git a/lib/zano/cw_zano.dart b/lib/zano/cw_zano.dart index a1cae19c8..19fec04e4 100644 --- a/lib/zano/cw_zano.dart +++ b/lib/zano/cw_zano.dart @@ -53,8 +53,8 @@ class CWZano extends Zano { } @override - WalletCredentials createZanoNewWalletCredentials({required String name, required String? password}) { - return ZanoNewWalletCredentials(name: name, password: password); + WalletCredentials createZanoNewWalletCredentials({required String name, required String? password, required String? passphrase}) { + return ZanoNewWalletCredentials(name: name, password: password, passphrase: passphrase); } @override diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index eefa9d5ef..ec2cb4908 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -8,7 +8,7 @@ if [[ ! -d "monero_c/.git" ]]; then git clone https://github.com/mrcyjanek/monero_c --branch master monero_c cd monero_c - git checkout 629fa4a346ca29d5ed18a2b44895b8858ba7c9f7 + git checkout 65608c09e9093f1cd42c6afd8e9131016c82574b git reset --hard git submodule update --init --force --recursive ./apply_patches.sh monero diff --git a/tool/configure.dart b/tool/configure.dart index 3a49438e4..7fb72d5e4 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1443,7 +1443,7 @@ abstract class Zano { List<String> getWordList(String language); WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic}); - WalletCredentials createZanoNewWalletCredentials({required String name, required String? password}); + WalletCredentials createZanoNewWalletCredentials({required String name, required String? password, required String? passphrase}); Map<String, String> getKeys(Object wallet); Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency}); double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});