diff --git a/chaincfg/params.go b/chaincfg/params.go index 0090ab1a1..8c33ea893 100644 --- a/chaincfg/params.go +++ b/chaincfg/params.go @@ -8,6 +8,7 @@ import ( "errors" "math" "math/big" + "strings" "time" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -195,8 +196,8 @@ type Params struct { // Mempool parameters RelayNonStdTxs bool - // Human-readable part for Bech32 encoded segwit addresses, as defined in - // BIP 173. + // Human-readable part for Bech32 encoded segwit addresses, as defined + // in BIP 173. Bech32HRPSegwit string // Address encoding magics @@ -547,10 +548,11 @@ var ( ) var ( - registeredNets = make(map[wire.BitcoinNet]struct{}) - pubKeyHashAddrIDs = make(map[byte]struct{}) - scriptHashAddrIDs = make(map[byte]struct{}) - hdPrivToPubKeyIDs = make(map[[4]byte][]byte) + registeredNets = make(map[wire.BitcoinNet]struct{}) + pubKeyHashAddrIDs = make(map[byte]struct{}) + scriptHashAddrIDs = make(map[byte]struct{}) + bech32SegwitPrefixes = make(map[string]struct{}) + hdPrivToPubKeyIDs = make(map[[4]byte][]byte) ) // String returns the hostname of the DNS seed in human-readable form. @@ -575,6 +577,10 @@ func Register(params *Params) error { pubKeyHashAddrIDs[params.PubKeyHashAddrID] = struct{}{} scriptHashAddrIDs[params.ScriptHashAddrID] = struct{}{} hdPrivToPubKeyIDs[params.HDPrivateKeyID] = params.HDPublicKeyID[:] + + // A valid Bech32 encoded segwit address always has as prefix the + // human-readable part for the given net followed by '1'. + bech32SegwitPrefixes[params.Bech32HRPSegwit+"1"] = struct{}{} return nil } @@ -608,6 +614,15 @@ func IsScriptHashAddrID(id byte) bool { return ok } +// IsBech32SegwitPrefix returns whether the prefix is a known prefix for segwit +// addresses on any default or registered network. This is used when decoding +// an address string into a specific address type. +func IsBech32SegwitPrefix(prefix string) bool { + prefix = strings.ToLower(prefix) + _, ok := bech32SegwitPrefixes[prefix] + return ok +} + // HDPrivateKeyToPublicKeyID accepts a private hierarchical deterministic // extended key id and returns the associated public key id. When the provided // id is not registered, the ErrUnknownHDKeyID error will be returned. diff --git a/chaincfg/register_test.go b/chaincfg/register_test.go index b5ac392bc..bcb5b3c6f 100644 --- a/chaincfg/register_test.go +++ b/chaincfg/register_test.go @@ -3,6 +3,7 @@ package chaincfg_test import ( "bytes" "reflect" + "strings" "testing" . "github.com/btcsuite/btcd/chaincfg" @@ -16,6 +17,7 @@ var mockNetParams = Params{ Net: 1<<32 - 1, PubKeyHashAddrID: 0x9f, ScriptHashAddrID: 0xf9, + Bech32HRPSegwit: "tc", HDPrivateKeyID: [4]byte{0x01, 0x02, 0x03, 0x04}, HDPublicKeyID: [4]byte{0x05, 0x06, 0x07, 0x08}, } @@ -30,6 +32,10 @@ func TestRegister(t *testing.T) { magic byte valid bool } + type prefixTest struct { + prefix string + valid bool + } type hdTest struct { priv []byte want []byte @@ -37,11 +43,12 @@ func TestRegister(t *testing.T) { } tests := []struct { - name string - register []registerTest - p2pkhMagics []magicTest - p2shMagics []magicTest - hdMagics []hdTest + name string + register []registerTest + p2pkhMagics []magicTest + p2shMagics []magicTest + segwitPrefixes []prefixTest + hdMagics []hdTest }{ { name: "default networks", @@ -119,6 +126,44 @@ func TestRegister(t *testing.T) { valid: false, }, }, + segwitPrefixes: []prefixTest{ + { + prefix: MainNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: TestNet3Params.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: RegressionNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: SimNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: strings.ToUpper(MainNetParams.Bech32HRPSegwit + "1"), + valid: true, + }, + { + prefix: mockNetParams.Bech32HRPSegwit + "1", + valid: false, + }, + { + prefix: "abc1", + valid: false, + }, + { + prefix: "1", + valid: false, + }, + { + prefix: MainNetParams.Bech32HRPSegwit, + valid: false, + }, + }, hdMagics: []hdTest{ { priv: MainNetParams.HDPrivateKeyID[:], @@ -215,6 +260,44 @@ func TestRegister(t *testing.T) { valid: false, }, }, + segwitPrefixes: []prefixTest{ + { + prefix: MainNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: TestNet3Params.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: RegressionNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: SimNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: strings.ToUpper(MainNetParams.Bech32HRPSegwit + "1"), + valid: true, + }, + { + prefix: mockNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: "abc1", + valid: false, + }, + { + prefix: "1", + valid: false, + }, + { + prefix: MainNetParams.Bech32HRPSegwit, + valid: false, + }, + }, hdMagics: []hdTest{ { priv: mockNetParams.HDPrivateKeyID[:], @@ -304,6 +387,44 @@ func TestRegister(t *testing.T) { valid: false, }, }, + segwitPrefixes: []prefixTest{ + { + prefix: MainNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: TestNet3Params.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: RegressionNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: SimNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: strings.ToUpper(MainNetParams.Bech32HRPSegwit + "1"), + valid: true, + }, + { + prefix: mockNetParams.Bech32HRPSegwit + "1", + valid: true, + }, + { + prefix: "abc1", + valid: false, + }, + { + prefix: "1", + valid: false, + }, + { + prefix: MainNetParams.Bech32HRPSegwit, + valid: false, + }, + }, hdMagics: []hdTest{ { priv: MainNetParams.HDPrivateKeyID[:], @@ -364,6 +485,13 @@ func TestRegister(t *testing.T) { test.name, i, valid, magTest.valid) } } + for i, prxTest := range test.segwitPrefixes { + valid := IsBech32SegwitPrefix(prxTest.prefix) + if valid != prxTest.valid { + t.Errorf("%s: segwit prefix %s (%d) valid mismatch: got %v expected %v", + test.name, prxTest.prefix, i, valid, prxTest.valid) + } + } for i, magTest := range test.hdMagics { pubKey, err := HDPrivateKeyToPublicKeyID(magTest.priv[:]) if !reflect.DeepEqual(err, magTest.err) {