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:convert/convert.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.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/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.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: // 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; walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null; 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/bitcoin_wallet_keys.dart';
import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_balance.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_history.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -133,7 +134,7 @@ abstract class ElectrumWalletBase
return currency == CryptoCurrency.bch return currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes) ? bitcoinCashHDWallet(seedBytes)
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
.derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? "m/0'")); .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path));
} }
return bitcoin.HDWallet.fromBase58(xpub!); return bitcoin.HDWallet.fromBase58(xpub!);

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.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/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/utils/file.dart'; import 'package:cw_core/utils/file.dart';
@ -71,7 +72,7 @@ class ElectrumWalletSnapshot {
final derivationType = DerivationType final derivationType = DerivationType
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index]; .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 { try {
regularAddressIndexByType = { 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:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:hex/hex.dart'; import 'package:hex/hex.dart';
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, int? index}) { bitcoin.PaymentData generatePaymentData({
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!; required bitcoin.HDWallet hd,
required int index,
}) {
final pubKey = hd.derive(index).pubKey!;
return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey))); return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
} }
ECPrivate generateECPrivate( ECPrivate generateECPrivate({
{required bitcoin.HDWallet hd, required BasedUtxoNetwork network, int? index}) { required bitcoin.HDWallet hd,
final wif = index != null ? hd.derive(index).wif! : hd.wif!; required BasedUtxoNetwork network,
required int index,
}) {
final wif = hd.derive(index).wif!;
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer); return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
} }
String generateP2WPKHAddress({ String generateP2WPKHAddress({
required bitcoin.HDWallet hd, required bitcoin.HDWallet hd,
required BasedUtxoNetwork network, 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); return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
} }
String generateP2SHAddress({ String generateP2SHAddress({
required bitcoin.HDWallet hd, required bitcoin.HDWallet hd,
required BasedUtxoNetwork network, 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); return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
} }
String generateP2WSHAddress({ String generateP2WSHAddress({
required bitcoin.HDWallet hd, required bitcoin.HDWallet hd,
required BasedUtxoNetwork network, 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); return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
} }
String generateP2PKHAddress({ String generateP2PKHAddress({
required bitcoin.HDWallet hd, required bitcoin.HDWallet hd,
required BasedUtxoNetwork network, 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); return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
} }
String generateP2TRAddress({ String generateP2TRAddress({
required bitcoin.HDWallet hd, required bitcoin.HDWallet hd,
required BasedUtxoNetwork network, 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); 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); List<DerivationType> types = await compareDerivationMethods(mnemonic: mnemonic, node: node);
if (types.length == 1 && types.first == DerivationType.electrum) { if (types.length == 1 && types.first == DerivationType.electrum) {
return [ return [getElectrumDerivations()[DerivationType.electrum]!.first];
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
description: "Electrum",
scriptType: "p2wpkh",
)
];
} }
final electrumClient = ElectrumClient(); final electrumClient = ElectrumClient();
@ -339,38 +332,34 @@ class CWBitcoin extends Bitcoin {
scriptType: dInfo.scriptType, scriptType: dInfo.scriptType,
); );
String derivationPath = dInfoCopy.derivationPath!; String balancePath = dInfoCopy.derivationPath!;
int derivationDepth = _countOccurrences(derivationPath, "/"); int derivationDepth = _countOccurrences(balancePath, "/");
// 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:
// for BIP44 // for BIP44
if (derivationDepth == 3) { if (derivationDepth == 3 || derivationDepth == 1) {
// we add "/0/0" so that we generate account 0, index 0 and correctly get balance // we add "/0" so that we generate account 0
derivationPath += "/0/0"; balancePath += "/0";
} }
// var hd = bip32.BIP32.fromSeed(seedBytes).derivePath(derivationPath);
final hd = btc.HDWallet.fromSeed( final hd = btc.HDWallet.fromSeed(
seedBytes, seedBytes,
network: networkType, network: networkType,
).derivePath(derivationPath); ).derivePath(balancePath);
// derive address at index 0:
String? address; String? address;
switch (dInfoCopy.scriptType) { switch (dInfoCopy.scriptType) {
case "p2wpkh": case "p2wpkh":
address = generateP2WPKHAddress(hd: hd, network: network); address = generateP2WPKHAddress(hd: hd, network: network, index: 0);
break; break;
case "p2pkh": case "p2pkh":
address = generateP2PKHAddress(hd: hd, network: network); address = generateP2PKHAddress(hd: hd, network: network, index: 0);
break; break;
case "p2wpkh-p2sh": case "p2wpkh-p2sh":
address = generateP2SHAddress(hd: hd, network: network); address = generateP2SHAddress(hd: hd, network: network, index: 0);
break; break;
case "p2tr": case "p2tr":
address = generateP2TRAddress(hd: hd, network: network); address = generateP2TRAddress(hd: hd, network: network, index: 0);
break; break;
default: default:
continue; continue;
@ -396,6 +385,11 @@ class CWBitcoin extends Bitcoin {
return list; return list;
} }
@override
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations() {
return electrum_derivations;
}
@override @override
bool hasTaprootInput(PendingTransaction pendingTransaction) { bool hasTaprootInput(PendingTransaction pendingTransaction) {
return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs; return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs;

View file

@ -233,6 +233,8 @@ Future<void> defaultSettingsMigration(
case 36: case 36:
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 37:
await fixBtcDerivationPaths(walletInfoSource);
default: default:
break; 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 { Future<void> updateBtcNanoWalletInfos(Box<WalletInfo> walletsInfoSource) async {
for (WalletInfo walletInfo in walletsInfoSource.values) { for (WalletInfo walletInfo in walletsInfoSource.values) {
if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) { if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) {

View file

@ -202,7 +202,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, 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/core/wallet_creation_service.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/background_tasks.dart'; import 'package:cake_wallet/entities/background_tasks.dart';
@ -98,10 +99,7 @@ abstract class WalletCreationVMBase with Store {
); );
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
return DerivationInfo( return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
);
default: default:
return null; return null;
} }

View file

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