Skip to content

Commit 1094b78

Browse files
committed
fix: debugging keystone integration
feat: support for zpub view only restore
1 parent 5a27f0a commit 1094b78

File tree

12 files changed

+207
-16
lines changed

12 files changed

+207
-16
lines changed

cw_bitcoin/lib/bitcoin_wallet.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:cw_bitcoin/electrum_derivations.dart';
1313
import 'package:cw_bitcoin/electrum_wallet.dart';
1414
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
1515
import 'package:cw_bitcoin/payjoin/manager.dart';
16+
import 'package:cw_bitcoin/utils.dart';
1617
import 'package:cw_bitcoin/payjoin/storage.dart';
1718
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
1819
import 'package:cw_bitcoin/psbt/signer.dart';
@@ -241,7 +242,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
241242

242243
return BitcoinWallet(
243244
mnemonic: mnemonic,
244-
xpub: keysData.xPub,
245+
xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null,
245246
password: password,
246247
passphrase: passphrase,
247248
walletInfo: walletInfo,

cw_bitcoin/lib/bitcoin_wallet_service.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
44
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
55
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
66
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
7+
import 'package:cw_bitcoin/utils.dart';
78
import 'package:cw_core/encryption_file_utils.dart';
89
import 'package:cw_core/payjoin_session.dart';
910
import 'package:cw_core/unspent_coins_info.dart';
@@ -150,9 +151,12 @@ class BitcoinWalletService extends WalletService<
150151
credentials.walletInfo?.network = network.value;
151152
credentials.walletInfo?.derivationInfo?.derivationPath =
152153
credentials.hwAccountData.derivationPath;
154+
155+
final xpub = convertZpubToXpub(credentials.hwAccountData.xpub!);
156+
153157
final wallet = await BitcoinWallet(
154158
password: credentials.password!,
155-
xpub: credentials.hwAccountData.xpub,
159+
xpub: xpub,
156160
walletInfo: credentials.walletInfo!,
157161
unspentCoinsInfo: unspentCoinsInfoSource,
158162
networkParam: network,
@@ -166,13 +170,15 @@ class BitcoinWalletService extends WalletService<
166170

167171
@override
168172
Future<BitcoinWallet> restoreFromKeys(BitcoinWalletFromKeysCredentials credentials,
169-
{bool? isTestnet}) async {
173+
{bool? isTestnet}) async {
170174
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
171175
credentials.walletInfo?.network = network.value;
172176

177+
final xpub = convertZpubToXpub(credentials.xpub);
178+
173179
final wallet = await BitcoinWallet(
174180
password: credentials.password!,
175-
xpub: credentials.xpub,
181+
xpub: xpub,
176182
walletInfo: credentials.walletInfo!,
177183
unspentCoinsInfo: unspentCoinsInfoSource,
178184
networkParam: network,

cw_bitcoin/lib/utils.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:typed_data';
12
import 'package:bitcoin_base/bitcoin_base.dart';
23
import 'package:blockchain_utils/blockchain_utils.dart';
34

@@ -52,3 +53,45 @@ String generateP2TRAddress({
5253
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
5354
.toTaprootAddress()
5455
.toAddress(network);
56+
57+
String convertZpubToXpub(String zpub) {
58+
try {
59+
final decoded = Base58Decoder.checkDecode(zpub);
60+
61+
if (decoded.length < 4) {
62+
throw ArgumentError('Invalid extended public key length');
63+
}
64+
65+
final versionBytes = decoded.sublist(0, 4);
66+
final zpubVersionBytes = [0x04, 0xb2, 0x47, 0x46]; // zpub mainnet version
67+
final zpubTestnetVersionBytes = [0x04, 0x5f, 0x1c, 0xf6]; // vpub testnet version
68+
69+
bool isZpub = listEquals(versionBytes, zpubVersionBytes);
70+
bool isVpub = listEquals(versionBytes, zpubTestnetVersionBytes);
71+
72+
if (!isZpub && !isVpub) {
73+
return zpub;
74+
}
75+
76+
final xpubVersionBytes = isZpub ?
77+
[0x04, 0x88, 0xb2, 0x1e] : // xpub mainnet
78+
[0x04, 0x35, 0x87, 0xcf]; // tpub testnet
79+
80+
final newExtendedKey = Uint8List.fromList([
81+
...xpubVersionBytes,
82+
...decoded.sublist(4),
83+
]);
84+
85+
return Base58Encoder.checkEncode(newExtendedKey);
86+
} catch (e) {
87+
throw ArgumentError('Failed to convert zpub to xpub: $e');
88+
}
89+
}
90+
91+
bool listEquals<T>(List<T> a, List<T> b) {
92+
if (a.length != b.length) return false;
93+
for (int i = 0; i < a.length; i++) {
94+
if (a[i] != b[i]) return false;
95+
}
96+
return true;
97+
}

ios/Podfile.lock

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ PODS:
6969
- Flutter
7070
- image_picker_ios (0.0.1):
7171
- Flutter
72-
- image_picker_ios (0.0.1):
73-
- Flutter
7472
- in_app_review (2.0.0):
7573
- Flutter
7674
- integration_test (0.0.1):
@@ -87,9 +85,9 @@ PODS:
8785
- reown_yttrium (0.0.1):
8886
- Flutter
8987
- YttriumWrapper (= 0.8.35)
90-
- SDWebImage (5.20.0):
91-
- SDWebImage/Core (= 5.20.0)
92-
- SDWebImage/Core (5.20.0)
88+
- SDWebImage (5.21.2):
89+
- SDWebImage/Core (= 5.21.2)
90+
- SDWebImage/Core (5.21.2)
9391
- sensitive_clipboard (0.0.1):
9492
- Flutter
9593
- share_plus (0.0.1):
@@ -256,7 +254,7 @@ SPEC CHECKSUMS:
256254
payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e
257255
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
258256
reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58
259-
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
257+
SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
260258
sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b
261259
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
262260
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7

lib/di.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
3636
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
3737
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
3838
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
39+
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
3940
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
4041
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
4142
import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart';
@@ -1562,9 +1563,12 @@ Future<void> setup({
15621563
getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get<BackgroundSyncLogsViewModel>()));
15631564

15641565
getIt.registerFactory(() => SocketHealthLogsViewModel());
1566+
15651567
getIt.registerFactory(() => DevSocketHealthLogsPage(getIt.get<SocketHealthLogsViewModel>()));
15661568

15671569
getIt.registerFactory(() => DevNetworkRequests());
1570+
1571+
getIt.registerFactory(() => DevQRToolsPage());
15681572

15691573
getIt.registerFactory(() => StartTorPage(StartTorViewModel(),));
15701574

lib/router.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
3939
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
4040
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
4141
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
42+
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
4243
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
4344
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
4445
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
@@ -920,6 +921,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
920921
builder: (_) => getIt.get<DevSocketHealthLogsPage>(),
921922
);
922923

924+
case Routes.devQRTools:
925+
return MaterialPageRoute<void>(
926+
builder: (_) => getIt.get<DevQRToolsPage>(),
927+
);
928+
923929
case Routes.devNetworkRequests:
924930
return MaterialPageRoute<void>(
925931
builder: (_) => getIt.get<DevNetworkRequests>(),

lib/routes.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class Routes {
123123
static const devBackgroundSyncLogs = '/dev/background_sync_logs';
124124
static const devSocketHealthLogs = '/dev/socket_health_logs';
125125
static const devNetworkRequests = '/dev/network_requests';
126+
static const devQRTools = '/dev/qr_tools';
126127

127128
static const signPage = '/sign_page';
128129
static const connectDevices = '/device/connect';
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import 'package:cake_wallet/src/screens/base_page.dart';
2+
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
3+
import 'package:cake_wallet/view_model/dev/qr_tools_view_model.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_mobx/flutter_mobx.dart';
6+
7+
class DevQRToolsPage extends BasePage {
8+
@override
9+
String? get title => "[dev] *QR tools";
10+
11+
final QRToolsViewModel viewModel = QRToolsViewModel();
12+
13+
late final textCtrl = TextEditingController(text: viewModel.input);
14+
15+
@override
16+
Widget body(BuildContext context) {
17+
return Container(
18+
child: Column(
19+
mainAxisSize: MainAxisSize.min,
20+
children: [
21+
TextField(
22+
controller: textCtrl,
23+
maxLines: 8,
24+
onChanged: (value) {
25+
viewModel.input = value;
26+
}
27+
),
28+
Observer(
29+
builder: (_) {
30+
return Expanded(
31+
child: JsonExplorer(data: viewModel.data, title: "result"),
32+
);
33+
},
34+
),
35+
],
36+
),
37+
);
38+
}
39+
}

lib/src/screens/settings/other_settings_page.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ class OtherSettingsPage extends BasePage {
111111
handler: (BuildContext context) =>
112112
Navigator.of(context).pushNamed(Routes.devNetworkRequests),
113113
),
114+
if (FeatureFlag.hasDevOptions)
115+
SettingsCellWithArrow(
116+
title: '[dev] *QR tools',
117+
handler: (BuildContext context) =>
118+
Navigator.of(context).pushNamed(Routes.devQRTools),
119+
),
114120
Spacer(),
115121
SettingsVersionCell(
116122
title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import 'dart:convert';
2+
3+
import 'package:flutter/foundation.dart';
4+
import 'package:ur/cbor_lite.dart';
5+
import 'package:ur/ur.dart';
6+
import 'package:mobx/mobx.dart';
7+
import 'package:ur/ur_decoder.dart';
8+
9+
part 'qr_tools_view_model.g.dart';
10+
11+
class QRToolsViewModel = QRToolsViewModelBase with _$QRToolsViewModel;
12+
13+
enum DevQrType {
14+
zpub,
15+
xpub,
16+
bbqr,
17+
bcur,
18+
unknown,
19+
}
20+
21+
abstract class QRToolsViewModelBase with Store {
22+
@observable
23+
String input = '';
24+
25+
26+
27+
@computed
28+
Map<String, dynamic> get data => _getResult(input);
29+
30+
Uint8List _decodeCBOR(Uint8List cbor) {
31+
final cborDecoder = CBORDecoder(cbor);
32+
final out = cborDecoder.decodeBytes();
33+
return out.$1;
34+
}
35+
36+
37+
Map<String, dynamic> _getResult(String input) {
38+
try {
39+
final decoder = URDecoder();
40+
for (var part in input.split('\n')) {
41+
part = part.toLowerCase();
42+
part = part.trim();
43+
if (part.startsWith('-')) {
44+
part = part.substring(1).trim();
45+
}
46+
if (!part.startsWith('ur:')) {
47+
continue;
48+
}
49+
decoder.receivePart(part);
50+
}
51+
return {
52+
"result": (decoder.result != null) ? switch (decoder.result.runtimeType) {
53+
UR => {
54+
"cbor": (decoder.result as UR).cbor,
55+
"_cbor.decode.base64": base64.encode(_decodeCBOR((decoder.result as UR).cbor)),
56+
"type": (decoder.result as UR).type,
57+
"toString": (decoder.result as UR).toString(),
58+
},
59+
_ => "unknown type: ${decoder.result.runtimeType}"
60+
} : null,
61+
"expectedType": decoder.expectedType,
62+
"estimatedPercentComplete": decoder.estimatedPercentComplete(),
63+
"processedPartsCount": decoder.processedPartsCount(),
64+
"expectedPartCount": decoder.expectedPartCount(),
65+
"isComplete": decoder.isComplete(),
66+
"isFailure": decoder.isFailure(),
67+
"isSuccess": decoder.isSuccess(),
68+
"lastPartIndexes": decoder.lastPartIndexes(),
69+
"receivedPartIndexes": decoder.receivedPartIndexes(),
70+
"resultError": decoder.resultError(),
71+
"resultMessage": decoder.resultMessage(),
72+
"toString": decoder.toString(),
73+
74+
};
75+
} catch (e) {
76+
return {
77+
"error": e.toString(),
78+
};
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)