diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 834cbbb4c..37413dc81 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:mobx/mobx.dart'; @@ -133,7 +134,8 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { required this.silentPaymentTweak, required super.network, required super.type, - }) : super(); + this.spendDerivationPath = SILENT_PAYMENTS_SPEND_PATH_TESTNET, + }); factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) { @@ -155,10 +157,13 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { ? BitcoinAddressType.values .firstWhere((type) => type.toString() == decoded['type'] as String) : SilentPaymentsAddresType.p2sp, + spendDerivationPath: + decoded['spend_derivation_path'] as String? ?? SILENT_PAYMENTS_SPEND_PATH_TESTNET, ); } final String? silentPaymentTweak; + final String spendDerivationPath; @override String toJSON() => json.encode({ @@ -172,5 +177,6 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { 'type': type.toString(), 'network': network?.value, 'silent_payment_tweak': silentPaymentTweak, + 'spend_derivation_path': spendDerivationPath, }); } diff --git a/cw_bitcoin/lib/electrum_derivations.dart b/cw_bitcoin/lib/electrum_derivations.dart index 81a3626d2..26dc1f82d 100644 --- a/cw_bitcoin/lib/electrum_derivations.dart +++ b/cw_bitcoin/lib/electrum_derivations.dart @@ -1,5 +1,10 @@ import 'package:cw_core/wallet_info.dart'; +const SILENT_PAYMENTS_SCAN_PATH = "m/352'/0'/0'/1'/0"; +const SILENT_PAYMENTS_SPEND_PATH = "m/352'/0'/0'/0'/0"; +const SILENT_PAYMENTS_SCAN_PATH_TESTNET = "m/352'/1'/0'/1'/0"; +const SILENT_PAYMENTS_SPEND_PATH_TESTNET = "m/352'/1'/0'/0'/0"; + Map> electrum_derivations = { DerivationType.electrum: [ DerivationInfo( diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 477544ee9..a285ff979 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -4,14 +4,6 @@ import 'dart:io'; import 'dart:isolate'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_core/hardware/hardware_wallet_service.dart'; -import 'package:cw_core/root_dir.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:cw_bitcoin/bitcoin_amount_format.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_bitcoin/bitcoin_wallet.dart'; -import 'package:cw_bitcoin/litecoin_wallet.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:collection/collection.dart'; import 'package:cw_bitcoin/address_from_output.dart'; @@ -19,6 +11,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet_keys.dart'; import 'package:cw_bitcoin/electrum.dart' as electrum; import 'package:cw_bitcoin/electrum_balance.dart'; @@ -27,31 +20,37 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/exceptions.dart'; +import 'package:cw_bitcoin/litecoin_wallet.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/get_height_by_date.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/node.dart'; +import 'package:cw_core/output_info.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/root_dir.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_core/utils/socket_health_logger.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_core/unspent_coin_type.dart'; -import 'package:cw_core/output_info.dart'; import 'package:flutter/foundation.dart'; +import 'package:hex/hex.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:sp_scanner/sp_scanner.dart'; -import 'package:hex/hex.dart'; -import 'package:cw_core/utils/socket_health_logger.dart'; part 'electrum_wallet.g.dart'; @@ -76,8 +75,9 @@ abstract class ElectrumWalletBase ElectrumBalance? initialBalance, CryptoCurrency? currency, bool? alwaysScan, - }) : accountHD = - getAccountHDWallet(currency, network, seedBytes, xpub, derivationInfo, walletInfo.hardwareWalletType), + }) : _masterHD = getMasterHD(seedBytes, network, walletInfo.hardwareWalletType), + accountHD = getAccountHDWallet( + currency, network, seedBytes, xpub, derivationInfo, walletInfo.hardwareWalletType), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], @@ -171,9 +171,18 @@ abstract class ElectrumWalletBase } } + static Bip32Slip10Secp256k1? getMasterHD(Uint8List? seedBytes, + [BasedUtxoNetwork? network, HardwareWalletType? hardwareWalletType]) { + if (seedBytes == null) return null; + + return Bip32Slip10Secp256k1.fromSeed( + seedBytes, network != null ? getKeyNetVersion(network, hardwareWalletType) : null); + } + @observable bool? alwaysScan; + final Bip32Slip10Secp256k1? _masterHD; final Bip32Slip10Secp256k1 accountHD; final String? _mnemonic; @@ -383,6 +392,7 @@ abstract class ElectrumWalletBase ScanData( sendPort: receivePort.sendPort, silentAddress: walletAddresses.silentAddress!, + masterHD: _masterHD!, network: network, height: height, chainTip: chainTip, @@ -745,10 +755,10 @@ abstract class ElectrumWalletBase if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; - privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( - BigintUtils.fromBytes( - BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), - ), + privkey = ECPrivate.fromHex( + _masterHD!.derivePath(unspentAddress.spendDerivationPath).privateKey.toHex()) + .tweakAdd( + BigintUtils.fromBytes(BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!)), ); spendsSilentPayment = true; isSilentPayment = true; @@ -772,7 +782,6 @@ abstract class ElectrumWalletBase pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex(); } - final derivationPath = "${_hardenedDerivationPath(derivationInfo.derivationPath ?? electrum_path)}" "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" @@ -1141,7 +1150,6 @@ abstract class ElectrumWalletBase bool hasSilentPayment = false, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) async { - final utxoDetailsAll = _createUTXOS( sendAll: true, paysToSilentPayment: hasSilentPayment, @@ -2768,8 +2776,8 @@ abstract class ElectrumWalletBase @override String formatCryptoAmount(String amount) { - final amountInt = int.parse(amount); - return bitcoinAmountToString(amount: amountInt); + final amountBigInt = BigInt.parse(amount); + return currency.formatAmount(amountBigInt); } /// Checks the health of the socket connection @@ -2930,6 +2938,7 @@ class ScanNode { class ScanData { final SendPort sendPort; final SilentPaymentOwner silentAddress; + final Bip32Slip10Secp256k1 masterHD; final int height; final ScanNode? node; final BasedUtxoNetwork network; @@ -2945,6 +2954,7 @@ class ScanData { ScanData({ required this.sendPort, required this.silentAddress, + required this.masterHD, required this.height, required this.node, required this.network, @@ -2962,6 +2972,7 @@ class ScanData { return ScanData( sendPort: scanData.sendPort, silentAddress: scanData.silentAddress, + masterHD: scanData.masterHD, height: newHeight, node: scanData.node, network: scanData.network, @@ -3003,16 +3014,29 @@ Future _handleScanSilentPayments(ScanData scanData) async { log("connected to ${node.toString()}", LogLevel.info); - final receiver = Receiver( - scanData.silentAddress.b_scan.toHex(), - scanData.silentAddress.B_spend.toHex(), - scanData.network == BitcoinNetwork.testnet, - scanData.labelIndexes, - scanData.labelIndexes.length, - ); + final receivers = [ + Receiver( + scanData.silentAddress.b_scan.toHex(), + scanData.silentAddress.B_spend.toHex(), + scanData.network == BitcoinNetwork.testnet, + scanData.labelIndexes, + scanData.labelIndexes.length, + ), + Receiver( + scanData.masterHD.derivePath(SILENT_PAYMENTS_SCAN_PATH_TESTNET).privateKey.toHex(), + scanData.masterHD.derivePath(SILENT_PAYMENTS_SPEND_PATH_TESTNET).publicKey.toHex(), + scanData.network == BitcoinNetwork.testnet, + scanData.labelIndexes, + scanData.labelIndexes.length, + ) + ]; log( - "using receiver: b_scan: ${scanData.silentAddress.b_scan.toHex()}, B_scan: ${scanData.silentAddress.B_spend.toHex()}, b_spend: ${scanData.silentAddress.B_spend.toHex()}, B_spend: ${scanData.silentAddress.B_spend.toHex()}, network: ${scanData.network.value}, labelIndexes: ${scanData.labelIndexes}", + "using receiver: b_scan: ${scanData.silentAddress.b_scan.toHex()}, b_spend: ${scanData.silentAddress.B_spend.toHex()}, network: ${scanData.network.value}, labelIndexes: ${scanData.labelIndexes}", + LogLevel.info, + ); + log( + "using receiver: b_scan: ${receivers[1].bScan}, b_spend: ${receivers[1].BSpend}, network: ${scanData.network.value}, labelIndexes: ${scanData.labelIndexes}", LogLevel.info, ); @@ -3140,22 +3164,20 @@ Future _handleScanSilentPayments(ScanData scanData) async { final tweak = tweakData.tweak; try { - final addToWallet = {}; + final addToWallet = {}; - // receivers.forEach((receiver) { - // NOTE: scanOutputs, from sp_scanner package, called from rust here - final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver); + receivers.forEach((receiver) { + // NOTE: scanOutputs, from sp_scanner package, called from rust here + final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver); - if (scanResult.isEmpty) { - continue; - } + if (scanResult.isEmpty) return; - if (addToWallet[receiver.BSpend] == null) { - addToWallet[receiver.BSpend] = scanResult; - } else { - addToWallet[receiver.BSpend].addAll(scanResult); - } - // }); + if (addToWallet[receiver.BSpend] == null) { + addToWallet[receiver.BSpend] = scanResult; + } else { + addToWallet[receiver.BSpend].addAll(scanResult); + } + }); if (addToWallet.isEmpty) { // no results tx, continue to next tx @@ -3237,9 +3259,8 @@ Future _handleScanSilentPayments(ScanData scanData) async { final pos = matchingOutput.vout; final spent = matchingOutput.spendingInput; - // final matchingSPWallet = scanData.silentPaymentsWallets.firstWhere( - // (receiver) => receiver.B_spend.toHex() == BSpend.toString(), - // ); + final matchingReceiver = + receivers.indexWhere((receiver) => receiver.BSpend == BSpend); // final labelIndex = labelValue != null ? scanData.labels[label] : 0; // final balance = ElectrumBalance(); @@ -3255,6 +3276,9 @@ Future _handleScanSilentPayments(ScanData scanData) async { type: SegwitAddresType.p2tr, txCount: 1, balance: amount, + spendDerivationPath: matchingReceiver == 0 + ? SILENT_PAYMENTS_SPEND_PATH + : SILENT_PAYMENTS_SPEND_PATH_TESTNET, ); final unspent = BitcoinSilentPaymentsUnspent( diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 18d2898b4..e91428717 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -3,6 +3,7 @@ import 'dart:io' show Platform; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; @@ -75,8 +76,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { super(walletInfo) { if (masterHd != null) { silentAddress = SilentPaymentOwner.fromPrivateKeys( - b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()), - b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()), + b_scan: + ECPrivate.fromHex(masterHd.derivePath(SILENT_PAYMENTS_SCAN_PATH).privateKey.toHex()), + b_spend: + ECPrivate.fromHex(masterHd.derivePath(SILENT_PAYMENTS_SPEND_PATH).privateKey.toHex()), network: network, );