electrum updates (#1449)

* hotfixes * copy over the rest of the fixes * use hardened derivation path everywhere * correct balance path for electrum * revert index nullability and correct balance path for all cases * only save wallet info if we changed it

Matthew Fosse committed Jun 18, 2024 at 07:08 UTC 591342ec6a0fd26e3163e8536fd14789edeb2aa4
10 files changed +66 -46
cw_bitcoin/lib/bitcoin_wallet.dart
+2 -1
@@ -6,6 +6,7 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
6 import 'package:convert/convert.dart';
7 import 'package:cw_bitcoin/bitcoin_address_record.dart';
8 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
9 +import 'package:cw_bitcoin/electrum_derivations.dart';
10 import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
11 import 'package:cw_bitcoin/electrum_balance.dart';
12 import 'package:cw_bitcoin/electrum_wallet.dart';
@@ -150,7 +151,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
151 );
152
153 // set the default if not present:
153 - walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/0";
154 + walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path;
155 walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
156
157 Uint8List? seedBytes = null;
cw_bitcoin/lib/electrum_derivations.dart
+3
@@ -108,3 +108,6 @@ Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
108 ),
109 ],
110 };
111 +
112 +
113 +String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!;
\ No newline at end of file
cw_bitcoin/lib/electrum_wallet.dart
+2 -1
@@ -17,6 +17,7 @@ import 'package:cw_bitcoin/bitcoin_unspent.dart';
17 import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
18 import 'package:cw_bitcoin/electrum.dart';
19 import 'package:cw_bitcoin/electrum_balance.dart';
20 +import 'package:cw_bitcoin/electrum_derivations.dart';
21 import 'package:cw_bitcoin/electrum_transaction_history.dart';
22 import 'package:cw_bitcoin/electrum_transaction_info.dart';
23 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@@ -133,7 +134,7 @@ abstract class ElectrumWalletBase
134 return currency == CryptoCurrency.bch
135 ? bitcoinCashHDWallet(seedBytes)
136 : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
136 - .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? "m/0'"));
137 + .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path));
138 }
139
140 return bitcoin.HDWallet.fromBase58(xpub!);
cw_bitcoin/lib/electrum_wallet_snapshot.dart
+2 -1
@@ -2,6 +2,7 @@ import 'dart:convert';
2 import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:cw_bitcoin/bitcoin_address_record.dart';
4 import 'package:cw_bitcoin/electrum_balance.dart';
5 +import 'package:cw_bitcoin/electrum_derivations.dart';
6 import 'package:cw_core/pathForWallet.dart';
7 import 'package:cw_core/wallet_info.dart';
8 import 'package:cw_core/utils/file.dart';
@@ -71,7 +72,7 @@ class ElectrumWalletSnapshot {
72
73 final derivationType = DerivationType
74 .values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
74 - final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
75 + final derivationPath = data['derivationPath'] as String? ?? electrum_path;
76
77 try {
78 regularAddressIndexByType = {
cw_bitcoin/lib/utils.dart
+21 -15
@@ -5,58 +5,64 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
5 import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
6 import 'package:hex/hex.dart';
7
8 -bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, int? index}) {
9 - final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
8 +bitcoin.PaymentData generatePaymentData({
9 + required bitcoin.HDWallet hd,
10 + required int index,
11 +}) {
12 + final pubKey = hd.derive(index).pubKey!;
13 return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
14 }
15
13 -ECPrivate generateECPrivate(
14 - {required bitcoin.HDWallet hd, required BasedUtxoNetwork network, int? index}) {
15 - final wif = index != null ? hd.derive(index).wif! : hd.wif!;
16 +ECPrivate generateECPrivate({
17 + required bitcoin.HDWallet hd,
18 + required BasedUtxoNetwork network,
19 + required int index,
20 +}) {
21 + final wif = hd.derive(index).wif!;
22 return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
23 }
24
25 String generateP2WPKHAddress({
26 required bitcoin.HDWallet hd,
27 required BasedUtxoNetwork network,
22 - int? index,
28 + required int index,
29 }) {
24 - final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
30 + final pubKey = hd.derive(index).pubKey!;
31 return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
32 }
33
34 String generateP2SHAddress({
35 required bitcoin.HDWallet hd,
36 required BasedUtxoNetwork network,
31 - int? index,
37 + required int index,
38 }) {
33 - final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
39 + final pubKey = hd.derive(index).pubKey!;
40 return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
41 }
42
43 String generateP2WSHAddress({
44 required bitcoin.HDWallet hd,
45 required BasedUtxoNetwork network,
40 - int? index,
46 + required int index,
47 }) {
42 - final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
48 + final pubKey = hd.derive(index).pubKey!;
49 return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
50 }
51
52 String generateP2PKHAddress({
53 required bitcoin.HDWallet hd,
54 required BasedUtxoNetwork network,
49 - int? index,
55 + required int index,
56 }) {
51 - final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
57 + final pubKey = hd.derive(index).pubKey!;
58 return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
59 }
60
61 String generateP2TRAddress({
62 required bitcoin.HDWallet hd,
63 required BasedUtxoNetwork network,
58 - int? index,
64 + required int index,
65 }) {
60 - final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
66 + final pubKey = hd.derive(index).pubKey!;
67 return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
68 }
lib/bitcoin/cw_bitcoin.dart
+17 -23
@@ -295,14 +295,7 @@ class CWBitcoin extends Bitcoin {
295
296 List<DerivationType> types = await compareDerivationMethods(mnemonic: mnemonic, node: node);
297 if (types.length == 1 && types.first == DerivationType.electrum) {
298 - return [
299 - DerivationInfo(
300 - derivationType: DerivationType.electrum,
301 - derivationPath: "m/0'",
302 - description: "Electrum",
303 - scriptType: "p2wpkh",
304 - )
305 - ];
298 + return [getElectrumDerivations()[DerivationType.electrum]!.first];
299 }
300
301 final electrumClient = ElectrumClient();
@@ -339,38 +332,34 @@ class CWBitcoin extends Bitcoin {
332 scriptType: dInfo.scriptType,
333 );
334
342 - String derivationPath = dInfoCopy.derivationPath!;
343 - int derivationDepth = _countOccurrences(derivationPath, "/");
344 -
345 - // the correct derivation depth is dependant on the derivation type:
346 - // the derivation paths defined in electrum_derivations are at the ROOT level, i.e.:
347 - // electrum's format doesn't specify subaddresses, just subaccounts:
335 + String balancePath = dInfoCopy.derivationPath!;
336 + int derivationDepth = _countOccurrences(balancePath, "/");
337
338 // for BIP44
350 - if (derivationDepth == 3) {
351 - // we add "/0/0" so that we generate account 0, index 0 and correctly get balance
352 - derivationPath += "/0/0";
339 + if (derivationDepth == 3 || derivationDepth == 1) {
340 + // we add "/0" so that we generate account 0
341 + balancePath += "/0";
342 }
343
355 - // var hd = bip32.BIP32.fromSeed(seedBytes).derivePath(derivationPath);
344 final hd = btc.HDWallet.fromSeed(
345 seedBytes,
346 network: networkType,
359 - ).derivePath(derivationPath);
347 + ).derivePath(balancePath);
348
349 + // derive address at index 0:
350 String? address;
351 switch (dInfoCopy.scriptType) {
352 case "p2wpkh":
364 - address = generateP2WPKHAddress(hd: hd, network: network);
353 + address = generateP2WPKHAddress(hd: hd, network: network, index: 0);
354 break;
355 case "p2pkh":
367 - address = generateP2PKHAddress(hd: hd, network: network);
356 + address = generateP2PKHAddress(hd: hd, network: network, index: 0);
357 break;
358 case "p2wpkh-p2sh":
370 - address = generateP2SHAddress(hd: hd, network: network);
359 + address = generateP2SHAddress(hd: hd, network: network, index: 0);
360 break;
361 case "p2tr":
373 - address = generateP2TRAddress(hd: hd, network: network);
362 + address = generateP2TRAddress(hd: hd, network: network, index: 0);
363 break;
364 default:
365 continue;
@@ -396,6 +385,11 @@ class CWBitcoin extends Bitcoin {
385 return list;
386 }
387
388 + @override
389 + Map<DerivationType, List<DerivationInfo>> getElectrumDerivations() {
390 + return electrum_derivations;
391 + }
392 +
393 @override
394 bool hasTaprootInput(PendingTransaction pendingTransaction) {
395 return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs;
lib/entities/default_settings_migration.dart
+15
@@ -233,6 +233,8 @@ Future<void> defaultSettingsMigration(
233 case 36:
234 await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
235 break;
236 + case 37:
237 + await fixBtcDerivationPaths(walletInfoSource);
238 default:
239 break;
240 }
@@ -775,6 +777,19 @@ Future<void> changeDefaultMoneroNode(
777 }
778 }
779
780 +Future<void> fixBtcDerivationPaths(Box<WalletInfo> walletsInfoSource) async {
781 + for (WalletInfo walletInfo in walletsInfoSource.values) {
782 + if (walletInfo.type == WalletType.bitcoin ||
783 + walletInfo.type == WalletType.bitcoinCash ||
784 + walletInfo.type == WalletType.litecoin) {
785 + if (walletInfo.derivationInfo?.derivationPath == "m/0'/0") {
786 + walletInfo.derivationInfo!.derivationPath = "m/0'";
787 + await walletInfo.save();
788 + }
789 + }
790 + }
791 +}
792 +
793 Future<void> updateBtcNanoWalletInfos(Box<WalletInfo> walletsInfoSource) async {
794 for (WalletInfo walletInfo in walletsInfoSource.values) {
795 if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) {
lib/main.dart
+1 -1
@@ -202,7 +202,7 @@ Future<void> initializeAppConfigs() async {
202 transactionDescriptions: transactionDescriptions,
203 secureStorage: secureStorage,
204 anonpayInvoiceInfo: anonpayInvoiceInfo,
205 - initialMigrationVersion: 36,
205 + initialMigrationVersion: 37,
206 );
207 }
208
lib/view_model/wallet_creation_vm.dart
+2 -4
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/core/wallet_creation_service.dart';
3 import 'package:cake_wallet/di.dart';
4 import 'package:cake_wallet/entities/background_tasks.dart';
@@ -98,10 +99,7 @@ abstract class WalletCreationVMBase with Store {
99 );
100 case WalletType.bitcoin:
101 case WalletType.litecoin:
101 - return DerivationInfo(
102 - derivationType: DerivationType.electrum,
103 - derivationPath: "m/0'",
104 - );
102 + return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
103 default:
104 return null;
105 }
tool/configure.dart
+1
@@ -186,6 +186,7 @@ abstract class Bitcoin {
186 {required String mnemonic, required Node node});
187 Future<List<DerivationInfo>> getDerivationsFromMnemonic(
188 {required String mnemonic, required Node node, String? passphrase});
189 + Map<DerivationType, List<DerivationInfo>> getElectrumDerivations();
190 Future<void> setAddressType(Object wallet, dynamic option);
191 ReceivePageOption getSelectedAddressType(Object wallet);
192 List<ReceivePageOption> getBitcoinReceivePageOptions();