hdkeychain: BIP32 maximum depth is 255.

BIP32 keys serialize the depth as a uint8 over the wire. I noticed
uint16 was being used and that the depth was being taken modulo 256
during serialization.

This seems like a bug, as the behaviour is not described in the BIP,
and also introduces incompatibilities which can be hard to make sense
of. For example, the parent fingerprint should be 0x00000000 for a key
of depth zero, whereas with the existing code if depth=256, then the
serialization will set 0 but still set a parent fingerprint.
This commit is contained in:
Thomas Kerin 2017-05-09 18:40:57 +02:00 committed by Dave Collins
parent dcd4997b06
commit 4f8b4dbcb2
2 changed files with 49 additions and 5 deletions

View file

@ -49,6 +49,9 @@ const (
// fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes // fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes
// public/private key data. // public/private key data.
serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
// maxUint8 is the max positive integer which can be serialized in a uint8
maxUint8 = 1<<8 - 1
) )
var ( var (
@ -57,6 +60,11 @@ var (
ErrDeriveHardFromPublic = errors.New("cannot derive a hardened key " + ErrDeriveHardFromPublic = errors.New("cannot derive a hardened key " +
"from a public key") "from a public key")
// ErrDeriveBeyondMaxDepth describes an error in which the caller
// has attempted to derive more than 255 keys from a root key.
ErrDeriveBeyondMaxDepth = errors.New("cannot derive a key with more than " +
"255 indices in its path")
// ErrNotPrivExtKey describes an error in which the caller attempted // ErrNotPrivExtKey describes an error in which the caller attempted
// to extract a private key from a public extended key. // to extract a private key from a public extended key.
ErrNotPrivExtKey = errors.New("unable to create private keys from a " + ErrNotPrivExtKey = errors.New("unable to create private keys from a " +
@ -101,7 +109,7 @@ type ExtendedKey struct {
key []byte // This will be the pubkey for extended pub keys key []byte // This will be the pubkey for extended pub keys
pubKey []byte // This will only be set for extended priv keys pubKey []byte // This will only be set for extended priv keys
chainCode []byte chainCode []byte
depth uint16 depth uint8
parentFP []byte parentFP []byte
childNum uint32 childNum uint32
version []byte version []byte
@ -111,7 +119,7 @@ type ExtendedKey struct {
// newExtendedKey returns a new instance of an extended key with the given // newExtendedKey returns a new instance of an extended key with the given
// fields. No error checking is performed here as it's only intended to be a // fields. No error checking is performed here as it's only intended to be a
// convenience method used to create a populated struct. // convenience method used to create a populated struct.
func newExtendedKey(version, key, chainCode, parentFP []byte, depth uint16, func newExtendedKey(version, key, chainCode, parentFP []byte, depth uint8,
childNum uint32, isPrivate bool) *ExtendedKey { childNum uint32, isPrivate bool) *ExtendedKey {
// NOTE: The pubKey field is intentionally left nil so it is only // NOTE: The pubKey field is intentionally left nil so it is only
@ -195,6 +203,10 @@ func (k *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
// 3) Public extended key -> Non-hardened child public extended key // 3) Public extended key -> Non-hardened child public extended key
// 4) Public extended key -> Hardened child public extended key (INVALID!) // 4) Public extended key -> Hardened child public extended key (INVALID!)
if k.depth == maxUint8 {
return nil, ErrDeriveBeyondMaxDepth
}
// Case #4 is invalid, so error out early. // Case #4 is invalid, so error out early.
// A hardened child extended key may not be created from a public // A hardened child extended key may not be created from a public
// extended key. // extended key.
@ -376,7 +388,6 @@ func (k *ExtendedKey) String() string {
} }
var childNumBytes [4]byte var childNumBytes [4]byte
depthByte := byte(k.depth % 256)
binary.BigEndian.PutUint32(childNumBytes[:], k.childNum) binary.BigEndian.PutUint32(childNumBytes[:], k.childNum)
// The serialized format is: // The serialized format is:
@ -384,7 +395,7 @@ func (k *ExtendedKey) String() string {
// child num (4) || chain code (32) || key data (33) || checksum (4) // child num (4) || chain code (32) || key data (33) || checksum (4)
serializedBytes := make([]byte, 0, serializedKeyLen+4) serializedBytes := make([]byte, 0, serializedKeyLen+4)
serializedBytes = append(serializedBytes, k.version...) serializedBytes = append(serializedBytes, k.version...)
serializedBytes = append(serializedBytes, depthByte) serializedBytes = append(serializedBytes, k.depth)
serializedBytes = append(serializedBytes, k.parentFP...) serializedBytes = append(serializedBytes, k.parentFP...)
serializedBytes = append(serializedBytes, childNumBytes[:]...) serializedBytes = append(serializedBytes, childNumBytes[:]...)
serializedBytes = append(serializedBytes, k.chainCode...) serializedBytes = append(serializedBytes, k.chainCode...)
@ -503,7 +514,7 @@ func NewKeyFromString(key string) (*ExtendedKey, error) {
// Deserialize each of the payload fields. // Deserialize each of the payload fields.
version := payload[:4] version := payload[:4]
depth := uint16(payload[4:5][0]) depth := payload[4:5][0]
parentFP := payload[5:9] parentFP := payload[5:9]
childNum := binary.BigEndian.Uint32(payload[9:13]) childNum := binary.BigEndian.Uint32(payload[9:13])
chainCode := payload[13:45] chainCode := payload[13:45]

View file

@ -12,6 +12,7 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"errors" "errors"
"math"
"reflect" "reflect"
"testing" "testing"
@ -1013,3 +1014,35 @@ func TestZero(t *testing.T) {
} }
} }
} }
// The serialization of a BIP32 key uses uint8 to encode the depth. This implicitly
// bounds the depth of the tree to 255 derivations. Here we test that an error is
// returned after 'max uint8'.
func TestMaximumDepth(t *testing.T) {
extKey, err := hdkeychain.NewMaster([]byte(`abcd1234abcd1234abcd1234abcd1234`), &chaincfg.MainNetParams)
if err != nil {
t.Error("MaxDepthTest: Failed to produce test fixture key from string")
return
}
for i := uint8(0); i < math.MaxUint8; i++ {
newKey, err := extKey.Child(1)
if err != nil {
t.Error("MaxDepthTest: Failed to produce key required for test")
return
}
extKey = newKey
}
noKey, err := extKey.Child(1)
if noKey != nil {
t.Error("MaxDepthTest: Deriving 256th key should not succeed")
return
}
if err != hdkeychain.ErrDeriveBeyondMaxDepth {
t.Error("MaxDepthTest: Received unexpected error during test")
}
}