bech32 encoded segwit addresses.

This commit adds support for bech32 encoded segwit
addresses, namely AddressWitnessPubKeyHash and
AddressWitnessScriptHash. These are both specified
in BIP 173.
This commit is contained in:
Johan T. Halseth 2017-07-25 07:47:41 +02:00 committed by Dave Collins
parent 38c25ef9cd
commit fd898ec77a
3 changed files with 587 additions and 1 deletions

View file

@ -5,15 +5,35 @@
package btcutil package btcutil
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"strings"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/base58" "github.com/btcsuite/btcutil/base58"
"github.com/btcsuite/btcutil/bech32"
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
// UnsupportedWitnessVerError describes an error where a segwit address being
// decoded has an unsupported witness version.
type UnsupportedWitnessVerError byte
func (e UnsupportedWitnessVerError) Error() string {
return "unsupported witness version: " + string(e)
}
// UnsupportedWitnessProgLenError describes an error where a segwit address
// being decoded has an unsupported witness program length.
type UnsupportedWitnessProgLenError int
func (e UnsupportedWitnessProgLenError) Error() string {
return "unsupported witness program length: " + string(e)
}
var ( var (
// ErrChecksumMismatch describes an error where decoding failed due // ErrChecksumMismatch describes an error where decoding failed due
// to a bad checksum. // to a bad checksum.
@ -44,6 +64,39 @@ 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
// 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)
bech, err := bech32.Encode(hrp, combined)
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
}
// Address is an interface type for any type of destination a transaction // Address is an interface type for any type of destination a transaction
// output may spend to. This includes pay-to-pubkey (P2PK), pay-to-pubkey-hash // output may spend to. This includes pay-to-pubkey (P2PK), pay-to-pubkey-hash
// (P2PKH), and pay-to-script-hash (P2SH). Address is designed to be generic // (P2PKH), and pay-to-script-hash (P2SH). Address is designed to be generic
@ -81,6 +134,40 @@ type Address interface {
// When the address does not encode the network, such as in the case of a raw // When the address does not encode the network, such as in the case of a raw
// public key, the address will be associated with the passed defaultNet. // public key, the address will be associated with the passed defaultNet.
func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) { func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) {
// Bech32 encoded segwit addresses start with a human-readable part
// (hrp) followed by '1'. For Bitcoin mainnet the hrp is "bc", and for
// testnet it is "tb". If the address string has a prefix that matches
// one of the prefixes for the known networks, we try to decode it as
// a segwit address.
oneIndex := strings.LastIndexByte(addr, '1')
if oneIndex > 1 {
prefix := addr[:oneIndex+1]
if chaincfg.IsBech32SegwitPrefix(prefix) {
witnessVer, witnessProg, err := decodeSegWitAddress(addr)
if err != nil {
return nil, err
}
// We currently only support P2WPKH and P2WSH, which is
// witness version 0.
if witnessVer != 0 {
return nil, UnsupportedWitnessVerError(witnessVer)
}
// The HRP is everything before the found '1'.
hrp := prefix[:len(prefix)-1]
switch len(witnessProg) {
case 20:
return newAddressWitnessPubKeyHash(hrp, witnessProg)
case 32:
return newAddressWitnessScriptHash(hrp, witnessProg)
default:
return nil, UnsupportedWitnessProgLenError(len(witnessProg))
}
}
}
// Serialized public keys are either 65 bytes (130 hex chars) if // Serialized public keys are either 65 bytes (130 hex chars) if
// uncompressed/hybrid or 33 bytes (66 hex chars) if compressed. // uncompressed/hybrid or 33 bytes (66 hex chars) if compressed.
if len(addr) == 130 || len(addr) == 66 { if len(addr) == 130 || len(addr) == 66 {
@ -119,6 +206,49 @@ func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) {
} }
} }
// decodeSegWitAddress parses a bech32 encoded segwit address string and
// returns the witness version and witness program byte representation.
func decodeSegWitAddress(address string) (byte, []byte, error) {
// Decode the bech32 encoded address.
_, data, err := bech32.Decode(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))
}
return version, regrouped, nil
}
// AddressPubKeyHash is an Address for a pay-to-pubkey-hash (P2PKH) // AddressPubKeyHash is an Address for a pay-to-pubkey-hash (P2PKH)
// transaction. // transaction.
type AddressPubKeyHash struct { type AddressPubKeyHash struct {
@ -375,3 +505,179 @@ func (a *AddressPubKey) AddressPubKeyHash() *AddressPubKeyHash {
func (a *AddressPubKey) PubKey() *btcec.PublicKey { func (a *AddressPubKey) PubKey() *btcec.PublicKey {
return a.pubKey return a.pubKey
} }
// AddressWitnessPubKeyHash is an Address for a pay-to-witness-pubkey-hash
// (P2WPKH) output. See BIP 173 for further details regarding native segregated
// witness address encoding:
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
type AddressWitnessPubKeyHash struct {
hrp string
witnessVersion byte
witnessProgram [20]byte
}
// NewAddressWitnessPubKeyHash returns a new AddressWitnessPubKeyHash.
func NewAddressWitnessPubKeyHash(witnessProg []byte, net *chaincfg.Params) (*AddressWitnessPubKeyHash, error) {
return newAddressWitnessPubKeyHash(net.Bech32HRPSegwit, witnessProg)
}
// newAddressWitnessPubKeyHash is an internal helper function to create an
// AddressWitnessPubKeyHash with a known human-readable part, rather than
// looking it up through its parameters.
func newAddressWitnessPubKeyHash(hrp string, witnessProg []byte) (*AddressWitnessPubKeyHash, error) {
// Check for valid program length for witness version 0, which is 20
// for P2WPKH.
if len(witnessProg) != 20 {
return nil, errors.New("witness program must be 20 " +
"bytes for p2wpkh")
}
addr := &AddressWitnessPubKeyHash{
hrp: strings.ToLower(hrp),
witnessVersion: 0x00,
}
copy(addr.witnessProgram[:], witnessProg)
return addr, nil
}
// EncodeAddress returns the bech32 string encoding of an
// AddressWitnessPubKeyHash.
// Part of the Address interface.
func (a *AddressWitnessPubKeyHash) EncodeAddress() string {
str, err := encodeSegWitAddress(a.hrp, a.witnessVersion,
a.witnessProgram[:])
if err != nil {
return ""
}
return str
}
// ScriptAddress returns the witness program for this address.
// Part of the Address interface.
func (a *AddressWitnessPubKeyHash) ScriptAddress() []byte {
return a.witnessProgram[:]
}
// IsForNet returns whether or not the AddressWitnessPubKeyHash is associated
// with the passed bitcoin network.
// Part of the Address interface.
func (a *AddressWitnessPubKeyHash) 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.
// Part of the Address interface.
func (a *AddressWitnessPubKeyHash) String() string {
return a.EncodeAddress()
}
// Hrp returns the human-readable part of the bech32 encoded
// AddressWitnessPubKeyHash.
func (a *AddressWitnessPubKeyHash) Hrp() string {
return a.hrp
}
// WitnessVersion returns the witness version of the AddressWitnessPubKeyHash.
func (a *AddressWitnessPubKeyHash) WitnessVersion() byte {
return a.witnessVersion
}
// WitnessProgram returns the witness program of the AddressWitnessPubKeyHash.
func (a *AddressWitnessPubKeyHash) WitnessProgram() []byte {
return a.witnessProgram[:]
}
// Hash160 returns the witness program of the AddressWitnessPubKeyHash as a
// byte array.
func (a *AddressWitnessPubKeyHash) Hash160() *[20]byte {
return &a.witnessProgram
}
// AddressWitnessScriptHash is an Address for a pay-to-witness-script-hash
// (P2WSH) output. See BIP 173 for further details regarding native segregated
// witness address encoding:
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
type AddressWitnessScriptHash struct {
hrp string
witnessVersion byte
witnessProgram [32]byte
}
// NewAddressWitnessScriptHash returns a new AddressWitnessPubKeyHash.
func NewAddressWitnessScriptHash(witnessProg []byte, net *chaincfg.Params) (*AddressWitnessScriptHash, error) {
return newAddressWitnessScriptHash(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 newAddressWitnessScriptHash(hrp string, witnessProg []byte) (*AddressWitnessScriptHash, error) {
// Check for valid program length for witness version 0, which is 32
// for P2WSH.
if len(witnessProg) != 32 {
return nil, errors.New("witness program must be 32 " +
"bytes for p2wsh")
}
addr := &AddressWitnessScriptHash{
hrp: strings.ToLower(hrp),
witnessVersion: 0x00,
}
copy(addr.witnessProgram[:], witnessProg)
return addr, nil
}
// EncodeAddress returns the bech32 string encoding of an
// AddressWitnessScriptHash.
// Part of the Address interface.
func (a *AddressWitnessScriptHash) EncodeAddress() string {
str, err := encodeSegWitAddress(a.hrp, a.witnessVersion,
a.witnessProgram[:])
if err != nil {
return ""
}
return str
}
// ScriptAddress returns the witness program for this address.
// Part of the Address interface.
func (a *AddressWitnessScriptHash) ScriptAddress() []byte {
return a.witnessProgram[:]
}
// IsForNet returns whether or not the AddressWitnessScriptHash is associated
// with the passed bitcoin network.
// Part of the Address interface.
func (a *AddressWitnessScriptHash) IsForNet(net *chaincfg.Params) bool {
return a.hrp == net.Bech32HRPSegwit
}
// String returns a human-readable string for the AddressWitnessScriptHash.
// This is equivalent to calling EncodeAddress, but is provided so the type
// can be used as a fmt.Stringer.
// Part of the Address interface.
func (a *AddressWitnessScriptHash) String() string {
return a.EncodeAddress()
}
// Hrp returns the human-readable part of the bech32 encoded
// AddressWitnessScriptHash.
func (a *AddressWitnessScriptHash) Hrp() string {
return a.hrp
}
// WitnessVersion returns the witness version of the AddressWitnessScriptHash.
func (a *AddressWitnessScriptHash) WitnessVersion() byte {
return a.witnessVersion
}
// WitnessProgram returns the witness program of the AddressWitnessScriptHash.
func (a *AddressWitnessScriptHash) WitnessProgram() []byte {
return a.witnessProgram[:]
}

View file

@ -9,6 +9,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
@ -94,11 +95,13 @@ func TestAddresses(t *testing.T) {
0xaa} 0xaa}
return btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.MainNetParams) return btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.MainNetParams)
}, },
net: &chaincfg.MainNetParams,
}, },
{ {
name: "p2pkh bad checksum", name: "p2pkh bad checksum",
addr: "1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gY", addr: "1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gY",
valid: false, valid: false,
net: &chaincfg.MainNetParams,
}, },
// Positive P2SH tests. // Positive P2SH tests.
@ -195,6 +198,7 @@ func TestAddresses(t *testing.T) {
0x10} 0x10}
return btcutil.NewAddressScriptHashFromHash(hash, &chaincfg.MainNetParams) return btcutil.NewAddressScriptHashFromHash(hash, &chaincfg.MainNetParams)
}, },
net: &chaincfg.MainNetParams,
}, },
// Positive P2PK tests. // Positive P2PK tests.
@ -460,6 +464,188 @@ func TestAddresses(t *testing.T) {
}, },
net: &chaincfg.TestNet3Params, net: &chaincfg.TestNet3Params,
}, },
// Segwit address tests.
{
name: "segwit mainnet p2wpkh v0",
addr: "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
encoded: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
valid: true,
result: btcutil.TstAddressWitnessPubKeyHash(
0,
[20]byte{
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94,
0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6},
chaincfg.MainNetParams.Bech32HRPSegwit),
f: func() (btcutil.Address, error) {
pkHash := []byte{
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94,
0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6}
return btcutil.NewAddressWitnessPubKeyHash(pkHash, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
{
name: "segwit mainnet p2wsh v0",
addr: "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3",
encoded: "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3",
valid: true,
result: btcutil.TstAddressWitnessScriptHash(
0,
[32]byte{
0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68,
0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13,
0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1,
0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62},
chaincfg.MainNetParams.Bech32HRPSegwit),
f: func() (btcutil.Address, error) {
scriptHash := []byte{
0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68,
0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13,
0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1,
0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62}
return btcutil.NewAddressWitnessScriptHash(scriptHash, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
{
name: "segwit testnet p2wpkh v0",
addr: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx",
encoded: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx",
valid: true,
result: btcutil.TstAddressWitnessPubKeyHash(
0,
[20]byte{
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94,
0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6},
chaincfg.TestNet3Params.Bech32HRPSegwit),
f: func() (btcutil.Address, error) {
pkHash := []byte{
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94,
0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6}
return btcutil.NewAddressWitnessPubKeyHash(pkHash, &chaincfg.TestNet3Params)
},
net: &chaincfg.TestNet3Params,
},
{
name: "segwit testnet p2wsh v0",
addr: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
encoded: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
valid: true,
result: btcutil.TstAddressWitnessScriptHash(
0,
[32]byte{
0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68,
0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13,
0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1,
0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62},
chaincfg.TestNet3Params.Bech32HRPSegwit),
f: func() (btcutil.Address, error) {
scriptHash := []byte{
0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68,
0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13,
0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1,
0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62}
return btcutil.NewAddressWitnessScriptHash(scriptHash, &chaincfg.TestNet3Params)
},
net: &chaincfg.TestNet3Params,
},
{
name: "segwit testnet p2wsh witness v0",
addr: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
encoded: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
valid: true,
result: btcutil.TstAddressWitnessScriptHash(
0,
[32]byte{
0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62,
0x21, 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66,
0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2,
0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33},
chaincfg.TestNet3Params.Bech32HRPSegwit),
f: func() (btcutil.Address, error) {
scriptHash := []byte{
0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62,
0x21, 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66,
0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2,
0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33}
return btcutil.NewAddressWitnessScriptHash(scriptHash, &chaincfg.TestNet3Params)
},
net: &chaincfg.TestNet3Params,
},
// Unsupported witness versions (version 0 only supported at this point)
{
name: "segwit mainnet witness v1",
addr: "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
valid: false,
net: &chaincfg.MainNetParams,
},
{
name: "segwit mainnet witness v16",
addr: "BC1SW50QA3JX3S",
valid: false,
net: &chaincfg.MainNetParams,
},
{
name: "segwit mainnet witness v2",
addr: "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
valid: false,
net: &chaincfg.MainNetParams,
},
// Invalid segwit addresses
{
name: "segwit invalid hrp",
addr: "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
valid: false,
net: &chaincfg.TestNet3Params,
},
{
name: "segwit invalid checksum",
addr: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
valid: false,
net: &chaincfg.MainNetParams,
},
{
name: "segwit invalid witness version",
addr: "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
valid: false,
net: &chaincfg.MainNetParams,
},
{
name: "segwit invalid program length",
addr: "bc1rw5uspcuh",
valid: false,
net: &chaincfg.MainNetParams,
},
{
name: "segwit invalid program length",
addr: "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
valid: false,
net: &chaincfg.MainNetParams,
},
{
name: "segwit invalid program length for witness version 0 (per BIP141)",
addr: "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
valid: false,
net: &chaincfg.MainNetParams,
},
{
name: "segwit mixed case",
addr: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
valid: false,
net: &chaincfg.TestNet3Params,
},
{
name: "segwit zero padding of more than 4 bits",
addr: "tb1pw508d6qejxtdg4y5r3zarqfsj6c3",
valid: false,
net: &chaincfg.TestNet3Params,
},
{
name: "segwit non-zero padding in 8-to-5 conversion",
addr: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
valid: false,
net: &chaincfg.TestNet3Params,
},
} }
for _, test := range tests { for _, test := range tests {
@ -474,7 +660,16 @@ func TestAddresses(t *testing.T) {
// Ensure the stringer returns the same address as the // Ensure the stringer returns the same address as the
// original. // original.
if decodedStringer, ok := decoded.(fmt.Stringer); ok { if decodedStringer, ok := decoded.(fmt.Stringer); ok {
if test.addr != decodedStringer.String() { addr := test.addr
// For Segwit addresses the string representation
// will always be lower case, so in that case we
// convert the original to lower case first.
if strings.Contains(test.name, "segwit") {
addr = strings.ToLower(addr)
}
if addr != decodedStringer.String() {
t.Errorf("%v: String on decoded value does not match expected value: %v != %v", t.Errorf("%v: String on decoded value does not match expected value: %v != %v",
test.name, test.addr, decodedStringer.String()) test.name, test.addr, decodedStringer.String())
return return
@ -502,6 +697,10 @@ func TestAddresses(t *testing.T) {
// Ignore the error here since the script // Ignore the error here since the script
// address is checked below. // address is checked below.
saddr, _ = hex.DecodeString(d.String()) saddr, _ = hex.DecodeString(d.String())
case *btcutil.AddressWitnessPubKeyHash:
saddr = btcutil.TstAddressSegwitSAddr(encoded)
case *btcutil.AddressWitnessScriptHash:
saddr = btcutil.TstAddressSegwitSAddr(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
@ -525,6 +724,46 @@ func TestAddresses(t *testing.T) {
test.name, saddr, h) test.name, saddr, h)
return return
} }
case *btcutil.AddressWitnessPubKeyHash:
if hrp := a.Hrp(); test.net.Bech32HRPSegwit != hrp {
t.Errorf("%v: hrps do not match:\n%x != \n%x",
test.name, test.net.Bech32HRPSegwit, hrp)
return
}
expVer := test.result.(*btcutil.AddressWitnessPubKeyHash).WitnessVersion()
if v := a.WitnessVersion(); v != expVer {
t.Errorf("%v: witness versions do not match:\n%x != \n%x",
test.name, expVer, v)
return
}
if p := a.WitnessProgram(); !bytes.Equal(saddr, p) {
t.Errorf("%v: witness programs do not match:\n%x != \n%x",
test.name, saddr, p)
return
}
case *btcutil.AddressWitnessScriptHash:
if hrp := a.Hrp(); test.net.Bech32HRPSegwit != hrp {
t.Errorf("%v: hrps do not match:\n%x != \n%x",
test.name, test.net.Bech32HRPSegwit, hrp)
return
}
expVer := test.result.(*btcutil.AddressWitnessScriptHash).WitnessVersion()
if v := a.WitnessVersion(); v != expVer {
t.Errorf("%v: witness versions do not match:\n%x != \n%x",
test.name, expVer, v)
return
}
if p := a.WitnessProgram(); !bytes.Equal(saddr, p) {
t.Errorf("%v: witness programs do not match:\n%x != \n%x",
test.name, saddr, p)
return
}
} }
// Ensure the address is for the expected network. // Ensure the address is for the expected network.

View file

@ -14,6 +14,7 @@ package btcutil
import ( import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil/base58" "github.com/btcsuite/btcutil/base58"
"github.com/btcsuite/btcutil/bech32"
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
@ -52,6 +53,30 @@ func TstAddressScriptHash(hash [ripemd160.Size]byte,
} }
} }
// TstAddressWitnessPubKeyHash creates an AddressWitnessPubKeyHash, initiating
// the fields as given.
func TstAddressWitnessPubKeyHash(version byte, program [20]byte,
hrp string) *AddressWitnessPubKeyHash {
return &AddressWitnessPubKeyHash{
hrp: hrp,
witnessVersion: version,
witnessProgram: program,
}
}
// TstAddressWitnessScriptHash creates an AddressWitnessScriptHash, initiating
// the fields as given.
func TstAddressWitnessScriptHash(version byte, program [32]byte,
hrp string) *AddressWitnessScriptHash {
return &AddressWitnessScriptHash{
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,
@ -71,3 +96,19 @@ func TstAddressSAddr(addr string) []byte {
decoded := base58.Decode(addr) decoded := base58.Decode(addr)
return decoded[1 : 1+ripemd160.Size] return decoded[1 : 1+ripemd160.Size]
} }
// TstAddressSegwitSAddr returns the expected witness program bytes for
// bech32 encoded P2WPKH and P2WSH bitcoin addresses.
func TstAddressSegwitSAddr(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
}