hdkeychain: Correct extended privkey serialization.
This corrects an issue with the serialization of extended private keys where certain underlying derivations could cause lead to printing extended privkeys that did not have the expected xprv prefix. In addition, tests for private key derivation have been added as well as a specific test which triggers the previously failing case.
This commit is contained in:
parent
ff82dacded
commit
2d190f72ff
2 changed files with 139 additions and 1 deletions
|
@ -359,6 +359,16 @@ func (k *ExtendedKey) Address(net *chaincfg.Params) (*btcutil.AddressPubKeyHash,
|
||||||
return btcutil.NewAddressPubKeyHash(pkHash, net)
|
return btcutil.NewAddressPubKeyHash(pkHash, net)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// paddedAppend appends the src byte slice to dst, returning the new slice.
|
||||||
|
// If the length of the source is smaller than the passed size, leading zero
|
||||||
|
// bytes are appended to the dst slice before appending src.
|
||||||
|
func paddedAppend(size uint, dst, src []byte) []byte {
|
||||||
|
for i := 0; i < int(size)-len(src); i++ {
|
||||||
|
dst = append(dst, 0)
|
||||||
|
}
|
||||||
|
return append(dst, src...)
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the extended key as a human-readable base58-encoded string.
|
// String returns the extended key as a human-readable base58-encoded string.
|
||||||
func (k *ExtendedKey) String() string {
|
func (k *ExtendedKey) String() string {
|
||||||
if len(k.key) == 0 {
|
if len(k.key) == 0 {
|
||||||
|
@ -380,7 +390,7 @@ func (k *ExtendedKey) String() string {
|
||||||
serializedBytes = append(serializedBytes, k.chainCode...)
|
serializedBytes = append(serializedBytes, k.chainCode...)
|
||||||
if k.isPrivate {
|
if k.isPrivate {
|
||||||
serializedBytes = append(serializedBytes, 0x00)
|
serializedBytes = append(serializedBytes, 0x00)
|
||||||
serializedBytes = append(serializedBytes, k.key...)
|
serializedBytes = paddedAppend(32, serializedBytes, k.key)
|
||||||
} else {
|
} else {
|
||||||
serializedBytes = append(serializedBytes, k.pubKeyBytes()...)
|
serializedBytes = append(serializedBytes, k.pubKeyBytes()...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -245,6 +245,134 @@ tests:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPrivateDerivation tests several vectors which derive private keys from
|
||||||
|
// other private keys works as intended.
|
||||||
|
func TestPrivateDerivation(t *testing.T) {
|
||||||
|
// The private extended keys for test vectors in [BIP32].
|
||||||
|
testVec1MasterPrivKey := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
|
||||||
|
testVec2MasterPrivKey := "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
master string
|
||||||
|
path []uint32
|
||||||
|
wantPriv string
|
||||||
|
}{
|
||||||
|
// Test vector 1
|
||||||
|
{
|
||||||
|
name: "test vector 1 chain m",
|
||||||
|
master: testVec1MasterPrivKey,
|
||||||
|
path: []uint32{},
|
||||||
|
wantPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 1 chain m/0",
|
||||||
|
master: testVec1MasterPrivKey,
|
||||||
|
path: []uint32{0},
|
||||||
|
wantPriv: "xprv9uHRZZhbkedL37eZEnyrNsQPFZYRAvjy5rt6M1nbEkLSo378x1CQQLo2xxBvREwiK6kqf7GRNvsNEchwibzXaV6i5GcsgyjBeRguXhKsi4R",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 1 chain m/0/1",
|
||||||
|
master: testVec1MasterPrivKey,
|
||||||
|
path: []uint32{0, 1},
|
||||||
|
wantPriv: "xprv9ww7sMFLzJMzy7bV1qs7nGBxgKYrgcm3HcJvGb4yvNhT9vxXC7eX7WVULzCfxucFEn2TsVvJw25hH9d4mchywguGQCZvRgsiRaTY1HCqN8G",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 1 chain m/0/1/2",
|
||||||
|
master: testVec1MasterPrivKey,
|
||||||
|
path: []uint32{0, 1, 2},
|
||||||
|
wantPriv: "xprv9xrdP7iD2L1YZCgR9AecDgpDMZSTzP5KCfUykGXgjBxLgp1VFHsEeL3conzGAkbc1MigG1o8YqmfEA2jtkPdf4vwMaGJC2YSDbBTPAjfRUi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 1 chain m/0/1/2/2",
|
||||||
|
master: testVec1MasterPrivKey,
|
||||||
|
path: []uint32{0, 1, 2, 2},
|
||||||
|
wantPriv: "xprvA2J8Hq4eiP7xCEBP7gzRJGJnd9CHTkEU6eTNMrZ6YR7H5boik8daFtDZxmJDfdMSKHwroCfAfsBKWWidRfBQjpegy6kzXSkQGGoMdWKz5Xh",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 1 chain m/0/1/2/2/1000000000",
|
||||||
|
master: testVec1MasterPrivKey,
|
||||||
|
path: []uint32{0, 1, 2, 2, 1000000000},
|
||||||
|
wantPriv: "xprvA3XhazxncJqJsQcG85Gg61qwPQKiobAnWjuPpjKhExprZjfse6nErRwTMwGe6uGWXPSykZSTiYb2TXAm7Qhwj8KgRd2XaD21Styu6h6AwFz",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Test vector 2
|
||||||
|
{
|
||||||
|
name: "test vector 2 chain m",
|
||||||
|
master: testVec2MasterPrivKey,
|
||||||
|
path: []uint32{},
|
||||||
|
wantPriv: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 2 chain m/0",
|
||||||
|
master: testVec2MasterPrivKey,
|
||||||
|
path: []uint32{0},
|
||||||
|
wantPriv: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 2 chain m/0/2147483647",
|
||||||
|
master: testVec2MasterPrivKey,
|
||||||
|
path: []uint32{0, 2147483647},
|
||||||
|
wantPriv: "xprv9wSp6B7cXJWXZRpDbxkFg3ry2fuSyUfvboJ5Yi6YNw7i1bXmq9QwQ7EwMpeG4cK2pnMqEx1cLYD7cSGSCtruGSXC6ZSVDHugMsZgbuY62m6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 2 chain m/0/2147483647/1",
|
||||||
|
master: testVec2MasterPrivKey,
|
||||||
|
path: []uint32{0, 2147483647, 1},
|
||||||
|
wantPriv: "xprv9ysS5br6UbWCRCJcggvpUNMyhVWgD7NypY9gsVTMYmuRtZg8izyYC5Ey4T931WgWbfJwRDwfVFqV3b29gqHDbuEpGcbzf16pdomk54NXkSm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 2 chain m/0/2147483647/1/2147483646",
|
||||||
|
master: testVec2MasterPrivKey,
|
||||||
|
path: []uint32{0, 2147483647, 1, 2147483646},
|
||||||
|
wantPriv: "xprvA2LfeWWwRCxh4iqigcDMnUf2E3nVUFkntc93nmUYBtb9rpSPYWa8MY3x9ZHSLZkg4G84UefrDruVK3FhMLSJsGtBx883iddHNuH1LNpRrEp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
|
||||||
|
master: testVec2MasterPrivKey,
|
||||||
|
path: []uint32{0, 2147483647, 1, 2147483646, 2},
|
||||||
|
wantPriv: "xprvA48ALo8BDjcRET68R5RsPzF3H7WeyYYtHcyUeLRGBPHXu6CJSGjwW7dWoeUWTEzT7LG3qk6Eg6x2ZoqD8gtyEFZecpAyvchksfLyg3Zbqam",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Custom tests to trigger specific conditions.
|
||||||
|
{
|
||||||
|
// Seed 000000000000000000000000000000da.
|
||||||
|
name: "Derived privkey with zero high byte m/0",
|
||||||
|
master: "xprv9s21ZrQH143K4FR6rNeqEK4EBhRgLjWLWhA3pw8iqgAKk82ypz58PXbrzU19opYcxw8JDJQF4id55PwTsN1Zv8Xt6SKvbr2KNU5y8jN8djz",
|
||||||
|
path: []uint32{0},
|
||||||
|
wantPriv: "xprv9uC5JqtViMmgcAMUxcsBCBFA7oYCNs4bozPbyvLfddjHou4rMiGEHipz94xNaPb1e4f18TRoPXfiXx4C3cDAcADqxCSRSSWLvMBRWPctSN9",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests:
|
||||||
|
for i, test := range tests {
|
||||||
|
extKey, err := hdkeychain.NewKeyFromString(test.master)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewKeyFromString #%d (%s): unexpected error "+
|
||||||
|
"creating extended key: %v", i, test.name,
|
||||||
|
err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, childNum := range test.path {
|
||||||
|
var err error
|
||||||
|
extKey, err = extKey.Child(childNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %v", err)
|
||||||
|
continue tests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
privStr := extKey.String()
|
||||||
|
if privStr != test.wantPriv {
|
||||||
|
t.Errorf("Child #%d (%s): mismatched serialized "+
|
||||||
|
"private extended key -- got: %s, want: %s", i,
|
||||||
|
test.name, privStr, test.wantPriv)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestPublicDerivation tests several vectors which derive public keys from
|
// TestPublicDerivation tests several vectors which derive public keys from
|
||||||
// other public keys works as intended.
|
// other public keys works as intended.
|
||||||
func TestPublicDerivation(t *testing.T) {
|
func TestPublicDerivation(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue