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
This commit is contained in:
cyan 2025-02-18 23:28:27 +01:00 committed by GitHub
parent 9d6f985a59
commit dd8ccee1ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 143 additions and 41 deletions

View file

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

View file

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

View file

@ -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,

View file

@ -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";
}

View file

@ -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 {

View file

@ -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"

View file

@ -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: