CW-1092-restoring-from-backup-doesnt-maintain-hardware-wallets (#2319)
* feat: add hardware wallet verification during backup restoration * style: improve readability of verifyHardwareWallets in backup_service_v3.dart
Konstantin Ullrich committed
Jun 17, 2025 at 00:31 UTC
85d3e727e20203b411e3225396f6ca00d280f18c
2 files changed
+42
-5
lib/core/backup_service.dart
+6
-4
@@ -110,7 +110,7 @@ class $BackupService {
110
}
111
112
Future<void> verifyWallets() async {
113
- final walletInfoSource = await _reloadHiveWalletInfoBox();
113
+ final walletInfoSource = await reloadHiveWalletInfoBox();
114
correctWallets =
115
walletInfoSource.values.where((info) => availableWalletTypes.contains(info.type)).toList();
116
@@ -119,7 +119,7 @@ class $BackupService {
119
}
120
}
121
122
- Future<Box<WalletInfo>> _reloadHiveWalletInfoBox() async {
122
+ Future<Box<WalletInfo>> reloadHiveWalletInfoBox() async {
123
final appDir = await getAppDir();
124
await CakeHive.close();
125
CakeHive.init(appDir.path);
@@ -288,13 +288,15 @@ class $BackupService {
288
return {
289
'name': walletInfo.name,
290
'type': walletInfo.type.toString(),
291
- 'password': await keyService.getWalletPassword(walletName: walletInfo.name)
291
+ 'password': await keyService.getWalletPassword(walletName: walletInfo.name),
292
+ 'hardwareWalletType': walletInfo.hardwareWalletType?.index,
293
};
294
} catch (e) {
295
return {
296
'name': walletInfo.name,
297
'type': walletInfo.type.toString(),
297
- 'password': ''
298
+ 'password': '',
299
+ 'hardwareWalletType': walletInfo.hardwareWalletType?.index,
300
};
301
}
302
}));
lib/core/backup_service_v3.dart
+36
-1
@@ -10,6 +10,7 @@ import 'package:cake_wallet/utils/package_info.dart';
10
import 'package:crypto/crypto.dart';
11
import 'package:cw_core/root_dir.dart';
12
import 'package:cw_core/utils/print_verbose.dart';
13
+import 'package:cw_core/wallet_info.dart';
14
import 'package:flutter/foundation.dart';
15
16
enum BackupVersion {
@@ -305,6 +306,7 @@ class BackupServiceV3 extends $BackupService {
306
307
// Continue importing the backup the old way
308
await super.verifyWallets();
309
+ await verifyHardwareWallets(password);
310
await super.importKeychainDumpV2(password);
311
await super.importPreferencesDump();
312
await super.importTransactionDescriptionDump();
@@ -313,6 +315,39 @@ class BackupServiceV3 extends $BackupService {
315
decryptedData.deleteSync();
316
}
317
318
+ Future<void> verifyHardwareWallets(String password,
319
+ {String keychainSalt = secrets.backupKeychainSalt}) async {
320
+ final walletInfoSource = await reloadHiveWalletInfoBox();
321
+ final appDir = await getAppDir();
322
+ final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
323
+ final decryptedKeychainDumpFileData = await decryptV2(
324
+ keychainDumpFile.readAsBytesSync(), '$keychainSalt$password');
325
+ final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
326
+ as Map<String, dynamic>;
327
+ final keychainWalletsInfo = keychainJSON['wallets'] as List;
328
+
329
+ final expectedHardwareWallets = keychainWalletsInfo
330
+ .where((e) =>
331
+ (e as Map<String, dynamic>).containsKey("hardwareWalletType") &&
332
+ e["hardwareWalletType"] != null)
333
+ .toList();
334
+
335
+ for (final expectedHardwareWallet in expectedHardwareWallets) {
336
+ final info = expectedHardwareWallet as Map<String, dynamic>;
337
+ final actualWalletInfo = walletInfoSource.values
338
+ .where((e) =>
339
+ e.name == info['name'] && e.type.toString() == info['type'])
340
+ .firstOrNull;
341
+ if (actualWalletInfo != null &&
342
+ info["hardwareWalletType"] !=
343
+ actualWalletInfo.hardwareWalletType?.index) {
344
+ actualWalletInfo.hardwareWalletType =
345
+ HardwareWalletType.values[info["hardwareWalletType"] as int];
346
+ await actualWalletInfo.save();
347
+ }
348
+ }
349
+ }
350
+
351
Future<File> exportBackupFileV3(String password, {String nonce = secrets.backupSalt}) async {
352
final metadata = BackupMetadata(
353
version: BackupVersion.v3,
@@ -467,4 +502,4 @@ This backup was created on ${DateTime.now().toIso8601String()}
502
file.writeAsBytesSync(data);
503
return file;
504
}
470
-}
\ No newline at end of file
505
+}