address: add AddressTaproot

This commit is contained in:
Oliver Gugger 2021-09-27 13:40:07 +02:00
parent 4a0c04f989
commit c5b2b0b979
No known key found for this signature in database
GPG key ID: 8E4256593F177720
3 changed files with 216 additions and 9 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
} }
@ -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
}

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

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