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
This commit is contained in:
Matthew Fosse 2024-06-18 07:08:03 +02:00 committed by GitHub
parent fc2c9a2bcc
commit 591342ec6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 66 additions and 46 deletions

View file

@ -6,6 +6,7 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:convert/convert.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
@ -150,7 +151,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
);
// set the default if not present:
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/0";
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path;
walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null;

View file

@ -108,3 +108,6 @@ Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
),
],
};
String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!;

View file

@ -17,6 +17,7 @@ import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -133,7 +134,7 @@ abstract class ElectrumWalletBase
return currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes)
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
.derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? "m/0'"));
.derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path));
}
return bitcoin.HDWallet.fromBase58(xpub!);

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/utils/file.dart';
@ -71,7 +72,7 @@ class ElectrumWalletSnapshot {
final derivationType = DerivationType
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
final derivationPath = data['derivationPath'] as String? ?? electrum_path;
try {
regularAddressIndexByType = {

View file

@ -5,58 +5,64 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:hex/hex.dart';
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, int? index}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
bitcoin.PaymentData generatePaymentData({
required bitcoin.HDWallet hd,
required int index,
}) {
final pubKey = hd.derive(index).pubKey!;
return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
}
ECPrivate generateECPrivate(
{required bitcoin.HDWallet hd, required BasedUtxoNetwork network, int? index}) {
final wif = index != null ? hd.derive(index).wif! : hd.wif!;
ECPrivate generateECPrivate({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
required int index,
}) {
final wif = hd.derive(index).wif!;
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
}
String generateP2WPKHAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
required int index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
final pubKey = hd.derive(index).pubKey!;
return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
}
String generateP2SHAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
required int index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
final pubKey = hd.derive(index).pubKey!;
return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
}
String generateP2WSHAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
required int index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
final pubKey = hd.derive(index).pubKey!;
return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
}
String generateP2PKHAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
required int index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
final pubKey = hd.derive(index).pubKey!;
return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
}
String generateP2TRAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
required int index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
final pubKey = hd.derive(index).pubKey!;
return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
}

View file

@ -295,14 +295,7 @@ class CWBitcoin extends Bitcoin {
List<DerivationType> types = await compareDerivationMethods(mnemonic: mnemonic, node: node);
if (types.length == 1 && types.first == DerivationType.electrum) {
return [
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
description: "Electrum",
scriptType: "p2wpkh",
)
];
return [getElectrumDerivations()[DerivationType.electrum]!.first];
}
final electrumClient = ElectrumClient();
@ -339,38 +332,34 @@ class CWBitcoin extends Bitcoin {
scriptType: dInfo.scriptType,
);
String derivationPath = dInfoCopy.derivationPath!;
int derivationDepth = _countOccurrences(derivationPath, "/");
// the correct derivation depth is dependant on the derivation type:
// the derivation paths defined in electrum_derivations are at the ROOT level, i.e.:
// electrum's format doesn't specify subaddresses, just subaccounts:
String balancePath = dInfoCopy.derivationPath!;
int derivationDepth = _countOccurrences(balancePath, "/");
// for BIP44
if (derivationDepth == 3) {
// we add "/0/0" so that we generate account 0, index 0 and correctly get balance
derivationPath += "/0/0";
if (derivationDepth == 3 || derivationDepth == 1) {
// we add "/0" so that we generate account 0
balancePath += "/0";
}
// var hd = bip32.BIP32.fromSeed(seedBytes).derivePath(derivationPath);
final hd = btc.HDWallet.fromSeed(
seedBytes,
network: networkType,
).derivePath(derivationPath);
).derivePath(balancePath);
// derive address at index 0:
String? address;
switch (dInfoCopy.scriptType) {
case "p2wpkh":
address = generateP2WPKHAddress(hd: hd, network: network);
address = generateP2WPKHAddress(hd: hd, network: network, index: 0);
break;
case "p2pkh":
address = generateP2PKHAddress(hd: hd, network: network);
address = generateP2PKHAddress(hd: hd, network: network, index: 0);
break;
case "p2wpkh-p2sh":
address = generateP2SHAddress(hd: hd, network: network);
address = generateP2SHAddress(hd: hd, network: network, index: 0);
break;
case "p2tr":
address = generateP2TRAddress(hd: hd, network: network);
address = generateP2TRAddress(hd: hd, network: network, index: 0);
break;
default:
continue;
@ -396,6 +385,11 @@ class CWBitcoin extends Bitcoin {
return list;
}
@override
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations() {
return electrum_derivations;
}
@override
bool hasTaprootInput(PendingTransaction pendingTransaction) {
return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs;

View file

@ -233,6 +233,8 @@ Future<void> defaultSettingsMigration(
case 36:
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 37:
await fixBtcDerivationPaths(walletInfoSource);
default:
break;
}
@ -775,6 +777,19 @@ Future<void> changeDefaultMoneroNode(
}
}
Future<void> fixBtcDerivationPaths(Box<WalletInfo> walletsInfoSource) async {
for (WalletInfo walletInfo in walletsInfoSource.values) {
if (walletInfo.type == WalletType.bitcoin ||
walletInfo.type == WalletType.bitcoinCash ||
walletInfo.type == WalletType.litecoin) {
if (walletInfo.derivationInfo?.derivationPath == "m/0'/0") {
walletInfo.derivationInfo!.derivationPath = "m/0'";
await walletInfo.save();
}
}
}
}
Future<void> updateBtcNanoWalletInfos(Box<WalletInfo> walletsInfoSource) async {
for (WalletInfo walletInfo in walletsInfoSource.values) {
if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) {

View file

@ -202,7 +202,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 36,
initialMigrationVersion: 37,
);
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
@ -98,10 +99,7 @@ abstract class WalletCreationVMBase with Store {
);
case WalletType.bitcoin:
case WalletType.litecoin:
return DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
);
return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
default:
return null;
}

View file

@ -186,6 +186,7 @@ abstract class Bitcoin {
{required String mnemonic, required Node node});
Future<List<DerivationInfo>> getDerivationsFromMnemonic(
{required String mnemonic, required Node node, String? passphrase});
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations();
Future<void> setAddressType(Object wallet, dynamic option);
ReceivePageOption getSelectedAddressType(Object wallet);
List<ReceivePageOption> getBitcoinReceivePageOptions();