diff --git a/hdkeychain/extendedkey.go b/hdkeychain/extendedkey.go index fb09d7c..d937113 100644 --- a/hdkeychain/extendedkey.go +++ b/hdkeychain/extendedkey.go @@ -359,6 +359,16 @@ func (k *ExtendedKey) Address(net *chaincfg.Params) (*btcutil.AddressPubKeyHash, 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. func (k *ExtendedKey) String() string { if len(k.key) == 0 { @@ -380,7 +390,7 @@ func (k *ExtendedKey) String() string { serializedBytes = append(serializedBytes, k.chainCode...) if k.isPrivate { serializedBytes = append(serializedBytes, 0x00) - serializedBytes = append(serializedBytes, k.key...) + serializedBytes = paddedAppend(32, serializedBytes, k.key) } else { serializedBytes = append(serializedBytes, k.pubKeyBytes()...) } diff --git a/hdkeychain/extendedkey_test.go b/hdkeychain/extendedkey_test.go index 95ce51a..37e90e1 100644 --- a/hdkeychain/extendedkey_test.go +++ b/hdkeychain/extendedkey_test.go @@ -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 // other public keys works as intended. func TestPublicDerivation(t *testing.T) {