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 +}