From 591342ec6a0fd26e3163e8536fd14789edeb2aa4 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Tue, 18 Jun 2024 07:08:03 +0200 Subject: [PATCH] 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 --- cw_bitcoin/lib/bitcoin_wallet.dart | 3 +- cw_bitcoin/lib/electrum_derivations.dart | 3 ++ cw_bitcoin/lib/electrum_wallet.dart | 3 +- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 3 +- cw_bitcoin/lib/utils.dart | 36 ++++++++++-------- lib/bitcoin/cw_bitcoin.dart | 40 +++++++++----------- lib/entities/default_settings_migration.dart | 15 ++++++++ lib/main.dart | 2 +- lib/view_model/wallet_creation_vm.dart | 6 +-- tool/configure.dart | 1 + 10 files changed, 66 insertions(+), 46 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 3954631e8..d061480ed 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -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; diff --git a/cw_bitcoin/lib/electrum_derivations.dart b/cw_bitcoin/lib/electrum_derivations.dart index 7e19f1cb4..749e5c7af 100644 --- a/cw_bitcoin/lib/electrum_derivations.dart +++ b/cw_bitcoin/lib/electrum_derivations.dart @@ -108,3 +108,6 @@ Map> electrum_derivations = { ), ], }; + + +String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!; \ No newline at end of file diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index d3736e076..db42e2356 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -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!); diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 3e3f39131..15ad1cf63 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -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 = { diff --git a/cw_bitcoin/lib/utils.dart b/cw_bitcoin/lib/utils.dart index b3707e764..e3ebc00db 100644 --- a/cw_bitcoin/lib/utils.dart +++ b/cw_bitcoin/lib/utils.dart @@ -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); } diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 15d2ffadf..c62030504 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -295,14 +295,7 @@ class CWBitcoin extends Bitcoin { List 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> getElectrumDerivations() { + return electrum_derivations; + } + @override bool hasTaprootInput(PendingTransaction pendingTransaction) { return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs; diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index dcde1d3ce..d25c76dc7 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -233,6 +233,8 @@ Future defaultSettingsMigration( case 36: await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); break; + case 37: + await fixBtcDerivationPaths(walletInfoSource); default: break; } @@ -775,6 +777,19 @@ Future changeDefaultMoneroNode( } } +Future fixBtcDerivationPaths(Box 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 updateBtcNanoWalletInfos(Box walletsInfoSource) async { for (WalletInfo walletInfo in walletsInfoSource.values) { if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) { diff --git a/lib/main.dart b/lib/main.dart index 5bcd9ffdb..46bd7c608 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -202,7 +202,7 @@ Future initializeAppConfigs() async { transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, - initialMigrationVersion: 36, + initialMigrationVersion: 37, ); } diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index f825f0c47..841a88e7e 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -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; } diff --git a/tool/configure.dart b/tool/configure.dart index fc9bd5b91..e7ad676be 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -186,6 +186,7 @@ abstract class Bitcoin { {required String mnemonic, required Node node}); Future> getDerivationsFromMnemonic( {required String mnemonic, required Node node, String? passphrase}); + Map> getElectrumDerivations(); Future setAddressType(Object wallet, dynamic option); ReceivePageOption getSelectedAddressType(Object wallet); List getBitcoinReceivePageOptions();