From 1b89911b278cd688de1eb7920f45e1b3d92e8332 Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Thu, 24 Oct 2024 14:50:19 -0500 Subject: [PATCH] use upgraded btcd library to handle Taproot address; cleanup previous workaround code --- e2e/e2etests/test_bitcoin_withdraw_taproot.go | 4 +- pkg/chains/address.go | 16 +- pkg/chains/address_taproot.go | 217 ------------------ pkg/chains/address_taproot_test.go | 79 ------- pkg/chains/address_test.go | 2 +- zetaclient/chains/bitcoin/fee.go | 3 +- zetaclient/chains/bitcoin/fee_test.go | 6 +- zetaclient/chains/bitcoin/signer/signer.go | 4 +- .../bitcoin/signer/signer_keysign_test.go | 3 +- .../chains/bitcoin/signer/signer_test.go | 11 +- zetaclient/chains/bitcoin/tx_script.go | 15 +- 11 files changed, 23 insertions(+), 337 deletions(-) delete mode 100644 pkg/chains/address_taproot.go delete mode 100644 pkg/chains/address_taproot_test.go diff --git a/e2e/e2etests/test_bitcoin_withdraw_taproot.go b/e2e/e2etests/test_bitcoin_withdraw_taproot.go index d9d0b4cf48..f675a88c43 100644 --- a/e2e/e2etests/test_bitcoin_withdraw_taproot.go +++ b/e2e/e2etests/test_bitcoin_withdraw_taproot.go @@ -1,10 +1,10 @@ package e2etests import ( + "github.com/btcsuite/btcd/btcutil" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/e2e/runner" - "github.com/zeta-chain/node/pkg/chains" ) func TestBitcoinWithdrawTaproot(r *runner.E2ERunner, args []string) { @@ -15,7 +15,7 @@ func TestBitcoinWithdrawTaproot(r *runner.E2ERunner, args []string) { // parse arguments and withdraw BTC defaultReceiver := "bcrt1pqqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0sj9hjuh" receiver, amount := parseBitcoinWithdrawArgs(r, args, defaultReceiver) - _, ok := receiver.(*chains.AddressTaproot) + _, ok := receiver.(*btcutil.AddressTaproot) require.True(r, ok, "Invalid receiver address specified for TestBitcoinWithdrawTaproot.") withdrawBTCZRC20(r, receiver, amount) diff --git a/pkg/chains/address.go b/pkg/chains/address.go index 1f2ddddad8..7ab0049742 100644 --- a/pkg/chains/address.go +++ b/pkg/chains/address.go @@ -54,6 +54,7 @@ func ConvertRecoverToError(r interface{}) error { // DecodeBtcAddress decodes a BTC address from a given string and chainID func DecodeBtcAddress(inputAddress string, chainID int64) (address btcutil.Address, err error) { + // prevent potential panic from 'btcutil.DecodeAddress' defer func() { if r := recover(); r != nil { err = ConvertRecoverToError(r) @@ -68,19 +69,14 @@ func DecodeBtcAddress(inputAddress string, chainID int64) (address btcutil.Addre if chainParams == nil { return nil, fmt.Errorf("chain params not found") } - // test taproot address type - address, err = DecodeTaprootAddress(inputAddress) - if err == nil { - if address.IsForNet(chainParams) { - return address, nil - } - return nil, fmt.Errorf("address %s is not for network %s", inputAddress, chainParams.Name) - } - // test taproot address failed; continue testing other types: P2WSH, P2WPKH, P2SH, P2PKH + + // try decoding input address as a Bitcoin address address, err = btcutil.DecodeAddress(inputAddress, chainParams) if err != nil { return nil, fmt.Errorf("decode address failed: %s, for input address %s", err.Error(), inputAddress) } + + // address must match the network ok := address.IsForNet(chainParams) if !ok { return nil, fmt.Errorf("address %s is not for network %s", inputAddress, chainParams.Name) @@ -109,7 +105,7 @@ func DecodeSolanaWalletAddress(inputAddress string) (pk solana.PublicKey, err er func IsBtcAddressSupported(addr btcutil.Address) bool { switch addr.(type) { // P2TR address - case *AddressTaproot, + case *btcutil.AddressTaproot, // P2WSH address *btcutil.AddressWitnessScriptHash, // P2WPKH address diff --git a/pkg/chains/address_taproot.go b/pkg/chains/address_taproot.go deleted file mode 100644 index 796c3fde85..0000000000 --- a/pkg/chains/address_taproot.go +++ /dev/null @@ -1,217 +0,0 @@ -package chains - -import ( - "bytes" - "errors" - "fmt" - "strings" - - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/btcutil/bech32" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" -) - -// taproot address type - -type AddressSegWit struct { - hrp string - witnessVersion byte - witnessProgram []byte -} - -type AddressTaproot struct { - AddressSegWit -} - -var _ btcutil.Address = &AddressTaproot{} - -// NewAddressTaproot returns a new AddressTaproot. -func NewAddressTaproot(witnessProg []byte, - net *chaincfg.Params) (*AddressTaproot, error) { - return newAddressTaproot(net.Bech32HRPSegwit, witnessProg) -} - -// newAddressTaproot is an internal helper function to create an -// AddressWitnessScriptHash with a known human-readable part, rather than -// looking it up through its parameters. -func newAddressTaproot(hrp string, witnessProg []byte) (*AddressTaproot, error) { - // Check for valid program length for witness version 1, which is 32 - // for P2TR. - if len(witnessProg) != 32 { - return nil, errors.New("witness program must be 32 bytes for " + - "p2tr") - } - - addr := &AddressTaproot{ - AddressSegWit{ - hrp: strings.ToLower(hrp), - witnessVersion: 0x01, - witnessProgram: witnessProg, - }, - } - - return addr, nil -} - -// EncodeAddress returns the bech32 (or bech32m for SegWit v1) string encoding -// of an AddressSegWit. -// -// NOTE: This method is part of the Address interface. -func (a AddressSegWit) EncodeAddress() string { - str, err := encodeSegWitAddress( - a.hrp, a.witnessVersion, a.witnessProgram[:], - ) - if err != nil { - return "" - } - return str -} - -// encodeSegWitAddress creates a bech32 (or bech32m for SegWit v1) encoded -// address string representation from witness version and witness program. -func encodeSegWitAddress(hrp string, witnessVersion byte, witnessProgram []byte) (string, error) { - // Group the address bytes into 5 bit groups, as this is what is used to - // encode each character in the address string. - converted, err := bech32.ConvertBits(witnessProgram, 8, 5, true) - if err != nil { - return "", err - } - - // Concatenate the witness version and program, and encode the resulting - // bytes using bech32 encoding. - combined := make([]byte, len(converted)+1) - combined[0] = witnessVersion - copy(combined[1:], converted) - - var bech string - switch witnessVersion { - case 0: - bech, err = bech32.Encode(hrp, combined) - - case 1: - bech, err = bech32.EncodeM(hrp, combined) - - default: - return "", fmt.Errorf("unsupported witness version %d", - witnessVersion) - } - if err != nil { - return "", err - } - - // Check validity by decoding the created address. - _, version, program, err := decodeSegWitAddress(bech) - if err != nil { - return "", fmt.Errorf("invalid segwit address: %v", err) - } - - if version != witnessVersion || !bytes.Equal(program, witnessProgram) { - return "", fmt.Errorf("invalid segwit address") - } - - return bech, nil -} - -// decodeSegWitAddress parses a bech32 encoded segwit address string and -// returns the witness version and witness program byte representation. -func decodeSegWitAddress(address string) (string, byte, []byte, error) { - // Decode the bech32 encoded address. - hrp, data, bech32version, err := bech32.DecodeGeneric(address) - if err != nil { - return "", 0, nil, err - } - - // The first byte of the decoded address is the witness version, it must - // exist. - if len(data) < 1 { - return "", 0, nil, fmt.Errorf("no witness version") - } - - // ...and be <= 16. - version := data[0] - if version > 16 { - return "", 0, nil, fmt.Errorf("invalid witness version: %v", version) - } - - // The remaining characters of the address returned are grouped into - // words of 5 bits. In order to restore the original witness program - // bytes, we'll need to regroup into 8 bit words. - regrouped, err := bech32.ConvertBits(data[1:], 5, 8, false) - if err != nil { - return "", 0, nil, err - } - - // The regrouped data must be between 2 and 40 bytes. - if len(regrouped) < 2 || len(regrouped) > 40 { - return "", 0, nil, fmt.Errorf("invalid data length") - } - - // For witness version 0, address MUST be exactly 20 or 32 bytes. - if version == 0 && len(regrouped) != 20 && len(regrouped) != 32 { - return "", 0, nil, fmt.Errorf("invalid data length for witness "+ - "version 0: %v", len(regrouped)) - } - - // For witness version 0, the bech32 encoding must be used. - if version == 0 && bech32version != bech32.Version0 { - return "", 0, nil, fmt.Errorf("invalid checksum expected bech32 " + - "encoding for address with witness version 0") - } - - // For witness version 1, the bech32m encoding must be used. - if version == 1 && bech32version != bech32.VersionM { - return "", 0, nil, fmt.Errorf("invalid checksum expected bech32m " + - "encoding for address with witness version 1") - } - - return hrp, version, regrouped, nil -} - -// ScriptAddress returns the witness program for this address. -// -// NOTE: This method is part of the Address interface. -func (a *AddressSegWit) ScriptAddress() []byte { - return a.witnessProgram[:] -} - -// IsForNet returns whether the AddressSegWit is associated with the passed -// bitcoin network. -// -// NOTE: This method is part of the Address interface. -func (a *AddressSegWit) IsForNet(net *chaincfg.Params) bool { - return a.hrp == net.Bech32HRPSegwit -} - -// String returns a human-readable string for the AddressWitnessPubKeyHash. -// This is equivalent to calling EncodeAddress, but is provided so the type -// can be used as a fmt.Stringer. -// -// NOTE: This method is part of the Address interface. -func (a *AddressSegWit) String() string { - return a.EncodeAddress() -} - -// DecodeTaprootAddress decodes taproot address only and returns error on non-taproot address -func DecodeTaprootAddress(addr string) (*AddressTaproot, error) { - hrp, version, program, err := decodeSegWitAddress(addr) - if err != nil { - return nil, err - } - if version != 1 { - return nil, errors.New("invalid witness version; taproot address must be version 1") - } - return &AddressTaproot{ - AddressSegWit{ - hrp: hrp, - witnessVersion: version, - witnessProgram: program, - }, - }, nil -} - -// PayToWitnessTaprootScript creates a new script to pay to a version 1 -// (taproot) witness program. The passed hash is expected to be valid. -func PayToWitnessTaprootScript(rawKey []byte) ([]byte, error) { - return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(rawKey).Script() -} diff --git a/pkg/chains/address_taproot_test.go b/pkg/chains/address_taproot_test.go deleted file mode 100644 index c3742dcefc..0000000000 --- a/pkg/chains/address_taproot_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package chains - -import ( - "encoding/hex" - "testing" - - "github.com/btcsuite/btcd/chaincfg" - "github.com/stretchr/testify/require" -) - -func TestAddressTaproot(t *testing.T) { - { - // should parse mainnet taproot address - addrStr := "bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c" - addr, err := DecodeTaprootAddress(addrStr) - require.NoError(t, err) - require.Equal(t, addrStr, addr.String()) - require.Equal(t, addrStr, addr.EncodeAddress()) - require.True(t, addr.IsForNet(&chaincfg.MainNetParams)) - } - { - // should parse testnet taproot address - addrStr := "tb1pzeclkt6upu8xwuksjcz36y4q56dd6jw5r543eu8j8238yaxpvcvq7t8f33" - addr, err := DecodeTaprootAddress(addrStr) - require.NoError(t, err) - require.Equal(t, addrStr, addr.String()) - require.Equal(t, addrStr, addr.EncodeAddress()) - require.True(t, addr.IsForNet(&chaincfg.TestNet3Params)) - } - { - // should parse regtest taproot address - addrStr := "bcrt1pqqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0sj9hjuh" - addr, err := DecodeTaprootAddress(addrStr) - require.NoError(t, err) - require.Equal(t, addrStr, addr.String()) - require.Equal(t, addrStr, addr.EncodeAddress()) - require.True(t, addr.IsForNet(&chaincfg.RegressionNetParams)) - } - - { - // should fail to parse invalid taproot address - // should parse mainnet taproot address - addrStr := "bc1qysd4sp9q8my59ul9wsf5rvs9p387hf8vfwatzu" - _, err := DecodeTaprootAddress(addrStr) - require.Error(t, err) - } - { - var witnessProg [32]byte - for i := 0; i < 32; i++ { - witnessProg[i] = byte(i) - } - _, err := newAddressTaproot("bcrt", witnessProg[:]) - require.NoError(t, err) - //t.Logf("addr: %v", addr) - } - { - // should create correct taproot address from given witness program - // these hex string comes from link - // https://mempool.space/tx/41f7cbaaf9a8d378d09ee86de32eebef455225520cb71015cc9a7318fb42e326 - witnessProg, err := hex.DecodeString("af06f3d4c726a3b952f2f648d86398af5ddfc3df27aa531d97203987add8db2e") - require.NoError(t, err) - addr, err := NewAddressTaproot(witnessProg[:], &chaincfg.MainNetParams) - require.NoError(t, err) - require.Equal(t, addr.EncodeAddress(), "bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c") - } - { - // should give correct ScriptAddress for taproot address - // example comes from - // https://blockstream.info/tx/09298a2f32f5267f419aeaf8a58c4807dcf6cac3edb59815a3b129cd8f1219b0?expand - addrStr := "bc1p6pls9gpm24g8ntl37pajpjtuhd3y08hs5rnf9a4n0wq595hwdh9suw7m2h" - addr, err := DecodeTaprootAddress(addrStr) - require.NoError(t, err) - require.Equal( - t, - "d07f02a03b555079aff1f07b20c97cbb62479ef0a0e692f6b37b8142d2ee6dcb", - hex.EncodeToString(addr.ScriptAddress()), - ) - } -} diff --git a/pkg/chains/address_test.go b/pkg/chains/address_test.go index 38991bc7ab..bba2db0bc9 100644 --- a/pkg/chains/address_test.go +++ b/pkg/chains/address_test.go @@ -179,7 +179,7 @@ func Test_IsBtcAddressSupported_P2TR(t *testing.T) { // it should be a taproot address addr, err := DecodeBtcAddress(tt.addr, tt.chainId) require.NoError(t, err) - _, ok := addr.(*AddressTaproot) + _, ok := addr.(*btcutil.AddressTaproot) require.True(t, ok) // it should be supported diff --git a/zetaclient/chains/bitcoin/fee.go b/zetaclient/chains/bitcoin/fee.go index 58297f37d6..f56a479364 100644 --- a/zetaclient/chains/bitcoin/fee.go +++ b/zetaclient/chains/bitcoin/fee.go @@ -13,7 +13,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/pkg/errors" - "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/rpc" "github.com/zeta-chain/node/zetaclient/chains/interfaces" clientcommon "github.com/zeta-chain/node/zetaclient/common" @@ -104,7 +103,7 @@ func EstimateOutboundSize(numInputs uint64, payees []btcutil.Address) (uint64, e // GetOutputSizeByAddress returns the size of a tx output in bytes by the given address func GetOutputSizeByAddress(to btcutil.Address) (uint64, error) { switch addr := to.(type) { - case *chains.AddressTaproot: + case *btcutil.AddressTaproot: if addr == nil { return 0, nil } diff --git a/zetaclient/chains/bitcoin/fee_test.go b/zetaclient/chains/bitcoin/fee_test.go index 8b1f54e5d5..82f60ff0ef 100644 --- a/zetaclient/chains/bitcoin/fee_test.go +++ b/zetaclient/chains/bitcoin/fee_test.go @@ -66,7 +66,7 @@ func generateKeyPair(t *testing.T, net *chaincfg.Params) (*btcec.PrivateKey, btc addr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, net) require.NoError(t, err) //fmt.Printf("New address: %s\n", addr.EncodeAddress()) - pkScript, err := PayToAddrScript(addr) + pkScript, err := txscript.PayToAddrScript(addr) require.NoError(t, err) return privateKey, addr, pkScript } @@ -83,7 +83,7 @@ func getTestAddrScript(t *testing.T, scriptType string) btcutil.Address { // createPkScripts creates 10 random amount of scripts to the given address 'to' func createPkScripts(t *testing.T, to btcutil.Address, repeat int) ([]btcutil.Address, [][]byte) { - pkScript, err := PayToAddrScript(to) + pkScript, err := txscript.PayToAddrScript(to) require.NoError(t, err) addrs := []btcutil.Address{} @@ -261,7 +261,7 @@ func TestOutboundSizeXIn3Out(t *testing.T) { func TestGetOutputSizeByAddress(t *testing.T) { // test nil P2TR address and non-nil P2TR address - nilP2TR := (*chains.AddressTaproot)(nil) + nilP2TR := (*btcutil.AddressTaproot)(nil) sizeNilP2TR, err := GetOutputSizeByAddress(nilP2TR) require.NoError(t, err) require.Equal(t, uint64(0), sizeNilP2TR) diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index 7e49d4d675..90257e019e 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -158,7 +158,7 @@ func (signer *Signer) AddWithdrawTxOutputs( if err != nil { return err } - payToSelfScript, err := bitcoin.PayToAddrScript(tssAddrP2WPKH) + payToSelfScript, err := txscript.PayToAddrScript(tssAddrP2WPKH) if err != nil { return err } @@ -167,7 +167,7 @@ func (signer *Signer) AddWithdrawTxOutputs( // 2nd output: the payment to the recipient if !cancelTx { - pkScript, err := bitcoin.PayToAddrScript(to) + pkScript, err := txscript.PayToAddrScript(to) if err != nil { return err } diff --git a/zetaclient/chains/bitcoin/signer/signer_keysign_test.go b/zetaclient/chains/bitcoin/signer/signer_keysign_test.go index 1ad50f0af5..a339b051dd 100644 --- a/zetaclient/chains/bitcoin/signer/signer_keysign_test.go +++ b/zetaclient/chains/bitcoin/signer/signer_keysign_test.go @@ -17,7 +17,6 @@ import ( "github.com/stretchr/testify/suite" "github.com/zeta-chain/node/pkg/chains" - "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/chains/interfaces" "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) @@ -96,7 +95,7 @@ func buildTX() (*wire.MsgTx, *txscript.TxSigHashes, int, int64, []byte, *btcec.P txIn := wire.NewTxIn(outpoint, nil, nil) tx.AddTxIn(txIn) - pkScript, err := bitcoin.PayToAddrScript(addr) + pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, nil, 0, 0, nil, nil, false, err } diff --git a/zetaclient/chains/bitcoin/signer/signer_test.go b/zetaclient/chains/bitcoin/signer/signer_test.go index b9decf25b1..17fb2dc3de 100644 --- a/zetaclient/chains/bitcoin/signer/signer_test.go +++ b/zetaclient/chains/bitcoin/signer/signer_test.go @@ -20,7 +20,6 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/zetaclient/chains/base" - "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/config" "github.com/zeta-chain/node/zetaclient/testutils/mocks" ) @@ -76,7 +75,7 @@ func (s *BTCSignerSuite) TestP2PH(c *C) { prevOut := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0)) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}, nil) originTx.AddTxIn(txIn) - pkScript, err := bitcoin.PayToAddrScript(addr) + pkScript, err := txscript.PayToAddrScript(addr) c.Assert(err, IsNil) @@ -148,7 +147,7 @@ func (s *BTCSignerSuite) TestP2WPH(c *C) { prevOut := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0)) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}, nil) originTx.AddTxIn(txIn) - pkScript, err := bitcoin.PayToAddrScript(addr) + pkScript, err := txscript.PayToAddrScript(addr) c.Assert(err, IsNil) txOut := wire.NewTxOut(100000000, pkScript) originTx.AddTxOut(txOut) @@ -169,7 +168,7 @@ func (s *BTCSignerSuite) TestP2WPH(c *C) { txOut = wire.NewTxOut(0, nil) redeemTx.AddTxOut(txOut) txSigHashes := txscript.NewTxSigHashes(redeemTx, txscript.NewCannedPrevOutputFetcher([]byte{}, 0)) - pkScript, err = bitcoin.PayToAddrScript(addr) + pkScript, err = txscript.PayToAddrScript(addr) c.Assert(err, IsNil) { @@ -240,7 +239,7 @@ func TestAddWithdrawTxOutputs(t *testing.T) { // tss address and script tssAddr, err := signer.TSS().BTCAddress(chains.BitcoinTestnet.ChainId) require.NoError(t, err) - tssScript, err := bitcoin.PayToAddrScript(tssAddr) + tssScript, err := txscript.PayToAddrScript(tssAddr) require.NoError(t, err) fmt.Printf("tss address: %s", tssAddr.EncodeAddress()) @@ -248,7 +247,7 @@ func TestAddWithdrawTxOutputs(t *testing.T) { receiver := "bc1qaxf82vyzy8y80v000e7t64gpten7gawewzu42y" to, err := chains.DecodeBtcAddress(receiver, chains.BitcoinMainnet.ChainId) require.NoError(t, err) - toScript, err := bitcoin.PayToAddrScript(to) + toScript, err := txscript.PayToAddrScript(to) require.NoError(t, err) // test cases diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index f5bc856d5d..9614010a65 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -36,17 +36,6 @@ const ( LengthScriptP2PKH = 25 ) -// PayToAddrScript creates a new script to pay a transaction output to a the -// specified address. -func PayToAddrScript(addr btcutil.Address) ([]byte, error) { - switch addr := addr.(type) { - case *chains.AddressTaproot: - return chains.PayToWitnessTaprootScript(addr.ScriptAddress()) - default: - return txscript.PayToAddrScript(addr) - } -} - // IsPkScriptP2TR checks if the given script is a P2TR script func IsPkScriptP2TR(script []byte) bool { return len(script) == LengthScriptP2TR && script[0] == txscript.OP_1 && script[1] == 0x20 @@ -91,7 +80,7 @@ func DecodeScriptP2TR(scriptHex string, net *chaincfg.Params) (string, error) { } witnessProg := script[2:] - receiverAddress, err := chains.NewAddressTaproot(witnessProg, net) + receiverAddress, err := btcutil.NewAddressTaproot(witnessProg, net) if err != nil { // should never happen return "", errors.Wrapf(err, "error getting address from script %s", scriptHex) } @@ -278,7 +267,7 @@ func DecodeTSSVout(vout btcjson.Vout, receiverExpected string, chain chains.Chai // parse receiver address from vout var receiverVout string switch addr.(type) { - case *chains.AddressTaproot: + case *btcutil.AddressTaproot: receiverVout, err = DecodeScriptP2TR(vout.ScriptPubKey.Hex, chainParams) case *btcutil.AddressWitnessScriptHash: receiverVout, err = DecodeScriptP2WSH(vout.ScriptPubKey.Hex, chainParams)