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:
parent
38c25ef9cd
commit
fd898ec77a
3 changed files with 587 additions and 1 deletions
306
address.go
306
address.go
|
@ -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[:]
|
||||||
|
}
|
||||||
|
|
241
address_test.go
241
address_test.go
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue