Files
hash-wallet/cw_core/lib/wallet_keys_file.dart
T
fb33a6f23d Cw 688 avoid wallet file corruption (#1582)
* CW-688 Store Seed and keys in .keys file

* CW-688 Open wallet from keys in .keys file and migrate wallets using the old file

* CW-688 Open wallet from keys in .keys file and migrate wallets using the old file

* CW-688 Restore .keys file from .keys.backup

* CW-688 Restore .keys file from .keys.backup

* CW-688 Move saving .keys files into the save function instead of the service

* CW-688 Handle corrupt wallets

* CW-688 Handle corrupt wallets

* CW-688 Remove code duplication

* CW-688 Reduce cache dependency

* wrap any file reading/writing function with try/catch [skip ci]

---------

Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
2024-08-09 23:15:30 +03:00

111 lines
3.9 KiB
Dart

import 'dart:convert';
import 'dart:developer' as dev;
import 'dart:io';
import 'package:cw_core/balance.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
mixin WalletKeysFile<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
TransactionType extends TransactionInfo>
on WalletBase<BalanceType, HistoryType, TransactionType> {
Future<String> makePath() => pathForWallet(name: walletInfo.name, type: walletInfo.type);
// this needs to be overridden
WalletKeysData get walletKeysData;
Future<String> makeKeysFilePath() async => "${await makePath()}.keys";
Future<void> saveKeysFile(String password, [bool isBackup = false]) async {
try {
final rootPath = await makeKeysFilePath();
final path = "$rootPath${isBackup ? ".backup" : ""}";
dev.log("Saving .keys file '$path'");
await write(path: path, password: password, data: walletKeysData.toJSON());
} catch (_) {}
}
static Future<void> createKeysFile(
String name, WalletType type, String password, WalletKeysData walletKeysData,
[bool withBackup = true]) async {
try {
final rootPath = await pathForWallet(name: name, type: type);
final path = "$rootPath.keys";
dev.log("Saving .keys file '$path'");
await write(path: path, password: password, data: walletKeysData.toJSON());
if (withBackup) {
dev.log("Saving .keys.backup file '$path.backup'");
await write(path: "$path.backup", password: password, data: walletKeysData.toJSON());
}
} catch (_) {}
}
static Future<bool> hasKeysFile(String name, WalletType type) async {
try {
final path = await pathForWallet(name: name, type: type);
return File("$path.keys").existsSync() || File("$path.keys.backup").existsSync();
} catch (_) {
return false;
}
}
static Future<WalletKeysData> readKeysFile(String name, WalletType type, String password) async {
final path = await pathForWallet(name: name, type: type);
var readPath = "$path.keys";
try {
if (!File(readPath).existsSync()) throw Exception("No .keys file found for $name $type");
final jsonSource = await read(path: readPath, password: password);
final data = json.decode(jsonSource) as Map<String, dynamic>;
return WalletKeysData.fromJSON(data);
} catch (e) {
dev.log("Failed to read .keys file. Trying .keys.backup file...");
readPath = "$readPath.backup";
if (!File(readPath).existsSync())
throw Exception("No .keys nor a .keys.backup file found for $name $type");
final jsonSource = await read(path: readPath, password: password);
final data = json.decode(jsonSource) as Map<String, dynamic>;
final keysData = WalletKeysData.fromJSON(data);
dev.log("Restoring .keys from .keys.backup");
createKeysFile(name, type, password, keysData, false);
return keysData;
}
}
}
class WalletKeysData {
final String? privateKey;
final String? mnemonic;
final String? altMnemonic;
final String? passphrase;
final String? xPub;
WalletKeysData({this.privateKey, this.mnemonic, this.altMnemonic, this.passphrase, this.xPub});
String toJSON() => jsonEncode({
"privateKey": privateKey,
"mnemonic": mnemonic,
if (altMnemonic != null) "altMnemonic": altMnemonic,
if (passphrase != null) "passphrase": passphrase,
if (xPub != null) "xPub": xPub
});
static WalletKeysData fromJSON(Map<String, dynamic> json) => WalletKeysData(
privateKey: json["privateKey"] as String?,
mnemonic: json["mnemonic"] as String?,
altMnemonic: json["altMnemonic"] as String?,
passphrase: json["passphrase"] as String?,
xPub: json["xPub"] as String?,
);
}