// Copyright (c) 2014-2017 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package hdkeychain // References: // [BIP32]: BIP0032 - Hierarchical Deterministic Wallets // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki import ( "bytes" "encoding/binary" "encoding/hex" "errors" "math" "reflect" "testing" "github.com/lbryio/lbcd/chaincfg" ) // TestBIP0032Vectors tests the vectors provided by [BIP32] to ensure the // derivation works as intended. func TestBIP0032Vectors(t *testing.T) { // The master seeds for each of the two test vectors in [BIP32]. testVec1MasterHex := "000102030405060708090a0b0c0d0e0f" testVec2MasterHex := "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" testVec3MasterHex := "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be" hkStart := uint32(0x80000000) tests := []struct { name string master string path []uint32 wantPub string wantPriv string net *chaincfg.Params }{ // Test vector 1 { name: "test vector 1 chain m", master: testVec1MasterHex, path: []uint32{}, wantPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", wantPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", net: &chaincfg.MainNetParams, }, { name: "test vector 1 chain m/0H", master: testVec1MasterHex, path: []uint32{hkStart}, wantPub: "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", wantPriv: "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", net: &chaincfg.MainNetParams, }, { name: "test vector 1 chain m/0H/1", master: testVec1MasterHex, path: []uint32{hkStart, 1}, wantPub: "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", wantPriv: "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", net: &chaincfg.MainNetParams, }, { name: "test vector 1 chain m/0H/1/2H", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2}, wantPub: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", wantPriv: "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", net: &chaincfg.MainNetParams, }, { name: "test vector 1 chain m/0H/1/2H/2", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2}, wantPub: "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", wantPriv: "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", net: &chaincfg.MainNetParams, }, { name: "test vector 1 chain m/0H/1/2H/2/1000000000", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2, 1000000000}, wantPub: "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", wantPriv: "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", net: &chaincfg.MainNetParams, }, // Test vector 2 { name: "test vector 2 chain m", master: testVec2MasterHex, path: []uint32{}, wantPub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", wantPriv: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", net: &chaincfg.MainNetParams, }, { name: "test vector 2 chain m/0", master: testVec2MasterHex, path: []uint32{0}, wantPub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", wantPriv: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", net: &chaincfg.MainNetParams, }, { name: "test vector 2 chain m/0/2147483647H", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647}, wantPub: "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", wantPriv: "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", net: &chaincfg.MainNetParams, }, { name: "test vector 2 chain m/0/2147483647H/1", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647, 1}, wantPub: "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", wantPriv: "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", net: &chaincfg.MainNetParams, }, { name: "test vector 2 chain m/0/2147483647H/1/2147483646H", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646}, wantPub: "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", wantPriv: "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", net: &chaincfg.MainNetParams, }, { name: "test vector 2 chain m/0/2147483647H/1/2147483646H/2", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646, 2}, wantPub: "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", wantPriv: "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", net: &chaincfg.MainNetParams, }, // Test vector 3 { name: "test vector 3 chain m", master: testVec3MasterHex, path: []uint32{}, wantPub: "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", wantPriv: "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", net: &chaincfg.MainNetParams, }, { name: "test vector 3 chain m/0H", master: testVec3MasterHex, path: []uint32{hkStart}, wantPub: "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y", wantPriv: "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L", net: &chaincfg.MainNetParams, }, // Test vector 1 - Testnet { name: "test vector 1 chain m - testnet", master: testVec1MasterHex, path: []uint32{}, wantPub: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", wantPriv: "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", net: &chaincfg.TestNet3Params, }, { name: "test vector 1 chain m/0H - testnet", master: testVec1MasterHex, path: []uint32{hkStart}, wantPub: "tpubD8eQVK4Kdxg3gHrF62jGP7dKVCoYiEB8dFSpuTawkL5YxTus5j5pf83vaKnii4bc6v2NVEy81P2gYrJczYne3QNNwMTS53p5uzDyHvnw2jm", wantPriv: "tprv8bxNLu25VazNnppTCP4fyhyCvBHcYtzE3wr3cwYeL4HA7yf6TLGEUdS4QC1vLT63TkjRssqJe4CvGNEC8DzW5AoPUw56D1Ayg6HY4oy8QZ9", net: &chaincfg.TestNet3Params, }, { name: "test vector 1 chain m/0H/1 - testnet", master: testVec1MasterHex, path: []uint32{hkStart, 1}, wantPub: "tpubDApXh6cD2fZ7WjtgpHd8yrWyYaneiFuRZa7fVjMkgxsmC1QzoXW8cgx9zQFJ81Jx4deRGfRE7yXA9A3STsxXj4CKEZJHYgpMYikkas9DBTP", wantPriv: "tprv8e8VYgZxtHsSdGrtvdxYaSrryZGiYviWzGWtDDKTGh5NMXAEB8gYSCLHpFCywNs5uqV7ghRjimALQJkRFZnUrLHpzi2pGkwqLtbubgWuQ8q", net: &chaincfg.TestNet3Params, }, { name: "test vector 1 chain m/0H/1/2H - testnet", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2}, wantPub: "tpubDDRojdS4jYQXNugn4t2WLrZ7mjfAyoVQu7MLk4eurqFCbrc7cHLZX8W5YRS8ZskGR9k9t3PqVv68bVBjAyW4nWM9pTGRddt3GQftg6MVQsm", wantPriv: "tprv8gjmbDPpbAirVSezBEMuwSu1Ci9EpUJWKokZTYccSZSomNMLytWyLdtDNHRbucNaRJWWHANf9AzEdWVAqahfyRjVMKbNRhBmxAM8EJr7R15", net: &chaincfg.TestNet3Params, }, { name: "test vector 1 chain m/0H/1/2H/2 - testnet", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2}, wantPub: "tpubDFfCa4Z1v25WTPAVm9EbEMiRrYwucPocLbEe12BPBGooxxEUg42vihy1DkRWyftztTsL23snYezF9uXjGGwGW6pQjEpcTpmsH6ajpf4CVPn", wantPriv: "tprv8iyAReWmmePqZv8hsVZzpx4KHXRyT4chmHdriW95m11R8Tyi3fDLYDM93bq4NGn1V6eCu5cE3zSQ6hPd31F2ApKXkZgTyn1V78pHjkq1V2v", net: &chaincfg.TestNet3Params, }, { name: "test vector 1 chain m/0H/1/2H/2/1000000000 - testnet", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2, 1000000000}, wantPub: "tpubDHNy3kAG39ThyiwwsgoKY4iRenXDRtce8qdCFJZXPMCJg5dsCUHayp84raLTpvyiNA9sXPob5rgqkKvkN8S7MMyXbnEhGJMW64Cf4vFAoaF", wantPriv: "tprv8kgvuL81tmn36Fv9z38j8f4K5m1HGZRjZY2QxnXDy5PuqbP6a5TzoKWCgTcGHBu66W3TgSbAu2yX6sPza5FkHmy564Sh6gmCPUNeUt4yj2x", net: &chaincfg.TestNet3Params, }, } tests: for i, test := range tests { masterSeed, err := hex.DecodeString(test.master) if err != nil { t.Errorf("DecodeString #%d (%s): unexpected error: %v", i, test.name, err) continue } extKey, err := NewMaster(masterSeed, test.net) if err != nil { t.Errorf("NewMaster #%d (%s): unexpected error when "+ "creating new master key: %v", i, test.name, err) continue } for _, childNum := range test.path { var err error extKey, err = extKey.Derive(childNum) if err != nil { t.Errorf("err: %v", err) continue tests } } if extKey.Depth() != uint8(len(test.path)) { t.Errorf("Depth of key %d should match fixture path: %v", extKey.Depth(), len(test.path)) continue } privStr := extKey.String() if privStr != test.wantPriv { t.Errorf("Serialize #%d (%s): mismatched serialized "+ "private extended key -- got: %s, want: %s", i, test.name, privStr, test.wantPriv) continue } pubKey, err := extKey.Neuter() if err != nil { t.Errorf("Neuter #%d (%s): unexpected error: %v ", i, test.name, err) continue } // Neutering a second time should have no effect. pubKey, err = pubKey.Neuter() if err != nil { t.Errorf("Neuter #%d (%s): unexpected error: %v", i, test.name, err) return } pubStr := pubKey.String() if pubStr != test.wantPub { t.Errorf("Neuter #%d (%s): mismatched serialized "+ "public extended key -- got: %s, want: %s", i, test.name, pubStr, test.wantPub) continue } } } // 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 := 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.Derive(childNum) if err != nil { t.Errorf("err: %v", err) continue tests } } privStr := extKey.String() if privStr != test.wantPriv { t.Errorf("Derive #%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) { // The public extended keys for test vectors in [BIP32]. testVec1MasterPubKey := "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" testVec2MasterPubKey := "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" tests := []struct { name string master string path []uint32 wantPub string }{ // Test vector 1 { name: "test vector 1 chain m", master: testVec1MasterPubKey, path: []uint32{}, wantPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", }, { name: "test vector 1 chain m/0", master: testVec1MasterPubKey, path: []uint32{0}, wantPub: "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1", }, { name: "test vector 1 chain m/0/1", master: testVec1MasterPubKey, path: []uint32{0, 1}, wantPub: "xpub6AvUGrnEpfvJBbfx7sQ89Q8hEMPM65UteqEX4yUbUiES2jHfjexmfJoxCGSwFMZiPBaKQT1RiKWrKfuDV4vpgVs4Xn8PpPTR2i79rwHd4Zr", }, { name: "test vector 1 chain m/0/1/2", master: testVec1MasterPubKey, path: []uint32{0, 1, 2}, wantPub: "xpub6BqyndF6rhZqmgktFCBcapkwubGxPqoAZtQaYewJHXVKZcLdnqBVC8N6f6FSHWUghjuTLeubWyQWfJdk2G3tGgvgj3qngo4vLTnnSjAZckv", }, { name: "test vector 1 chain m/0/1/2/2", master: testVec1MasterPubKey, path: []uint32{0, 1, 2, 2}, wantPub: "xpub6FHUhLbYYkgFQiFrDiXRfQFXBB2msCxKTsNyAExi6keFxQ8sHfwpogY3p3s1ePSpUqLNYks5T6a3JqpCGszt4kxbyq7tUoFP5c8KWyiDtPp", }, { name: "test vector 1 chain m/0/1/2/2/1000000000", master: testVec1MasterPubKey, path: []uint32{0, 1, 2, 2, 1000000000}, wantPub: "xpub6GX3zWVgSgPc5tgjE6ogT9nfwSADD3tdsxpzd7jJoJMqSY12Be6VQEFwDCp6wAQoZsH2iq5nNocHEaVDxBcobPrkZCjYW3QUmoDYzMFBDu9", }, // Test vector 2 { name: "test vector 2 chain m", master: testVec2MasterPubKey, path: []uint32{}, wantPub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", }, { name: "test vector 2 chain m/0", master: testVec2MasterPubKey, path: []uint32{0}, wantPub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", }, { name: "test vector 2 chain m/0/2147483647", master: testVec2MasterPubKey, path: []uint32{0, 2147483647}, wantPub: "xpub6ASAVgeWMg4pmutghzHG3BohahjwNwPmy2DgM6W9wGegtPrvNgjBwuZRD7hSDFhYfunq8vDgwG4ah1gVzZysgp3UsKz7VNjCnSUJJ5T4fdD", }, { name: "test vector 2 chain m/0/2147483647/1", master: testVec2MasterPubKey, path: []uint32{0, 2147483647, 1}, wantPub: "xpub6CrnV7NzJy4VdgP5niTpqWJiFXMAca6qBm5Hfsry77SQmN1HGYHnjsZSujoHzdxf7ZNK5UVrmDXFPiEW2ecwHGWMFGUxPC9ARipss9rXd4b", }, { name: "test vector 2 chain m/0/2147483647/1/2147483646", master: testVec2MasterPubKey, path: []uint32{0, 2147483647, 1, 2147483646}, wantPub: "xpub6FL2423qFaWzHCvBndkN9cbkn5cysiUeFq4eb9t9kE88jcmY63tNuLNRzpHPdAM4dUpLhZ7aUm2cJ5zF7KYonf4jAPfRqTMTRBNkQL3Tfta", }, { name: "test vector 2 chain m/0/2147483647/1/2147483646/2", master: testVec2MasterPubKey, path: []uint32{0, 2147483647, 1, 2147483646, 2}, wantPub: "xpub6H7WkJf547AiSwAbX6xsm8Bmq9M9P1Gjequ5SipsjipWmtXSyp4C3uwzewedGEgAMsDy4jEvNTWtxLyqqHY9C12gaBmgUdk2CGmwachwnWK", }, } tests: for i, test := range tests { extKey, err := 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.Derive(childNum) if err != nil { t.Errorf("err: %v", err) continue tests } } pubStr := extKey.String() if pubStr != test.wantPub { t.Errorf("Derive #%d (%s): mismatched serialized "+ "public extended key -- got: %s, want: %s", i, test.name, pubStr, test.wantPub) continue } } } // TestGenerateSeed ensures the GenerateSeed function works as intended. func TestGenerateSeed(t *testing.T) { wantErr := errors.New("seed length must be between 128 and 512 bits") tests := []struct { name string length uint8 err error }{ // Test various valid lengths. {name: "16 bytes", length: 16}, {name: "17 bytes", length: 17}, {name: "20 bytes", length: 20}, {name: "32 bytes", length: 32}, {name: "64 bytes", length: 64}, // Test invalid lengths. {name: "15 bytes", length: 15, err: wantErr}, {name: "65 bytes", length: 65, err: wantErr}, } for i, test := range tests { seed, err := GenerateSeed(test.length) if !reflect.DeepEqual(err, test.err) { t.Errorf("GenerateSeed #%d (%s): unexpected error -- "+ "want %v, got %v", i, test.name, test.err, err) continue } if test.err == nil && len(seed) != int(test.length) { t.Errorf("GenerateSeed #%d (%s): length mismatch -- "+ "got %d, want %d", i, test.name, len(seed), test.length) continue } } } // TestExtendedKeyAPI ensures the API on the ExtendedKey type works as intended. func TestExtendedKeyAPI(t *testing.T) { tests := []struct { name string extKey string isPrivate bool parentFP uint32 chainCode []byte childNum uint32 privKey string privKeyErr error pubKey string address string }{ { name: "test vector 1 master node private", extKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", isPrivate: true, parentFP: 0, chainCode: []byte{135, 61, 255, 129, 192, 47, 82, 86, 35, 253, 31, 229, 22, 126, 172, 58, 85, 160, 73, 222, 61, 49, 75, 180, 46, 226, 39, 255, 237, 55, 213, 8}, childNum: 0, privKey: "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", pubKey: "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", address: "bHVb1pN9ACQB2QxFoXwVAbM4WSyUVVDfbW", }, { name: "test vector 1 chain m/0H/1/2H public", extKey: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", isPrivate: false, parentFP: 3203769081, chainCode: []byte{4, 70, 107, 156, 200, 225, 97, 233, 102, 64, 156, 165, 41, 134, 197, 132, 240, 126, 157, 200, 31, 115, 93, 182, 131, 195, 255, 110, 199, 177, 80, 63}, childNum: 2147483650, privKeyErr: ErrNotPrivExtKey, pubKey: "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", address: "baUEXpVdvtuGCqyC68p94qC6tn1LSJq1LA", }, } for i, test := range tests { key, err := NewKeyFromString(test.extKey) if err != nil { t.Errorf("NewKeyFromString #%d (%s): unexpected "+ "error: %v", i, test.name, err) continue } if key.IsPrivate() != test.isPrivate { t.Errorf("IsPrivate #%d (%s): mismatched key type -- "+ "want private %v, got private %v", i, test.name, test.isPrivate, key.IsPrivate()) continue } parentFP := key.ParentFingerprint() if parentFP != test.parentFP { t.Errorf("ParentFingerprint #%d (%s): mismatched "+ "parent fingerprint -- want %d, got %d", i, test.name, test.parentFP, parentFP) continue } chainCode := key.ChainCode() if !bytes.Equal(chainCode, test.chainCode) { t.Errorf("ChainCode #%d (%s): want %v, got %v", i, test.name, test.chainCode, chainCode) continue } childIndex := key.ChildIndex() if childIndex != test.childNum { t.Errorf("ChildIndex #%d (%s): want %d, got %d", i, test.name, test.childNum, childIndex) continue } serializedKey := key.String() if serializedKey != test.extKey { t.Errorf("String #%d (%s): mismatched serialized key "+ "-- want %s, got %s", i, test.name, test.extKey, serializedKey) continue } privKey, err := key.ECPrivKey() if !reflect.DeepEqual(err, test.privKeyErr) { t.Errorf("ECPrivKey #%d (%s): mismatched error: want "+ "%v, got %v", i, test.name, test.privKeyErr, err) continue } if test.privKeyErr == nil { privKeyStr := hex.EncodeToString(privKey.Serialize()) if privKeyStr != test.privKey { t.Errorf("ECPrivKey #%d (%s): mismatched "+ "private key -- want %s, got %s", i, test.name, test.privKey, privKeyStr) continue } } pubKey, err := key.ECPubKey() if err != nil { t.Errorf("ECPubKey #%d (%s): unexpected error: %v", i, test.name, err) continue } pubKeyStr := hex.EncodeToString(pubKey.SerializeCompressed()) if pubKeyStr != test.pubKey { t.Errorf("ECPubKey #%d (%s): mismatched public key -- "+ "want %s, got %s", i, test.name, test.pubKey, pubKeyStr) continue } addr, err := key.Address(&chaincfg.MainNetParams) if err != nil { t.Errorf("Address #%d (%s): unexpected error: %v", i, test.name, err) continue } if addr.EncodeAddress() != test.address { t.Errorf("Address #%d (%s): mismatched address -- want "+ "%s, got %s", i, test.name, test.address, addr.EncodeAddress()) continue } } } // TestNet ensures the network related APIs work as intended. func TestNet(t *testing.T) { tests := []struct { name string key string origNet *chaincfg.Params newNet *chaincfg.Params newPriv string newPub string isPrivate bool }{ // Private extended keys. { name: "mainnet -> simnet", key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", origNet: &chaincfg.MainNetParams, newNet: &chaincfg.SimNetParams, newPriv: "sprv8Erh3X3hFeKunvVdAGQQtambRPapECWiTDtvsTGdyrhzhbYgnSZajRRWbihzvq4AM4ivm6uso31VfKaukwJJUs3GYihXP8ebhMb3F2AHu3P", newPub: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", isPrivate: true, }, { name: "simnet -> mainnet", key: "sprv8Erh3X3hFeKunvVdAGQQtambRPapECWiTDtvsTGdyrhzhbYgnSZajRRWbihzvq4AM4ivm6uso31VfKaukwJJUs3GYihXP8ebhMb3F2AHu3P", origNet: &chaincfg.SimNetParams, newNet: &chaincfg.MainNetParams, newPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", isPrivate: true, }, { name: "mainnet -> regtest", key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", origNet: &chaincfg.MainNetParams, newNet: &chaincfg.RegressionNetParams, newPriv: "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", newPub: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", isPrivate: true, }, { name: "regtest -> mainnet", key: "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", origNet: &chaincfg.RegressionNetParams, newNet: &chaincfg.MainNetParams, newPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", isPrivate: true, }, // Public extended keys. { name: "mainnet -> simnet", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", origNet: &chaincfg.MainNetParams, newNet: &chaincfg.SimNetParams, newPub: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", isPrivate: false, }, { name: "simnet -> mainnet", key: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", origNet: &chaincfg.SimNetParams, newNet: &chaincfg.MainNetParams, newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", isPrivate: false, }, { name: "mainnet -> regtest", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", origNet: &chaincfg.MainNetParams, newNet: &chaincfg.RegressionNetParams, newPub: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", isPrivate: false, }, { name: "regtest -> mainnet", key: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", origNet: &chaincfg.RegressionNetParams, newNet: &chaincfg.MainNetParams, newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", isPrivate: false, }, } for i, test := range tests { extKey, err := NewKeyFromString(test.key) if err != nil { t.Errorf("NewKeyFromString #%d (%s): unexpected error "+ "creating extended key: %v", i, test.name, err) continue } if !extKey.IsForNet(test.origNet) { t.Errorf("IsForNet #%d (%s): key is not for expected "+ "network %v", i, test.name, test.origNet.Name) continue } extKey.SetNet(test.newNet) if !extKey.IsForNet(test.newNet) { t.Errorf("SetNet/IsForNet #%d (%s): key is not for "+ "expected network %v", i, test.name, test.newNet.Name) continue } if test.isPrivate { privStr := extKey.String() if privStr != test.newPriv { t.Errorf("Serialize #%d (%s): mismatched serialized "+ "private extended key -- got: %s, want: %s", i, test.name, privStr, test.newPriv) continue } extKey, err = extKey.Neuter() if err != nil { t.Errorf("Neuter #%d (%s): unexpected error: %v ", i, test.name, err) continue } } pubStr := extKey.String() if pubStr != test.newPub { t.Errorf("Neuter #%d (%s): mismatched serialized "+ "public extended key -- got: %s, want: %s", i, test.name, pubStr, test.newPub) continue } } } // TestErrors performs some negative tests for various invalid cases to ensure // the errors are handled properly. func TestErrors(t *testing.T) { // Should get an error when seed has too few bytes. net := &chaincfg.MainNetParams _, err := NewMaster(bytes.Repeat([]byte{0x00}, 15), net) if err != ErrInvalidSeedLen { t.Fatalf("NewMaster: mismatched error -- got: %v, want: %v", err, ErrInvalidSeedLen) } // Should get an error when seed has too many bytes. _, err = NewMaster(bytes.Repeat([]byte{0x00}, 65), net) if err != ErrInvalidSeedLen { t.Fatalf("NewMaster: mismatched error -- got: %v, want: %v", err, ErrInvalidSeedLen) } // Generate a new key and neuter it to a public extended key. seed, err := GenerateSeed(RecommendedSeedLen) if err != nil { t.Fatalf("GenerateSeed: unexpected error: %v", err) } extKey, err := NewMaster(seed, net) if err != nil { t.Fatalf("NewMaster: unexpected error: %v", err) } pubKey, err := extKey.Neuter() if err != nil { t.Fatalf("Neuter: unexpected error: %v", err) } // Deriving a hardened child extended key should fail from a public key. _, err = pubKey.Derive(HardenedKeyStart) if err != ErrDeriveHardFromPublic { t.Fatalf("Derive: mismatched error -- got: %v, want: %v", err, ErrDeriveHardFromPublic) } // NewKeyFromString failure tests. tests := []struct { name string key string err error neuter bool neuterErr error }{ { name: "invalid key length", key: "xpub1234", err: ErrInvalidKeyLen, }, { name: "bad checksum", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EBygr15", err: ErrBadChecksum, }, { name: "pubkey not on curve", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ1hr9Rwbk95YadvBkQXxzHBSngB8ndpW6QH7zhhsXZ2jHyZqPjk", err: errors.New("invalid square root"), }, { name: "unsupported version", key: "xbad4LfUL9eKmA66w2GJdVMqhvDmYGJpTGjWRAtjHqoUY17sGaymoMV9Cm3ocn9Ud6Hh2vLFVC7KSKCRVVrqc6dsEdsTjRV1WUmkK85YEUujAPX", err: nil, neuter: true, neuterErr: chaincfg.ErrUnknownHDKeyID, }, } for i, test := range tests { extKey, err := NewKeyFromString(test.key) if !reflect.DeepEqual(err, test.err) { t.Errorf("NewKeyFromString #%d (%s): mismatched error "+ "-- got: %v, want: %v", i, test.name, err, test.err) continue } if test.neuter { _, err := extKey.Neuter() if !reflect.DeepEqual(err, test.neuterErr) { t.Errorf("Neuter #%d (%s): mismatched error "+ "-- got: %v, want: %v", i, test.name, err, test.neuterErr) continue } } } } // TestZero ensures that zeroing an extended key works as intended. func TestZero(t *testing.T) { tests := []struct { name string master string extKey string net *chaincfg.Params }{ // Test vector 1 { name: "test vector 1 chain m", master: "000102030405060708090a0b0c0d0e0f", extKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", net: &chaincfg.MainNetParams, }, // Test vector 2 { name: "test vector 2 chain m", master: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", extKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", net: &chaincfg.MainNetParams, }, } // Use a closure to test that a key is zeroed since the tests create // keys in different ways and need to test the same things multiple // times. testZeroed := func(i int, testName string, key *ExtendedKey) bool { // Zeroing a key should result in it no longer being private if key.IsPrivate() { t.Errorf("IsPrivate #%d (%s): mismatched key type -- "+ "want private %v, got private %v", i, testName, false, key.IsPrivate()) return false } parentFP := key.ParentFingerprint() if parentFP != 0 { t.Errorf("ParentFingerprint #%d (%s): mismatched "+ "parent fingerprint -- want %d, got %d", i, testName, 0, parentFP) return false } wantKey := "zeroed extended key" serializedKey := key.String() if serializedKey != wantKey { t.Errorf("String #%d (%s): mismatched serialized key "+ "-- want %s, got %s", i, testName, wantKey, serializedKey) return false } wantErr := ErrNotPrivExtKey _, err := key.ECPrivKey() if !reflect.DeepEqual(err, wantErr) { t.Errorf("ECPrivKey #%d (%s): mismatched error: want "+ "%v, got %v", i, testName, wantErr, err) return false } wantErr = errors.New("pubkey string is empty") _, err = key.ECPubKey() if !reflect.DeepEqual(err, wantErr) { t.Errorf("ECPubKey #%d (%s): mismatched error: want "+ "%v, got %v", i, testName, wantErr, err) return false } wantAddr := "bVBPehMs2zB8d3vLtHJ7epkesDQJJ9D7Ek" addr, err := key.Address(&chaincfg.MainNetParams) if err != nil { t.Errorf("Addres s #%d (%s): unexpected error: %v", i, testName, err) return false } if addr.EncodeAddress() != wantAddr { t.Errorf("Address #%d (%s): mismatched address -- want "+ "%s, got %s", i, testName, wantAddr, addr.EncodeAddress()) return false } return true } for i, test := range tests { // Create new key from seed and get the neutered version. masterSeed, err := hex.DecodeString(test.master) if err != nil { t.Errorf("DecodeString #%d (%s): unexpected error: %v", i, test.name, err) continue } key, err := NewMaster(masterSeed, test.net) if err != nil { t.Errorf("NewMaster #%d (%s): unexpected error when "+ "creating new master key: %v", i, test.name, err) continue } neuteredKey, err := key.Neuter() if err != nil { t.Errorf("Neuter #%d (%s): unexpected error: %v", i, test.name, err) continue } // Ensure both non-neutered and neutered keys are zeroed // properly. key.Zero() if !testZeroed(i, test.name+" from seed not neutered", key) { continue } neuteredKey.Zero() if !testZeroed(i, test.name+" from seed neutered", key) { continue } // Deserialize key and get the neutered version. key, err = NewKeyFromString(test.extKey) if err != nil { t.Errorf("NewKeyFromString #%d (%s): unexpected "+ "error: %v", i, test.name, err) continue } neuteredKey, err = key.Neuter() if err != nil { t.Errorf("Neuter #%d (%s): unexpected error: %v", i, test.name, err) continue } // Ensure both non-neutered and neutered keys are zeroed // properly. key.Zero() if !testZeroed(i, test.name+" deserialized not neutered", key) { continue } neuteredKey.Zero() if !testZeroed(i, test.name+" deserialized neutered", key) { continue } } } // TestMaximumDepth ensures that attempting to retrieve a child key when already // at the maximum depth is not allowed. 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) { net := &chaincfg.MainNetParams extKey, err := NewMaster([]byte(`abcd1234abcd1234abcd1234abcd1234`), net) if err != nil { t.Fatalf("NewMaster: unexpected error: %v", err) } for i := uint8(0); i < math.MaxUint8; i++ { if extKey.Depth() != i { t.Fatalf("extendedkey depth %d should match expected value %d", extKey.Depth(), i) } newKey, err := extKey.Derive(1) if err != nil { t.Fatalf("Derive: unexpected error: %v", err) } extKey = newKey } noKey, err := extKey.Derive(1) if err != ErrDeriveBeyondMaxDepth { t.Fatalf("Derive: mismatched error: want %v, got %v", ErrDeriveBeyondMaxDepth, err) } if noKey != nil { t.Fatal("Derive: deriving 256th key should not succeed") } } // TestCloneWithVersion ensures proper conversion between standard and SLIP132 // extended keys. // // The following tool was used for generating the tests: // https://jlopp.github.io/xpub-converter func TestCloneWithVersion(t *testing.T) { tests := []struct { name string key string version []byte want string wantErr error }{ { name: "test xpub to zpub", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", version: []byte{0x04, 0xb2, 0x47, 0x46}, want: "zpub6jftahH18ngZxUuv6oSniLNrBCSSE1B4EEU59bwTCEt8x6aS6b2mdfLxbS4QS53g85SWWP6wexqeer516433gYpZQoJie2tcMYdJ1SYYYAL", }, { name: "test zpub to xpub", key: "zpub6jftahH18ngZxUuv6oSniLNrBCSSE1B4EEU59bwTCEt8x6aS6b2mdfLxbS4QS53g85SWWP6wexqeer516433gYpZQoJie2tcMYdJ1SYYYAL", version: []byte{0x04, 0x88, 0xb2, 0x1e}, want: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", }, { name: "test xprv to zprv", key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", version: []byte{0x04, 0xb2, 0x43, 0x0c}, want: "zprvAWgYBBk7JR8GjzqSzmunMCS7dAbwpYTCs1YUMDXqduMA5JFHZ3iX5s2UkAR6vBdcCYYa1S5o1fVLrKsrnpCQ4WpUd6aVUWP1bS2Yy5DoaKv", }, { name: "test zprv to xprv", key: "zprvAWgYBBk7JR8GjzqSzmunMCS7dAbwpYTCs1YUMDXqduMA5JFHZ3iX5s2UkAR6vBdcCYYa1S5o1fVLrKsrnpCQ4WpUd6aVUWP1bS2Yy5DoaKv", version: []byte{0x04, 0x88, 0xad, 0xe4}, want: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", }, { name: "test invalid key id", key: "zprvAWgYBBk7JR8GjzqSzmunMCS7dAbwpYTCs1YUMDXqduMA5JFHZ3iX5s2UkAR6vBdcCYYa1S5o1fVLrKsrnpCQ4WpUd6aVUWP1bS2Yy5DoaKv", version: []byte{0x4B, 0x1D}, wantErr: chaincfg.ErrUnknownHDKeyID, }, } for i, test := range tests { extKey, err := NewKeyFromString(test.key) if err != nil { panic(err) // This is never expected to fail. } got, err := extKey.CloneWithVersion(test.version) if !reflect.DeepEqual(err, test.wantErr) { t.Errorf("CloneWithVersion #%d (%s): unexpected error -- "+ "want %v, got %v", i, test.name, test.wantErr, err) continue } if test.wantErr == nil { if k := got.String(); k != test.want { t.Errorf("CloneWithVersion #%d (%s): "+ "got %s, want %s", i, test.name, k, test.want) continue } } } } // TestLeadingZero ensures that deriving children from keys with a leading zero byte is done according // to the BIP-32 standard and that the legacy method generates a backwards-compatible result. func TestLeadingZero(t *testing.T) { // The 400th seed results in a m/0' public key with a leading zero, allowing us to test // the desired behavior. ii := 399 seed := make([]byte, 32) binary.BigEndian.PutUint32(seed[28:], uint32(ii)) masterKey, err := NewMaster(seed, &chaincfg.MainNetParams) if err != nil { t.Fatalf("hdkeychain.NewMaster failed: %v", err) } child0, err := masterKey.Derive(0 + HardenedKeyStart) if err != nil { t.Fatalf("masterKey.Derive failed: %v", err) } if !child0.IsAffectedByIssue172() { t.Fatal("expected child0 to be affected by issue 172") } child1, err := child0.Derive(0 + HardenedKeyStart) if err != nil { t.Fatalf("child0.Derive failed: %v", err) } if child1.IsAffectedByIssue172() { t.Fatal("did not expect child1 to be affected by issue 172") } child1nonstandard, err := child0.DeriveNonStandard(0 + HardenedKeyStart) if err != nil { t.Fatalf("child0.DeriveNonStandard failed: %v", err) } // This is the correct result based on BIP32 if hex.EncodeToString(child1.key) != "a9b6b30a5b90b56ed48728c73af1d8a7ef1e9cc372ec21afcc1d9bdf269b0988" { t.Error("incorrect standard BIP32 derivation") } if hex.EncodeToString(child1nonstandard.key) != "ea46d8f58eb863a2d371a938396af8b0babe85c01920f59a8044412e70e837ee" { t.Error("incorrect lbcutil backwards compatible BIP32-like derivation") } if !child0.IsAffectedByIssue172() { t.Error("child 0 should be affected by issue 172") } if child1.IsAffectedByIssue172() { t.Error("child 1 should not be affected by issue 172") } }