feat(dev): add secrets dev screen (used to recover corrupted wallet) (#2234)
* feat(dev): add secrets dev screen (used to recover corrupted wallet) * fix(dev): decode only password fields
cyan committed
Apr 30, 2025 at 08:29 UTC
da8a82a10f1d915342bcf73e7a4965a39dea2b1d
7 files changed
+173
lib/core/backup_service.dart
+16
@@ -251,6 +251,22 @@ class $BackupService {
251
await importWalletKeychainInfo(info);
252
});
253
254
+ for (var key in (keychainJSON['_all'] as Map<String, dynamic>).keys) {
255
+ try {
256
+ if (!key.startsWith('MONERO_WALLET_')) continue;
257
+ final decodedPassword = decodeWalletPassword(password: keychainJSON['_all'][key].toString());
258
+ final walletName = key.split('_WALLET_')[1];
259
+ final walletType = key.split('_WALLET_')[0].toLowerCase();
260
+ await importWalletKeychainInfo({
261
+ 'name': walletName,
262
+ 'type': "WalletType.$walletType",
263
+ 'password': decodedPassword,
264
+ });
265
+ } catch (e) {
266
+ printV('Error importing wallet ($key) password: $e');
267
+ }
268
+ }
269
+
270
keychainDumpFile.deleteSync();
271
}
272
lib/di.dart
+6
@@ -33,12 +33,14 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
33
import 'package:cake_wallet/haven/cw_haven.dart';
34
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
35
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
36
+import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
37
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
38
import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
39
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
40
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart';
41
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
42
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
43
+import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
44
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
45
import 'package:cake_wallet/view_model/link_view_model.dart';
46
import 'package:cake_wallet/tron/tron.dart';
@@ -920,6 +922,8 @@ Future<void> setup({
922
923
getIt.registerFactory(() => DevSharedPreferences());
924
925
+ getIt.registerFactory(() => DevSecurePreferences());
926
+
927
getIt.registerFactoryParam<WalletSeedPage, bool, void>((bool isWalletCreated, _) =>
928
WalletSeedPage(getIt.get<WalletSeedViewModel>(), isNewWalletCreated: isWalletCreated));
929
@@ -1463,6 +1467,8 @@ Future<void> setup({
1467
getIt.registerFactory(() => DevMoneroCallProfilerPage());
1468
1469
getIt.registerFactory(() => DevSharedPreferencesPage(getIt.get<DevSharedPreferences>()));
1470
+
1471
+ getIt.registerFactory(() => DevSecurePreferencesPage(getIt.get<DevSecurePreferences>()));
1472
1473
getIt.registerFactory(() => BackgroundSyncLogsViewModel());
1474
lib/router.dart
+6
@@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
36
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
37
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
38
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
39
+import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
40
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
41
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
42
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
@@ -853,6 +854,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
854
builder: (_) => getIt.get<DevMoneroCallProfilerPage>(),
855
);
856
857
+ case Routes.devSecurePreferences:
858
+ return MaterialPageRoute<void>(
859
+ builder: (_) => getIt.get<DevSecurePreferencesPage>(),
860
+ );
861
+
862
default:
863
return MaterialPageRoute<void>(
864
builder: (_) => Scaffold(
lib/routes.dart
+1
@@ -115,6 +115,7 @@ class Routes {
115
static const devMoneroBackgroundSync = '/dev/monero_background_sync';
116
static const devMoneroCallProfiler = '/dev/monero_call_profiler';
117
static const devSharedPreferences = '/dev/shared_preferences';
118
+ static const devSecurePreferences = '/dev/secure_preferences';
119
static const devBackgroundSyncLogs = '/dev/background_sync_logs';
120
121
static const signPage = '/sign_page';
lib/src/screens/dev/secure_preferences_page.dart
new
+63
@@ -0,0 +1,63 @@
1
+import 'package:cake_wallet/src/screens/base_page.dart';
2
+import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
3
+import 'package:flutter/material.dart';
4
+import 'package:flutter/services.dart';
5
+import 'package:flutter_mobx/flutter_mobx.dart';
6
+
7
+class DevSecurePreferencesPage extends BasePage {
8
+ final DevSecurePreferences viewModel;
9
+
10
+ DevSecurePreferencesPage(this.viewModel);
11
+
12
+ @override
13
+ String? get title => "[dev] secure preferences";
14
+
15
+ @override
16
+ Widget body(BuildContext context) {
17
+ return Observer(
18
+ builder: (_) {
19
+ if (viewModel.values.isEmpty) {
20
+ return Center(child: Text("No secure preferences found"));
21
+ }
22
+ final keys = viewModel.keys;
23
+ Map<String, dynamic> values = {};
24
+ for (final key in keys) {
25
+ values[key] = viewModel.get(key);
26
+ }
27
+ Map<String, PreferenceType> types = {};
28
+ for (final key in keys) {
29
+ types[key] = viewModel.getPreferenceType(key);
30
+ }
31
+ return ListView.builder(
32
+ itemCount: keys.length,
33
+ itemBuilder: (context, index) {
34
+ final key = keys[index];
35
+ final type = types[key]!;
36
+ return ListTile(
37
+ onTap: () {
38
+ Clipboard.setData(ClipboardData(text: key + ": " + values[key].toString()));
39
+ },
40
+ title: switch (type) {
41
+ PreferenceType.bool => Text(key, style: TextStyle(color: Colors.blue)),
42
+ PreferenceType.int => Text(key, style: TextStyle(color: Colors.green)),
43
+ PreferenceType.double => Text(key, style: TextStyle(color: Colors.yellow)),
44
+ PreferenceType.listString => Text(key, style: TextStyle(color: Colors.purple)),
45
+ PreferenceType.string => Text(key),
46
+ PreferenceType.unknown => Text(key),
47
+ },
48
+ subtitle: switch (type) {
49
+ PreferenceType.bool => Text("bool: ${values[key]}"),
50
+ PreferenceType.int => Text("int: ${values[key]}"),
51
+ PreferenceType.double => Text("double: ${values[key]}"),
52
+ PreferenceType.listString => values[key].isEmpty as bool ? Text("listString: []") : Text("listString:\n- ${values[key].join("\n- ")}"),
53
+ PreferenceType.string => Text("string: ${values[key]}"),
54
+ PreferenceType.unknown => Text("UNKNOWN(${values[key].runtimeType}): ${values[key]}"),
55
+ },
56
+ );
57
+ },
58
+ );
59
+ },
60
+ );
61
+ }
62
+
63
+}
\ No newline at end of file
lib/src/screens/settings/other_settings_page.dart
+6
@@ -81,6 +81,12 @@ class OtherSettingsPage extends BasePage {
81
handler: (BuildContext context) =>
82
Navigator.of(context).pushNamed(Routes.devSharedPreferences),
83
),
84
+ if (FeatureFlag.hasDevOptions)
85
+ SettingsCellWithArrow(
86
+ title: '[dev] secure storage preferences',
87
+ handler: (BuildContext context) =>
88
+ Navigator.of(context).pushNamed(Routes.devSecurePreferences),
89
+ ),
90
if (FeatureFlag.hasDevOptions)
91
SettingsCellWithArrow(
92
title: '[dev] background sync logs',
lib/view_model/dev/secure_preferences.dart
new
+75
@@ -0,0 +1,75 @@
1
+import 'package:cake_wallet/core/secure_storage.dart';
2
+import 'package:cake_wallet/entities/encrypt.dart';
3
+import 'package:mobx/mobx.dart';
4
+
5
+part 'secure_preferences.g.dart';
6
+
7
+class DevSecurePreferences = DevSecurePreferencesBase with _$DevSecurePreferences;
8
+
9
+enum PreferenceType {
10
+ unknown,
11
+ string,
12
+ int,
13
+ double,
14
+ bool,
15
+ listString
16
+}
17
+
18
+abstract class DevSecurePreferencesBase with Store {
19
+ DevSecurePreferencesBase() {
20
+ secureStorageShared.readAll().then((value) {
21
+ values = value;
22
+ });
23
+ }
24
+
25
+ @observable
26
+ Map<String, String> values = {};
27
+
28
+ @computed
29
+ List<String> get keys => values.keys.toList()..sort();
30
+
31
+ @action
32
+ Future<void> delete(String key) async {
33
+
34
+ }
35
+
36
+ dynamic get(String key) {
37
+ if (!values.containsKey(key)) {
38
+ return null;
39
+ }
40
+ if (!key.startsWith("MONERO_WALLET_")) return values[key]!;
41
+ try {
42
+ final decodedPassword = decodeWalletPassword(password: values[key]!);
43
+ return values[key]! + "\n\nDecoded: $decodedPassword";
44
+ } catch (e) {
45
+ return values[key]! +"\n$e";
46
+ }
47
+ }
48
+
49
+ Future<void> set(String key, PreferenceType type, dynamic value) async {
50
+
51
+ }
52
+
53
+ PreferenceType getPreferenceType(String key) {
54
+ if (!values.containsKey(key)) {
55
+ return PreferenceType.unknown;
56
+ }
57
+ final value = values[key];
58
+ if (value is String) {
59
+ return PreferenceType.string;
60
+ }
61
+ if (value is bool) {
62
+ return PreferenceType.bool;
63
+ }
64
+ if (value is int) {
65
+ return PreferenceType.int;
66
+ }
67
+ if (value is double) {
68
+ return PreferenceType.double;
69
+ }
70
+ if (value is List<String>) {
71
+ return PreferenceType.listString;
72
+ }
73
+ return PreferenceType.unknown;
74
+ }
75
+}