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();