Merge pull request #205 from guggero/schnorr-taproot

address: add AddressTaproot
This commit is contained in:
Olaoluwa Osuntokun 2021-09-29 16:32:59 -07:00 committed by GitHub
commit 9cdf59f60c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 309 additions and 127 deletions

View file

@ -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
} }
@ -506,25 +535,88 @@ func (a *AddressPubKey) PubKey() *btcec.PublicKey {
return a.pubKey return a.pubKey
} }
// AddressSegWit is the base address type for all SegWit addresses.
type AddressSegWit struct {
hrp string
witnessVersion byte
witnessProgram []byte
}
// 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
}
// 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()
}
// Hrp returns the human-readable part of the bech32 (or bech32m for SegWit v1)
// encoded AddressSegWit.
func (a *AddressSegWit) Hrp() string {
return a.hrp
}
// WitnessVersion returns the witness version of the AddressSegWit.
func (a *AddressSegWit) WitnessVersion() byte {
return a.witnessVersion
}
// WitnessProgram returns the witness program of the AddressSegWit.
func (a *AddressSegWit) WitnessProgram() []byte {
return a.witnessProgram[:]
}
// AddressWitnessPubKeyHash is an Address for a pay-to-witness-pubkey-hash // AddressWitnessPubKeyHash is an Address for a pay-to-witness-pubkey-hash
// (P2WPKH) output. See BIP 173 for further details regarding native segregated // (P2WPKH) output. See BIP 173 for further details regarding native segregated
// witness address encoding: // witness address encoding:
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
type AddressWitnessPubKeyHash struct { type AddressWitnessPubKeyHash struct {
hrp string AddressSegWit
witnessVersion byte
witnessProgram [20]byte
} }
// NewAddressWitnessPubKeyHash returns a new AddressWitnessPubKeyHash. // NewAddressWitnessPubKeyHash returns a new AddressWitnessPubKeyHash.
func NewAddressWitnessPubKeyHash(witnessProg []byte, net *chaincfg.Params) (*AddressWitnessPubKeyHash, error) { func NewAddressWitnessPubKeyHash(witnessProg []byte,
net *chaincfg.Params) (*AddressWitnessPubKeyHash, error) {
return newAddressWitnessPubKeyHash(net.Bech32HRPSegwit, witnessProg) return newAddressWitnessPubKeyHash(net.Bech32HRPSegwit, witnessProg)
} }
// newAddressWitnessPubKeyHash is an internal helper function to create an // newAddressWitnessPubKeyHash is an internal helper function to create an
// AddressWitnessPubKeyHash with a known human-readable part, rather than // AddressWitnessPubKeyHash with a known human-readable part, rather than
// looking it up through its parameters. // looking it up through its parameters.
func newAddressWitnessPubKeyHash(hrp string, witnessProg []byte) (*AddressWitnessPubKeyHash, error) { func newAddressWitnessPubKeyHash(hrp string,
witnessProg []byte) (*AddressWitnessPubKeyHash, error) {
// Check for valid program length for witness version 0, which is 20 // Check for valid program length for witness version 0, which is 20
// for P2WPKH. // for P2WPKH.
if len(witnessProg) != 20 { if len(witnessProg) != 20 {
@ -533,68 +625,22 @@ func newAddressWitnessPubKeyHash(hrp string, witnessProg []byte) (*AddressWitnes
} }
addr := &AddressWitnessPubKeyHash{ addr := &AddressWitnessPubKeyHash{
AddressSegWit{
hrp: strings.ToLower(hrp), hrp: strings.ToLower(hrp),
witnessVersion: 0x00, witnessVersion: 0x00,
witnessProgram: witnessProg,
},
} }
copy(addr.witnessProgram[:], witnessProg)
return addr, nil 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 // Hash160 returns the witness program of the AddressWitnessPubKeyHash as a
// byte array. // byte array.
func (a *AddressWitnessPubKeyHash) Hash160() *[20]byte { func (a *AddressWitnessPubKeyHash) Hash160() *[20]byte {
return &a.witnessProgram var pubKeyHashWitnessProgram [20]byte
copy(pubKeyHashWitnessProgram[:], a.witnessProgram)
return &pubKeyHashWitnessProgram
} }
// AddressWitnessScriptHash is an Address for a pay-to-witness-script-hash // AddressWitnessScriptHash is an Address for a pay-to-witness-script-hash
@ -602,20 +648,22 @@ func (a *AddressWitnessPubKeyHash) Hash160() *[20]byte {
// witness address encoding: // witness address encoding:
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
type AddressWitnessScriptHash struct { type AddressWitnessScriptHash struct {
hrp string AddressSegWit
witnessVersion byte
witnessProgram [32]byte
} }
// NewAddressWitnessScriptHash returns a new AddressWitnessPubKeyHash. // NewAddressWitnessScriptHash returns a new AddressWitnessPubKeyHash.
func NewAddressWitnessScriptHash(witnessProg []byte, net *chaincfg.Params) (*AddressWitnessScriptHash, error) { func NewAddressWitnessScriptHash(witnessProg []byte,
net *chaincfg.Params) (*AddressWitnessScriptHash, error) {
return newAddressWitnessScriptHash(net.Bech32HRPSegwit, witnessProg) return newAddressWitnessScriptHash(net.Bech32HRPSegwit, witnessProg)
} }
// newAddressWitnessScriptHash is an internal helper function to create an // newAddressWitnessScriptHash is an internal helper function to create an
// AddressWitnessScriptHash with a known human-readable part, rather than // AddressWitnessScriptHash with a known human-readable part, rather than
// looking it up through its parameters. // looking it up through its parameters.
func newAddressWitnessScriptHash(hrp string, witnessProg []byte) (*AddressWitnessScriptHash, error) { func newAddressWitnessScriptHash(hrp string,
witnessProg []byte) (*AddressWitnessScriptHash, error) {
// Check for valid program length for witness version 0, which is 32 // Check for valid program length for witness version 0, which is 32
// for P2WSH. // for P2WSH.
if len(witnessProg) != 32 { if len(witnessProg) != 32 {
@ -624,60 +672,49 @@ func newAddressWitnessScriptHash(hrp string, witnessProg []byte) (*AddressWitnes
} }
addr := &AddressWitnessScriptHash{ addr := &AddressWitnessScriptHash{
AddressSegWit{
hrp: strings.ToLower(hrp), hrp: strings.ToLower(hrp),
witnessVersion: 0x00, witnessVersion: 0x00,
witnessProgram: witnessProg,
},
} }
copy(addr.witnessProgram[:], witnessProg)
return addr, nil return addr, nil
} }
// EncodeAddress returns the bech32 string encoding of an // AddressTaproot is an Address for a pay-to-taproot (P2TR) output. See BIP 341
// AddressWitnessScriptHash. // for further details.
// Part of the Address interface. type AddressTaproot struct {
func (a *AddressWitnessScriptHash) EncodeAddress() string { AddressSegWit
str, err := encodeSegWitAddress(a.hrp, a.witnessVersion, }
a.witnessProgram[:])
if err != nil { // NewAddressTaproot returns a new AddressTaproot.
return "" 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")
} }
return str
}
// ScriptAddress returns the witness program for this address. addr := &AddressTaproot{
// Part of the Address interface. AddressSegWit{
func (a *AddressWitnessScriptHash) ScriptAddress() []byte { hrp: strings.ToLower(hrp),
return a.witnessProgram[:] witnessVersion: 0x01,
} witnessProgram: witnessProg,
},
}
// IsForNet returns whether or not the AddressWitnessScriptHash is associated return addr, nil
// 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

@ -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

View file

@ -59,9 +59,11 @@ func TstAddressWitnessPubKeyHash(version byte, program [20]byte,
hrp string) *AddressWitnessPubKeyHash { hrp string) *AddressWitnessPubKeyHash {
return &AddressWitnessPubKeyHash{ return &AddressWitnessPubKeyHash{
AddressSegWit{
hrp: hrp, hrp: hrp,
witnessVersion: version, witnessVersion: version,
witnessProgram: program, witnessProgram: program[:],
},
} }
} }
@ -71,9 +73,24 @@ func TstAddressWitnessScriptHash(version byte, program [32]byte,
hrp string) *AddressWitnessScriptHash { hrp string) *AddressWitnessScriptHash {
return &AddressWitnessScriptHash{ return &AddressWitnessScriptHash{
AddressSegWit{
hrp: hrp, hrp: hrp,
witnessVersion: version, witnessVersion: version,
witnessProgram: program, witnessProgram: program[:],
},
}
}
// 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[:],
},
} }
} }
@ -112,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
}