Add priv and public key bytes for HD wallet keys.
This commit introduces an HDPrivateKeyID and HDPublicKeyID field to the params struct which are used by hierarchical deterministic extended keys as defined by BIP0032. In addition, a new function named HDPrivateKeyToPublicKeyID has been added to allow the caller to get the associated public key ID given a private key ID. The tests have also been updated to maintain 100% test coverage. ok @jrick
This commit is contained in:
parent
d7e4789eda
commit
0994bcf4b2
2 changed files with 154 additions and 5 deletions
61
params.go
61
params.go
|
@ -80,10 +80,14 @@ type Params struct {
|
|||
// Mempool parameters
|
||||
RelayNonStdTxs bool
|
||||
|
||||
// Encoding magics
|
||||
// Address encoding magics
|
||||
PubKeyHashAddrID byte // First byte of a P2PKH address
|
||||
ScriptHashAddrID byte // First byte of a P2SH address
|
||||
PrivateKeyID byte // First byte of a WIF private key
|
||||
|
||||
// BIP32 hierarchical deterministic extended key magics
|
||||
HDPrivateKeyID [4]byte
|
||||
HDPublicKeyID [4]byte
|
||||
}
|
||||
|
||||
// MainNetParams defines the network parameters for the main Bitcoin network.
|
||||
|
@ -134,10 +138,14 @@ var MainNetParams = Params{
|
|||
// Mempool parameters
|
||||
RelayNonStdTxs: false,
|
||||
|
||||
// Encoding magics
|
||||
// Address encoding magics
|
||||
PubKeyHashAddrID: 0x00, // starts with 1
|
||||
ScriptHashAddrID: 0x05, // starts with 3
|
||||
PrivateKeyID: 0x80, // starts with 5 (uncompressed) or K (compressed)
|
||||
|
||||
// BIP32 hierarchical deterministic extended key magics
|
||||
HDPrivateKeyID: [4]byte{0x04, 0x88, 0xad, 0xe4}, // starts with xprv
|
||||
HDPublicKeyID: [4]byte{0x04, 0x88, 0xb2, 0x1e}, // starts with xpub
|
||||
}
|
||||
|
||||
// RegressionNetParams defines the network parameters for the regression test
|
||||
|
@ -175,10 +183,14 @@ var RegressionNetParams = Params{
|
|||
// Mempool parameters
|
||||
RelayNonStdTxs: true,
|
||||
|
||||
// Encoding magics
|
||||
// Address encoding magics
|
||||
PubKeyHashAddrID: 0x6f, // starts with m or n
|
||||
ScriptHashAddrID: 0xc4, // starts with 2
|
||||
PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed)
|
||||
|
||||
// BIP32 hierarchical deterministic extended key magics
|
||||
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
|
||||
HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xcf}, // starts with tpub
|
||||
}
|
||||
|
||||
// TestNet3Params defines the network parameters for the test Bitcoin network
|
||||
|
@ -218,10 +230,14 @@ var TestNet3Params = Params{
|
|||
// Mempool parameters
|
||||
RelayNonStdTxs: true,
|
||||
|
||||
// Encoding magics
|
||||
// Address encoding magics
|
||||
PubKeyHashAddrID: 0x6f, // starts with m or n
|
||||
ScriptHashAddrID: 0xc4, // starts with 2
|
||||
PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed)
|
||||
|
||||
// BIP32 hierarchical deterministic extended key magics
|
||||
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
|
||||
HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xcf}, // starts with tpub
|
||||
}
|
||||
|
||||
// SimNetParams defines the network parameters for the simulation test Bitcoin
|
||||
|
@ -261,10 +277,14 @@ var SimNetParams = Params{
|
|||
// Mempool parameters
|
||||
RelayNonStdTxs: true,
|
||||
|
||||
// Encoding magics
|
||||
// Address encoding magics
|
||||
PubKeyHashAddrID: 0x3f, // starts with S
|
||||
ScriptHashAddrID: 0x7b, // starts with s
|
||||
PrivateKeyID: 0x64, // starts with 4 (uncompressed) or F (compressed)
|
||||
|
||||
// BIP32 hierarchical deterministic extended key magics
|
||||
HDPrivateKeyID: [4]byte{0x04, 0x20, 0xb9, 0x00}, // starts with sprv
|
||||
HDPublicKeyID: [4]byte{0x04, 0x20, 0xbd, 0x3a}, // starts with spub
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -272,6 +292,11 @@ var (
|
|||
// network could not be set due to the network already being a standard
|
||||
// network or previously-registered into this package.
|
||||
ErrDuplicateNet = errors.New("duplicate Bitcoin network")
|
||||
|
||||
// ErrUnknownHDKeyID describes an error where the provided id which
|
||||
// is intended to identify the network for a hierarchical deterministic
|
||||
// private extended key is not registered.
|
||||
ErrUnknownHDKeyID = errors.New("unknown hd private extended key bytes")
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -293,6 +318,13 @@ var (
|
|||
TestNet3Params.ScriptHashAddrID: struct{}{}, // shared with regtest
|
||||
SimNetParams.ScriptHashAddrID: struct{}{},
|
||||
}
|
||||
|
||||
// Testnet is shared with regtest.
|
||||
hdPrivToPubKeyIDs = map[[4]byte][]byte{
|
||||
MainNetParams.HDPrivateKeyID: MainNetParams.HDPublicKeyID[:],
|
||||
TestNet3Params.HDPrivateKeyID: TestNet3Params.HDPublicKeyID[:],
|
||||
SimNetParams.HDPrivateKeyID: SimNetParams.HDPublicKeyID[:],
|
||||
}
|
||||
)
|
||||
|
||||
// Register registers the network parameters for a Bitcoin network. This may
|
||||
|
@ -311,6 +343,7 @@ func Register(params *Params) error {
|
|||
registeredNets[params.Net] = struct{}{}
|
||||
pubKeyHashAddrIDs[params.PubKeyHashAddrID] = struct{}{}
|
||||
scriptHashAddrIDs[params.ScriptHashAddrID] = struct{}{}
|
||||
hdPrivToPubKeyIDs[params.HDPrivateKeyID] = params.HDPublicKeyID[:]
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -336,6 +369,24 @@ func IsScriptHashAddrID(id byte) bool {
|
|||
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.
|
||||
func HDPrivateKeyToPublicKeyID(id []byte) ([]byte, error) {
|
||||
if len(id) != 4 {
|
||||
return nil, ErrUnknownHDKeyID
|
||||
}
|
||||
|
||||
var key [4]byte
|
||||
copy(key[:], id)
|
||||
pubBytes, ok := hdPrivToPubKeyIDs[key]
|
||||
if !ok {
|
||||
return nil, ErrUnknownHDKeyID
|
||||
}
|
||||
|
||||
return pubBytes, nil
|
||||
}
|
||||
|
||||
// newShaHashFromStr converts the passed big-endian hex string into a
|
||||
// btcwire.ShaHash. It only differs from the one available in btcwire in that
|
||||
// it panics on an error since it will only (and must only) be called with
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package btcnet_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "github.com/conformal/btcnet"
|
||||
|
@ -14,6 +16,8 @@ var mockNetParams = Params{
|
|||
Net: 1<<32 - 1,
|
||||
PubKeyHashAddrID: 0x9f,
|
||||
ScriptHashAddrID: 0xf9,
|
||||
HDPrivateKeyID: [4]byte{0x01, 0x02, 0x03, 0x04},
|
||||
HDPublicKeyID: [4]byte{0x05, 0x06, 0x07, 0x08},
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
|
@ -26,12 +30,18 @@ func TestRegister(t *testing.T) {
|
|||
magic byte
|
||||
valid bool
|
||||
}
|
||||
type hdTest struct {
|
||||
priv []byte
|
||||
want []byte
|
||||
err error
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
register []registerTest
|
||||
p2pkhMagics []magicTest
|
||||
p2shMagics []magicTest
|
||||
hdMagics []hdTest
|
||||
}{
|
||||
{
|
||||
name: "default networks",
|
||||
|
@ -109,6 +119,40 @@ func TestRegister(t *testing.T) {
|
|||
valid: false,
|
||||
},
|
||||
},
|
||||
hdMagics: []hdTest{
|
||||
{
|
||||
priv: MainNetParams.HDPrivateKeyID[:],
|
||||
want: MainNetParams.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: TestNet3Params.HDPrivateKeyID[:],
|
||||
want: TestNet3Params.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: RegressionNetParams.HDPrivateKeyID[:],
|
||||
want: RegressionNetParams.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: SimNetParams.HDPrivateKeyID[:],
|
||||
want: SimNetParams.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: mockNetParams.HDPrivateKeyID[:],
|
||||
err: ErrUnknownHDKeyID,
|
||||
},
|
||||
{
|
||||
priv: []byte{0xff, 0xff, 0xff, 0xff},
|
||||
err: ErrUnknownHDKeyID,
|
||||
},
|
||||
{
|
||||
priv: []byte{0xff},
|
||||
err: ErrUnknownHDKeyID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "register mocknet",
|
||||
|
@ -171,6 +215,13 @@ func TestRegister(t *testing.T) {
|
|||
valid: false,
|
||||
},
|
||||
},
|
||||
hdMagics: []hdTest{
|
||||
{
|
||||
priv: mockNetParams.HDPrivateKeyID[:],
|
||||
want: mockNetParams.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "more duplicates",
|
||||
|
@ -253,6 +304,41 @@ func TestRegister(t *testing.T) {
|
|||
valid: false,
|
||||
},
|
||||
},
|
||||
hdMagics: []hdTest{
|
||||
{
|
||||
priv: MainNetParams.HDPrivateKeyID[:],
|
||||
want: MainNetParams.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: TestNet3Params.HDPrivateKeyID[:],
|
||||
want: TestNet3Params.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: RegressionNetParams.HDPrivateKeyID[:],
|
||||
want: RegressionNetParams.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: SimNetParams.HDPrivateKeyID[:],
|
||||
want: SimNetParams.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: mockNetParams.HDPrivateKeyID[:],
|
||||
want: mockNetParams.HDPublicKeyID[:],
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
priv: []byte{0xff, 0xff, 0xff, 0xff},
|
||||
err: ErrUnknownHDKeyID,
|
||||
},
|
||||
{
|
||||
priv: []byte{0xff},
|
||||
err: ErrUnknownHDKeyID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -278,5 +364,17 @@ func TestRegister(t *testing.T) {
|
|||
test.name, i, valid, magTest.valid)
|
||||
}
|
||||
}
|
||||
for i, magTest := range test.hdMagics {
|
||||
pubKey, err := HDPrivateKeyToPublicKeyID(magTest.priv[:])
|
||||
if !reflect.DeepEqual(err, magTest.err) {
|
||||
t.Errorf("%s: HD magic %d mismatched error: got %v expected %v ",
|
||||
test.name, i, err, magTest.err)
|
||||
continue
|
||||
}
|
||||
if magTest.err == nil && !bytes.Equal(pubKey, magTest.want[:]) {
|
||||
t.Errorf("%s: HD magic %d private and public mismatch: got %v expected %v ",
|
||||
test.name, i, pubKey, magTest.want[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue