Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Bitcoin P2TR, P2WSH, P2SH, P2PKH address types #1942

Merged
merged 21 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
18c7196
backport/add a AddressTaproot type that satisfies btcutil.Address int…
brewmaster012 Mar 6, 2024
f78eb47
support taproot in DecodeBtcAddress
brewmaster012 Mar 6, 2024
0c167cd
minor tweak
brewmaster012 Mar 6, 2024
8252ca3
Merge branch 'develop' into feat-support-taproot
ws4charlie Mar 13, 2024
b0b2d74
Merge branch 'develop' into feat-support-taproot
ws4charlie Mar 21, 2024
a60375c
Merge branch 'develop' into feat-support-taproot
ws4charlie Mar 22, 2024
c2dfb7a
initial commit to support different types of BTC addresses
ws4charlie Mar 22, 2024
738d265
added decoding for more intx types
ws4charlie Mar 22, 2024
291322b
added btc intx address decoding and tests
ws4charlie Mar 24, 2024
18fc62f
fix compile error
ws4charlie Mar 25, 2024
50db809
Merge branch 'v16.0.0' of https://github.com/zeta-chain/node into fea…
ws4charlie Mar 25, 2024
76b6817
adjusted btc outtx size and withdrawer fee
ws4charlie Mar 26, 2024
c338d41
added changelog entry
ws4charlie Mar 26, 2024
31a0dfe
fix unit tests
ws4charlie Mar 26, 2024
82527b5
added e2e tests for different types of bitcoin addresses
ws4charlie Mar 26, 2024
d2935b9
try retriggering CI
lumtis Mar 27, 2024
bc02fb7
e2e tests all PR
lumtis Mar 27, 2024
2bcdfbc
resolved some PR review feedback
ws4charlie Mar 28, 2024
edc9be6
removed panic from test method LoadObjectFromJSONFile
ws4charlie Mar 28, 2024
f02cc52
improved function and added comments
ws4charlie Mar 28, 2024
a341da9
Merge branch 'v16.0.0' into feat-support-taproot
ws4charlie Mar 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- develop
pull_request:
branches:
- develop
- "*"
types:
- synchronize
- opened
Expand Down
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* [1755](https://github.com/zeta-chain/node/issues/1755) - use evm JSON RPC for inbound tx (including blob tx) observation.
* [1815](https://github.com/zeta-chain/node/pull/1815) - add authority module for authorized actions
* [1884](https://github.com/zeta-chain/node/pull/1884) - added zetatool cmd, added subcommand to filter deposits
* [1942](https://github.com/zeta-chain/node/pull/1942) - support Bitcoin P2TR, P2WSH, P2SH, P2PKH addresses
* [1954](https://github.com/zeta-chain/node/pull/1954) - add metric for concurrent keysigns

### Tests
Expand Down Expand Up @@ -160,6 +161,7 @@
* [1675](https://github.com/zeta-chain/node/issues/1675) - use chain param ConfirmationCount for bitcoin confirmation

## Chores

* [1694](https://github.com/zeta-chain/node/pull/1694) - remove standalone network, use require testing package for the entire node folder

## Version: v12.1.0
Expand All @@ -172,6 +174,7 @@
* [1658](https://github.com/zeta-chain/node/pull/1658) - modify emission distribution to use fixed block rewards

### Fixes

* [1535](https://github.com/zeta-chain/node/issues/1535) - Avoid voting on wrong ballots due to false blockNumber in EVM tx receipt
* [1588](https://github.com/zeta-chain/node/pull/1588) - fix chain params comparison logic
* [1650](https://github.com/zeta-chain/node/pull/1605) - exempt (discounted) *system txs* from min gas price check and gas fee deduction
Expand Down
6 changes: 5 additions & 1 deletion cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
e2etests.TestZetaDepositRestrictedName,
}
bitcoinTests := []string{
e2etests.TestBitcoinWithdrawName,
e2etests.TestBitcoinWithdrawSegWitName,
e2etests.TestBitcoinWithdrawTaprootName,
e2etests.TestBitcoinWithdrawLegacyName,
e2etests.TestBitcoinWithdrawP2SHName,
e2etests.TestBitcoinWithdrawP2WSHName,
e2etests.TestBitcoinWithdrawInvalidAddressName,
e2etests.TestZetaWithdrawBTCRevertName,
e2etests.TestCrosschainSwapName,
Expand Down
36 changes: 34 additions & 2 deletions common/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

"github.com/btcsuite/btcutil"
eth "github.com/ethereum/go-ethereum/common"
"github.com/zeta-chain/zetacore/common/bitcoin"
)

type Address string
Expand Down Expand Up @@ -51,6 +52,7 @@
}
}

// DecodeBtcAddress decodes a BTC address from a given string and chainID
lumtis marked this conversation as resolved.
Show resolved Hide resolved
func DecodeBtcAddress(inputAddress string, chainID int64) (address btcutil.Address, err error) {
defer func() {
if r := recover(); r != nil {
Expand All @@ -63,13 +65,43 @@
if err != nil {
return nil, err
}
if chainParams == nil {
return nil, fmt.Errorf("chain params not found")

Check warning on line 69 in common/address.go

View check run for this annotation

Codecov / codecov/patch

common/address.go#L69

Added line #L69 was not covered by tests
}
// test taproot address type
address, err = bitcoin.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
address, err = btcutil.DecodeAddress(inputAddress, chainParams)
if err != nil {
return nil, fmt.Errorf("decode address failed: %s , for input address %s", err.Error(), inputAddress)
return nil, fmt.Errorf("decode address failed: %s, for input address %s", err.Error(), inputAddress)
}
ok := address.IsForNet(chainParams)
if !ok {
return nil, fmt.Errorf("address is not for network %s", chainParams.Name)
return nil, fmt.Errorf("address %s is not for network %s", inputAddress, chainParams.Name)
}
return
}

// IsBtcAddressSupported returns true if the given BTC address is supported
func IsBtcAddressSupported(addr btcutil.Address) bool {
switch addr.(type) {
// P2TR address
case *bitcoin.AddressTaproot,
// P2WSH address
*btcutil.AddressWitnessScriptHash,
// P2WPKH address
*btcutil.AddressWitnessPubKeyHash,
// P2SH address
*btcutil.AddressScriptHash,
// P2PKH address
*btcutil.AddressPubKeyHash:
return true
}
return false

Check warning on line 106 in common/address.go

View check run for this annotation

Codecov / codecov/patch

common/address.go#L106

Added line #L106 was not covered by tests
}
255 changes: 254 additions & 1 deletion common/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"errors"
"testing"

"github.com/btcsuite/btcutil"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/zetacore/common/bitcoin"

. "gopkg.in/check.v1"
)
Expand Down Expand Up @@ -65,12 +67,263 @@ func TestDecodeBtcAddress(t *testing.T) {

t.Run("non legacy valid address with incorrect params", func(t *testing.T) {
_, err := DecodeBtcAddress("bcrt1qy9pqmk2pd9sv63g27jt8r657wy0d9uee4x2dt2", BtcMainnetChain().ChainId)
require.ErrorContains(t, err, "address is not for network mainnet")
require.ErrorContains(t, err, "not for network mainnet")
})
t.Run("non legacy valid address with correct params", func(t *testing.T) {
_, err := DecodeBtcAddress("bcrt1qy9pqmk2pd9sv63g27jt8r657wy0d9uee4x2dt2", BtcRegtestChain().ChainId)
require.NoError(t, err)
})

t.Run("taproot address with correct params", func(t *testing.T) {
_, err := DecodeBtcAddress("bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c", BtcMainnetChain().ChainId)
require.NoError(t, err)
})
t.Run("taproot address with incorrect params", func(t *testing.T) {
_, err := DecodeBtcAddress("bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c", BtcTestNetChain().ChainId)
require.ErrorContains(t, err, "not for network testnet")
})
}

func Test_IsBtcAddressSupported_P2TR(t *testing.T) {
tests := []struct {
name string
addr string
chainId int64
supported bool
}{
{
// https://mempool.space/tx/259fc21e63e138136c8f19270a0f7ca10039a66a474f91d23a17896f46e677a7
name: "mainnet taproot address",
addr: "bc1p4scddlkkuw9486579autxumxmkvuphm5pz4jvf7f6pdh50p2uzqstawjt9",
chainId: BtcMainnetChain().ChainId,
supported: true,
},
{
// https://mempool.space/testnet/tx/24991bd2fdc4f744bf7bbd915d4915925eecebdae249f81e057c0a6ffb700ab9
name: "testnet taproot address",
addr: "tb1p7qqaucx69xtwkx7vwmhz03xjmzxxpy3hk29y7q06mt3k6a8sehhsu5lacw",
chainId: BtcTestNetChain().ChainId,
supported: true,
},
{
name: "regtest taproot address",
addr: "bcrt1pqqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0sj9hjuh",
chainId: BtcRegtestChain().ChainId,
supported: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// it should be a taproot address
addr, err := DecodeBtcAddress(tt.addr, tt.chainId)
require.NoError(t, err)
_, ok := addr.(*bitcoin.AddressTaproot)
require.True(t, ok)

// it should be supported
require.NoError(t, err)
supported := IsBtcAddressSupported(addr)
require.Equal(t, tt.supported, supported)
})
}
}

func Test_IsBtcAddressSupported_P2WSH(t *testing.T) {
tests := []struct {
name string
addr string
chainId int64
supported bool
}{
{
// https://mempool.space/tx/791bb9d16f7ab05f70a116d18eaf3552faf77b9d5688699a480261424b4f7e53
name: "mainnet P2WSH address",
addr: "bc1qqv6pwn470vu0tssdfha4zdk89v3c8ch5lsnyy855k9hcrcv3evequdmjmc",
chainId: BtcMainnetChain().ChainId,
supported: true,
},
{
// https://mempool.space/testnet/tx/78fac3f0d4c0174c88d21c4bb1e23a8f007e890c6d2cfa64c97389ead16c51ed
name: "testnet P2WSH address",
addr: "tb1quhassyrlj43qar0mn0k5sufyp6mazmh2q85lr6ex8ehqfhxpzsksllwrsu",
chainId: BtcTestNetChain().ChainId,
supported: true,
},
{
name: "regtest P2WSH address",
addr: "bcrt1qm9mzhyky4w853ft2ms6dtqdyyu3z2tmrq8jg8xglhyuv0dsxzmgs2f0sqy",
chainId: BtcRegtestChain().ChainId,
supported: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// it should be a P2WSH address
addr, err := DecodeBtcAddress(tt.addr, tt.chainId)
require.NoError(t, err)
_, ok := addr.(*btcutil.AddressWitnessScriptHash)
require.True(t, ok)

// it should be supported
supported := IsBtcAddressSupported(addr)
require.Equal(t, tt.supported, supported)
})
}
}

func Test_IsBtcAddressSupported_P2WPKH(t *testing.T) {
tests := []struct {
name string
addr string
chainId int64
supported bool
}{
{
// https://mempool.space/tx/5d09d232bfe41c7cb831bf53fc2e4029ab33a99087fd5328a2331b52ff2ebe5b
name: "mainnet P2WPKH address",
addr: "bc1qaxf82vyzy8y80v000e7t64gpten7gawewzu42y",
chainId: BtcMainnetChain().ChainId,
supported: true,
},
{
// https://mempool.space/testnet/tx/508b4d723c754bad001eae9b7f3c12377d3307bd5b595c27fd8a90089094f0e9
name: "testnet P2WPKH address",
addr: "tb1q6rufg6myrxurdn0h57d2qhtm9zfmjw2mzcm05q",
chainId: BtcTestNetChain().ChainId,
supported: true,
},
{
name: "regtest P2WPKH address",
addr: "bcrt1qy9pqmk2pd9sv63g27jt8r657wy0d9uee4x2dt2",
chainId: BtcRegtestChain().ChainId,
supported: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// it should be a P2WPKH address
addr, err := DecodeBtcAddress(tt.addr, tt.chainId)
require.NoError(t, err)
_, ok := addr.(*btcutil.AddressWitnessPubKeyHash)
require.True(t, ok)

// it should be supported
supported := IsBtcAddressSupported(addr)
require.Equal(t, tt.supported, supported)
})
}
}

func Test_IsBtcAddressSupported_P2SH(t *testing.T) {
tests := []struct {
name string
addr string
chainId int64
supported bool
}{
{
// https://mempool.space/tx/fd68c8b4478686ca6f5ae4c28eaab055490650dbdaa6c2c8e380a7e075958a21
name: "mainnet P2SH address",
addr: "327z4GyFM8Y8DiYfasGKQWhRK4MvyMSEgE",
chainId: BtcMainnetChain().ChainId,
supported: true,
},
{
// https://mempool.space/testnet/tx/0c8c8f94817e0288a5273f5c971adaa3cee18a895c3ec8544785dddcd96f3848
name: "testnet P2SH address 1",
addr: "2N6AoUj3KPS7wNGZXuCckh8YEWcSYNsGbqd",
chainId: BtcTestNetChain().ChainId,
supported: true,
},
{
// https://mempool.space/testnet/tx/b5e074c5e021fcbd91ea14b1db29dfe5d14e1a6e046039467bf6ada7f8cc01b3
name: "testnet P2SH address 2",
addr: "2MwbFpRpZWv4zREjbdLB9jVW3Q8xonpVeyE",
chainId: BtcTestNetChain().ChainId,
supported: true,
},
{
name: "testnet P2SH address 1 should also be supported in regtest",
addr: "2N6AoUj3KPS7wNGZXuCckh8YEWcSYNsGbqd",
chainId: BtcRegtestChain().ChainId,
supported: true,
},
{
name: "testnet P2SH address 2 should also be supported in regtest",
addr: "2MwbFpRpZWv4zREjbdLB9jVW3Q8xonpVeyE",
chainId: BtcRegtestChain().ChainId,
supported: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// it should be a P2SH address
addr, err := DecodeBtcAddress(tt.addr, tt.chainId)
require.NoError(t, err)
_, ok := addr.(*btcutil.AddressScriptHash)
require.True(t, ok)

// it should be supported
supported := IsBtcAddressSupported(addr)
require.Equal(t, tt.supported, supported)
})
}
}

func Test_IsBtcAddressSupported_P2PKH(t *testing.T) {
tests := []struct {
name string
addr string
chainId int64
supported bool
}{
{
// https://mempool.space/tx/9c741de6e17382b7a9113fc811e3558981a35a360e3d1262a6675892c91322ca
name: "mainnet P2PKH address 1",
addr: "1FueivsE338W2LgifJ25HhTcVJ7CRT8kte",
chainId: BtcMainnetChain().ChainId,
supported: true,
},
{
// https://mempool.space/testnet/tx/1e3974386f071de7f65cabb57346c1a22ec9b3e211a96928a98149673f681237
name: "testnet P2PKH address 1",
addr: "mxpYha3UJKUgSwsAz2qYRqaDSwAkKZ3YEY",
chainId: BtcTestNetChain().ChainId,
supported: true,
},
{
// https://mempool.space/testnet/tx/e48459f372727f2253b0ea8c71ded83e8270873b8a044feb3435fc7a799a648f
name: "testnet P2PKH address 2",
addr: "n1gXcqxmzwqHmqmgobe1XXuJaweSu69tZz",
chainId: BtcTestNetChain().ChainId,
supported: true,
},
{
name: "testnet P2PKH address should also be supported in regtest",
addr: "mxpYha3UJKUgSwsAz2qYRqaDSwAkKZ3YEY",
chainId: BtcRegtestChain().ChainId,
supported: true,
},
{
name: "testnet P2PKH address should also be supported in regtest",
addr: "n1gXcqxmzwqHmqmgobe1XXuJaweSu69tZz",
chainId: BtcRegtestChain().ChainId,
supported: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// it should be a P2PKH address
addr, err := DecodeBtcAddress(tt.addr, tt.chainId)
require.NoError(t, err)
_, ok := addr.(*btcutil.AddressPubKeyHash)
require.True(t, ok)

// it should be supported
supported := IsBtcAddressSupported(addr)
require.Equal(t, tt.supported, supported)
})
}
}

func TestConvertRecoverToError(t *testing.T) {
Expand Down
Loading
Loading