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:
Dave Collins 2014-07-18 18:54:09 -05:00
parent d7e4789eda
commit 0994bcf4b2
2 changed files with 154 additions and 5 deletions

View file

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

View file

@ -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[:])
}
}
}
}