From 0994bcf4b2ef5f56a2493c15281c156c26dabf6c Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 18 Jul 2014 18:54:09 -0500 Subject: [PATCH] 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 --- params.go | 61 +++++++++++++++++++++++++++--- register_test.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 5 deletions(-) diff --git a/params.go b/params.go index b3cec58ef..8b72cd01a 100644 --- a/params.go +++ b/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 diff --git a/register_test.go b/register_test.go index f54ff3cca..b3e57a9a3 100644 --- a/register_test.go +++ b/register_test.go @@ -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[:]) + } + } } }