address: add AddressTaproot
This commit is contained in:
parent
4a0c04f989
commit
c5b2b0b979
3 changed files with 216 additions and 9 deletions
78
address.go
78
address.go
|
@ -64,8 +64,8 @@ func encodeAddress(hash160 []byte, netID byte) string {
|
||||||
return base58.CheckEncode(hash160[:ripemd160.Size], netID)
|
return base58.CheckEncode(hash160[:ripemd160.Size], netID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeSegWitAddress creates a bech32 encoded address string representation
|
// encodeSegWitAddress creates a bech32 (or bech32m for SegWit v1) encoded
|
||||||
// from witness version and witness program.
|
// address string representation from witness version and witness program.
|
||||||
func encodeSegWitAddress(hrp string, witnessVersion byte, witnessProgram []byte) (string, error) {
|
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
|
// Group the address bytes into 5 bit groups, as this is what is used to
|
||||||
// encode each character in the address string.
|
// encode each character in the address string.
|
||||||
|
@ -79,7 +79,19 @@ func encodeSegWitAddress(hrp string, witnessVersion byte, witnessProgram []byte)
|
||||||
combined := make([]byte, len(converted)+1)
|
combined := make([]byte, len(converted)+1)
|
||||||
combined[0] = witnessVersion
|
combined[0] = witnessVersion
|
||||||
copy(combined[1:], converted)
|
copy(combined[1:], converted)
|
||||||
bech, err := bech32.Encode(hrp, combined)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -149,8 +161,9 @@ func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We currently only support P2WPKH and P2WSH, which is
|
// We currently only support P2WPKH and P2WSH, which is
|
||||||
// witness version 0.
|
// witness version 0 and P2TR which is witness version
|
||||||
if witnessVer != 0 {
|
// 1.
|
||||||
|
if witnessVer != 0 && witnessVer != 1 {
|
||||||
return nil, UnsupportedWitnessVerError(witnessVer)
|
return nil, UnsupportedWitnessVerError(witnessVer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +174,10 @@ func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) {
|
||||||
case 20:
|
case 20:
|
||||||
return newAddressWitnessPubKeyHash(hrp, witnessProg)
|
return newAddressWitnessPubKeyHash(hrp, witnessProg)
|
||||||
case 32:
|
case 32:
|
||||||
|
if witnessVer == 1 {
|
||||||
|
return newAddressTaproot(hrp, witnessProg)
|
||||||
|
}
|
||||||
|
|
||||||
return newAddressWitnessScriptHash(hrp, witnessProg)
|
return newAddressWitnessScriptHash(hrp, witnessProg)
|
||||||
default:
|
default:
|
||||||
return nil, UnsupportedWitnessProgLenError(len(witnessProg))
|
return nil, UnsupportedWitnessProgLenError(len(witnessProg))
|
||||||
|
@ -210,7 +227,7 @@ func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) {
|
||||||
// returns the witness version and witness program byte representation.
|
// returns the witness version and witness program byte representation.
|
||||||
func decodeSegWitAddress(address string) (byte, []byte, error) {
|
func decodeSegWitAddress(address string) (byte, []byte, error) {
|
||||||
// Decode the bech32 encoded address.
|
// Decode the bech32 encoded address.
|
||||||
_, data, err := bech32.Decode(address)
|
_, data, bech32version, err := bech32.DecodeGeneric(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
@ -246,6 +263,18 @@ func decodeSegWitAddress(address string) (byte, []byte, error) {
|
||||||
"version 0: %v", len(regrouped))
|
"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 version, regrouped, nil
|
return version, regrouped, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,3 +681,40 @@ func newAddressWitnessScriptHash(hrp string,
|
||||||
|
|
||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddressTaproot is an Address for a pay-to-taproot (P2TR) output. See BIP 341
|
||||||
|
// for further details.
|
||||||
|
type AddressTaproot struct {
|
||||||
|
AddressSegWit
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAddressTaproot returns a new AddressTaproot.
|
||||||
|
func NewAddressTaproot(witnessProg []byte,
|
||||||
|
net *chaincfg.Params) (*AddressTaproot, error) {
|
||||||
|
|
||||||
|
return newAddressTaproot(net.Bech32HRPSegwit, witnessProg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAddressWitnessScriptHash 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
|
||||||
|
}
|
||||||
|
|
118
address_test.go
118
address_test.go
|
@ -655,13 +655,123 @@ func TestAddresses(t *testing.T) {
|
||||||
},
|
},
|
||||||
net: &customParams,
|
net: &customParams,
|
||||||
},
|
},
|
||||||
// Unsupported witness versions (version 0 only supported at this point)
|
|
||||||
|
// P2TR address tests.
|
||||||
{
|
{
|
||||||
name: "segwit mainnet witness v1",
|
name: "segwit v1 mainnet p2tr",
|
||||||
addr: "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
|
addr: "bc1paardr2nczq0rx5rqpfwnvpzm497zvux64y0f7wjgcs7xuuuh2nnqwr2d5c",
|
||||||
|
encoded: "bc1paardr2nczq0rx5rqpfwnvpzm497zvux64y0f7wjgcs7xuuuh2nnqwr2d5c",
|
||||||
|
valid: true,
|
||||||
|
result: btcutil.TstAddressTaproot(
|
||||||
|
1, [32]byte{
|
||||||
|
0xef, 0x46, 0xd1, 0xaa, 0x78, 0x10, 0x1e, 0x33,
|
||||||
|
0x50, 0x60, 0x0a, 0x5d, 0x36, 0x04, 0x5b, 0xa9,
|
||||||
|
0x7c, 0x26, 0x70, 0xda, 0xa9, 0x1e, 0x9f, 0x3a,
|
||||||
|
0x48, 0xc4, 0x3c, 0x6e, 0x73, 0x97, 0x54, 0xe6,
|
||||||
|
}, chaincfg.MainNetParams.Bech32HRPSegwit,
|
||||||
|
),
|
||||||
|
f: func() (btcutil.Address, error) {
|
||||||
|
scriptHash := []byte{
|
||||||
|
0xef, 0x46, 0xd1, 0xaa, 0x78, 0x10, 0x1e, 0x33,
|
||||||
|
0x50, 0x60, 0x0a, 0x5d, 0x36, 0x04, 0x5b, 0xa9,
|
||||||
|
0x7c, 0x26, 0x70, 0xda, 0xa9, 0x1e, 0x9f, 0x3a,
|
||||||
|
0x48, 0xc4, 0x3c, 0x6e, 0x73, 0x97, 0x54, 0xe6,
|
||||||
|
}
|
||||||
|
return btcutil.NewAddressTaproot(
|
||||||
|
scriptHash, &chaincfg.MainNetParams,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Invalid bech32m tests. Source:
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
|
||||||
|
{
|
||||||
|
name: "segwit v1 invalid human-readable part",
|
||||||
|
addr: "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
|
||||||
valid: false,
|
valid: false,
|
||||||
net: &chaincfg.MainNetParams,
|
net: &chaincfg.MainNetParams,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 mainnet bech32 instead of bech32m",
|
||||||
|
addr: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 testnet bech32 instead of bech32m",
|
||||||
|
addr: "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.TestNet3Params,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 mainnet bech32 instead of bech32m upper case",
|
||||||
|
addr: "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v0 mainnet bech32m instead of bech32",
|
||||||
|
addr: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 testnet bech32 instead of bech32m second test",
|
||||||
|
addr: "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.TestNet3Params,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 mainnet bech32m invalid character in checksum",
|
||||||
|
addr: "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit mainnet witness v17",
|
||||||
|
addr: "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 mainnet bech32m invalid program length (1 byte)",
|
||||||
|
addr: "bc1pw5dgrnzv",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 mainnet bech32m invalid program length (41 bytes)",
|
||||||
|
addr: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 testnet bech32m mixed case",
|
||||||
|
addr: "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.TestNet3Params,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 mainnet bech32m zero padding of more than 4 bits",
|
||||||
|
addr: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 mainnet bech32m non-zero padding in 8-to-5-conversion",
|
||||||
|
addr: "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.TestNet3Params,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "segwit v1 mainnet bech32m empty data section",
|
||||||
|
addr: "bc1gmk9yu",
|
||||||
|
valid: false,
|
||||||
|
net: &chaincfg.MainNetParams,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Unsupported witness versions (version 0 and 1 only supported at this point)
|
||||||
{
|
{
|
||||||
name: "segwit mainnet witness v16",
|
name: "segwit mainnet witness v16",
|
||||||
addr: "BC1SW50QA3JX3S",
|
addr: "BC1SW50QA3JX3S",
|
||||||
|
@ -788,6 +898,8 @@ func TestAddresses(t *testing.T) {
|
||||||
saddr = btcutil.TstAddressSegwitSAddr(encoded)
|
saddr = btcutil.TstAddressSegwitSAddr(encoded)
|
||||||
case *btcutil.AddressWitnessScriptHash:
|
case *btcutil.AddressWitnessScriptHash:
|
||||||
saddr = btcutil.TstAddressSegwitSAddr(encoded)
|
saddr = btcutil.TstAddressSegwitSAddr(encoded)
|
||||||
|
case *btcutil.AddressTaproot:
|
||||||
|
saddr = btcutil.TstAddressTaprootSAddr(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check script address, as well as the Hash160 method for P2PKH and
|
// Check script address, as well as the Hash160 method for P2PKH and
|
||||||
|
|
|
@ -81,6 +81,19 @@ func TstAddressWitnessScriptHash(version byte, program [32]byte,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TstAddressTaproot creates an AddressTaproot, initiating the fields as given.
|
||||||
|
func TstAddressTaproot(version byte, program [32]byte,
|
||||||
|
hrp string) *AddressTaproot {
|
||||||
|
|
||||||
|
return &AddressTaproot{
|
||||||
|
AddressSegWit{
|
||||||
|
hrp: hrp,
|
||||||
|
witnessVersion: version,
|
||||||
|
witnessProgram: program[:],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TstAddressPubKey makes an AddressPubKey, setting the unexported fields with
|
// TstAddressPubKey makes an AddressPubKey, setting the unexported fields with
|
||||||
// the parameters.
|
// the parameters.
|
||||||
func TstAddressPubKey(serializedPubKey []byte, pubKeyFormat PubKeyFormat,
|
func TstAddressPubKey(serializedPubKey []byte, pubKeyFormat PubKeyFormat,
|
||||||
|
@ -116,3 +129,19 @@ func TstAddressSegwitSAddr(addr string) []byte {
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TstAddressTaprootSAddr returns the expected witness program bytes for a
|
||||||
|
// bech32m encoded P2TR bitcoin address.
|
||||||
|
func TstAddressTaprootSAddr(addr string) []byte {
|
||||||
|
_, data, err := bech32.Decode(addr)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First byte is version, rest is base 32 encoded data.
|
||||||
|
data, err = bech32.ConvertBits(data[1:], 5, 8, false)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue