// 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/hex" "github.com/kaspanet/kaspad/testutil" "github.com/pkg/errors" "math" "reflect" "testing" "github.com/kaspanet/kaspad/util" ) // 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 hdKeyIDPair HDKeyIDPair }{ // Test vector 1 { name: "test vector 1 chain m", master: testVec1MasterHex, path: []uint32{}, wantPub: "xpub661MyMwAqRbcGTTa4Jdtmttwg6yq1xoxNMPqCMT4iasLkbMDJHwHX6GGExBu9TXWSs2W7YNtQciJWhDuE2yJSDbHKSptjnBNnHMGxg5ybMW", wantPriv: "xprv9s21ZrQH143K3yP6xH6tQkxD859LcW6718UEPy3TAFLMso24kkd2yHwnPeKuxD5MyK3YvXTJsqgXVp6hdrhYNabhhgxSPNbh3N69w5WrGT1", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 1 chain m/0H", master: testVec1MasterHex, path: []uint32{hkStart}, wantPub: "xpub69KEapfQ6NaCMD2hCspTKoAv2or3Ti3Uu5mXf84w5pFskMpPvNKWyBELokM6pCz51Pjw6gLor1troP7eeUgczktnzewPcdXVh9j6aPJhfH8", wantPriv: "xprv9vKtBK8WG11u8ixE6rHSxfEBUn1Z4FKdXrqvrjfKXUitsZVFNq1GRNurxUUZ5Rhrw5MpM6nbBQJJMnA2qoQD3j2JbbG4BS594GcQsToQaiB", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 1 chain m/0H/1", master: testVec1MasterHex, path: []uint32{hkStart, 1}, wantPub: "xpub6AjVQFnusUq1NAzf6s5HAfHnxPZAaXNvCD2dxb5mr68GYMYSTBsGYL1sqe1NERKsdZPA2CQeASih53Ms3XXQgwagHCVCM8jwvMTsrdBTUsi", wantPriv: "xprv9wk8zkG237Gi9gvBzqYGoXM4QMigB4f4pz73ACgAHkbHfZDHueZ1zXhPzMWScvVYsHvckXUViA3BfUiKiyTJuRnbWEkKCD1LNvzYQkjzAg6", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 1 chain m/0H/1/2H", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2}, wantPub: "xpub6BqBypWNhDSbEmsBUrkmQhBfpxAQN4HvStjopj7pdQSTHcG5z5yQhxR2qYQQa4J3FAfjK5nzPocGKzkjWxtEKDL5Gom4rfWkqxzR26QnsSk", wantPriv: "xprv9xqqaJyUrqtJ2HniNqDm3ZEwGvKuxba55fpD2LiD54uUQovwSYfAAA6YzHAPgpojwoc4webEpkbhyzrq6xKY18HrfZYbTb5F1ZchyoNTSJh", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 1 chain m/0H/1/2H/2", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2}, wantPub: "xpub6EgqrrZKS9A48mXpZtsWQ57Evxzt3sPHwP41p3TmqynQABtGcspvs1ivvHTiQWnuarDsCBPvMK6hf3rnqefXkVTtcs1JZdYSrL5WFtmm92b", wantPriv: "xprvA1hVTM2RbmbkvHTMTsLW2wAWNwAPeQfSaA8R1f4AHeFRHPZ85LWgKDQT53tf7QeCfrh679CxyJZSasHJHJg6Tqed8qo4gTDmapTY9RQEQLJ", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 1 chain m/0H/1/2H/2/1000000000", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2, 1000000000}, wantPub: "xpub6HJ4G6aN3TxzUM7jp992qnKyJR3eSwvPAL2YASqBmR3GnoxUGFjZWdCv8ZfuY3XxkJiq4ukTdfzMDLDrcSGL9QE51a6pWSNFi579DEGbdqQ", wantPriv: "xprvA4Jhrb3UD6QhFs3Gi7c2UePEkPDA3VCXo76wN4RaD5WHv1dKiiRJxptSHJYEBqf9DnhBy5Acj9ECL2Fi3vcr78ivPMes5qoELfw9xuENti1", hdKeyIDPair: HDKeyPairMainnet, }, // Test vector 2 { name: "test vector 2 chain m", master: testVec2MasterHex, path: []uint32{}, wantPub: "xpub661MyMwAqRbcGgMhBVBjF9Nmm7gem3XyVKNwpxEUVoreyzfksY8hvVjTHvxgNw5fZznRPrGE9xf1WR2vHkBB3Gc8oBDpiaC2vScCKGcdA1d", wantPriv: "xprv9s21ZrQH143K4CHE5Teit1S3D5rAMap886TM2ZprwUKg7CLcKzpTNhQySeBnY7R7LL6kZyZPjHGkc9ouLY4Dhy6e9UFP2KJ6j3TiBXACRSB", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 2 chain m/0", master: testVec2MasterHex, path: []uint32{0}, wantPub: "xpub6912QQoTAjwVLJiaMFhPbu4maaaieUN2FQrSWCYSBtBSeM5yd6eK7pCFFHXLUPeh6iAaGR6ErHjJ8x2VKW7F4Htu9iVWzAtvj88UQKNvxj6", wantPriv: "xprv9v1fzuGZLNPC7pe7FEAPEm832YkEF1eAtBvqhp8pdYeTmYkq5ZL4a1smPzv3LKVArYkYvPgLgP947tJmH38NqCWcpkzNbXvQ9joq6FQePaw", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 2 chain m/0/2147483647H", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647}, wantPub: "xpub6B9QvMX5vvu5y1aEfyJHpPymeL81y9cdU3eo8EA55ftMuqaJG6HNaznyxDXX1WaQPWQjRahRz5VsGNtJRRWTkhtxgKHCm4PhTsrYz11cdm7", wantPriv: "xprv9xA4WqzC6ZLnkXVmZwmHTG336JHXZgtn6pjCKqkTXLMP33F9iYy83CUW6ySkBGQtHhBGqZpqzStP5C3Xq5rvqNrhPCuPEsqUXPNQnTKyiax", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 2 chain m/0/2147483647H/1", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647, 1}, wantPub: "xpub6CPAoCUuVd4VKDE39r1aGoRndRdBqD9QXWXt4hYxKWDznaR1nJXgjbnb5DQNmzYt83SmvCe9zmGXWRwi1M5rqCUkwNDLsjc4jUJcjqbKgSW", wantPriv: "xprv9yPpPgx1fFWC6j9a3pUZufV45PnhRkRZAHcHGK9LmAh1un5sEmDSBoU7DwtYAvgsUoewLkJRhXhWZjxJ6TuPDSL3tXSuAkz3nFEw83F9PNX", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 2 chain m/0/2147483647H/1/2147483646H", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646}, wantPub: "xpub6EGnnakCt69xbT83sUN5BhrRr933BtTd4cMUiqzyrkRWkNhnKeMifJN1G9gjbART8QA86KxyYSijp9DpEi12oFnznwS7H1vFjGVcta65Ru1", wantPriv: "xprvA1HSP5DK3ibfNy3amSq4pZuhJ7CYnRjmhPRsvTbNJQtXsaNdn73U7W3XQszDCEKwRrgGTSe8R2nFcTje7eBeyP9V8264EHeuvt8mXLbdQWQ", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 2 chain m/0/2147483647H/1/2147483646H/2", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646, 2}, wantPub: "xpub6GyY8YKU9JoxnwgqbusBYvJByXBiY2FWE2HMrXQDunVs61Rr5ojSBkeLxwnsQ9mMkMY1JVvRdMtoY5KHSwv8jWypeFX5tWzkhiH4qj5xEhr", wantPriv: "xprvA3zBj2naJwFfaTcNVtLBBnMTRVME8ZXeroMm48zcMSxtDD6hYGRBdxKs7e1j3oih75fyXXkPLPcCoBt6GmfdgZQqTw3Zt7i18j1CTQnDCYs", hdKeyIDPair: HDKeyPairMainnet, }, // Test vector 3 { name: "test vector 3 chain m", master: testVec3MasterHex, path: []uint32{}, wantPub: "xpub661MyMwAqRbcH1Qh3yC3Zk1eESmJ1QpoXt8HK2h3MTA5QTMsKYD4guWkaqDdrmiHFRssCPRaUA9CD2tXhNtmN1aT7eV18q8BoExXbo4mEtZ", wantPriv: "xprv9s21ZrQH143K4XLDwwf3Cc4ugQvobx6xAfCgWeHRo7d6Xf2imztp97CGjX9Yu7gpv9tfoNNx99bEUTkinzeXKrdafDkuhqwK5TeViwMWZ6i", hdKeyIDPair: HDKeyPairMainnet, }, { name: "test vector 3 chain m/0H", master: testVec3MasterHex, path: []uint32{hkStart}, wantPub: "xpub68sNRZc1xTrER4A1q2buYUQyfaRMv8FgXDvR2xQhtEhCzVMaFoXbquRd5JPfCQv3dN48d7Cuq3fCdTXzrxXZxbZoEwhBAz6xKwFUc3vRgg4", wantPriv: "xprv9ut2245886HwCa5Yj14uBLUF7YasWfXq9zzpEa16KuAE7h2RiGDMJ779E4cv62x55XQNiZ4jaUbes1G9nYoBKAE8b6Ud8mrhABo9fxLRuad", hdKeyIDPair: HDKeyPairMainnet, }, // Test vector 1 - Testnet { name: "test vector 1 chain m - testnet", master: testVec1MasterHex, path: []uint32{}, wantPub: "tpubD6NzVbkrYhZ4YFeRWVcyypEK1E5V1MK1uyz8YuWD4VdEVt1vNWnNfXw6UzGgfxUkEmZQj7tkwiYMCsiWctpYKf3aP3aCRJqcNEwgienHE4w", wantPriv: "tprv8ZgxMBicQKsPencdcqxPaQaCSCZYr287LgPMGPTueDpqfPm9k7xnV3KEJpVZxaTgLkaKvd553CGKxfeSm53VBdsJELAk3jKjxTqaNj93jhF", hdKeyIDPair: HDKeyPairTestnet, }, { name: "test vector 1 chain m/0H - testnet", master: testVec1MasterHex, path: []uint32{hkStart}, wantPub: "tpubD9gs74V5oeXed1DYf4oYXiWHMvwhT6YYSiMq1g85Rj1mVeV6zbAc7cuB3nRtLhwJoJGqiFrgP7iuVZcG3LXrtCM64FghJABjH7KWLRMoCvo", wantPriv: "tprv8czpxeSqfGqyjYBkmR8x8JrAnuRmHmMdsQm3jA5n1TDNfAELNCM1w8HJseeD5o6BJWtbMCQMLkt6pdhmy1k9rnHu8EUMqnoByNMqKA1yKAG", hdKeyIDPair: HDKeyPairTestnet, }, { name: "test vector 1 chain m/0H/1 - testnet", master: testVec1MasterHex, path: []uint32{hkStart, 1}, wantPub: "tpubDB77vVcbaknTdyBWZ44NNadAHWepZusyjqcwK98vBztAHeD9XQiMgmgi5g69kvH7RTv4dmvWhYYjmDrUSPNeaP2yLoEW2fQBWK4HceSHWLM", wantPriv: "tprv8eR5n5aMSP6nkW9ifQPmyAy3iV8tQah5AY2A2d6cmj5mT9xNu1tmWH4quXg6dHssEjTPkd6FsWcz8LG4rBoFiV4C2sxcrZjPJ2jxrRiSdSz", hdKeyIDPair: HDKeyPairTestnet, }, { name: "test vector 1 chain m/0H/1/2H - testnet", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2}, wantPub: "tpubDCCpW4L4QVQ3Wa42w3jrccX3A5G4MSnyzXL7BHAxyKCM2tvo4JpVrQ5s5aVC6ZFH35CdvfJrvuSK2BFLupjUCenNLQWNYCAzRvapn4MGur7", wantPriv: "tprv8fWnMeHpG7iNd72F3Q5GDCrvb3k8C7c5RDjKtm8fZ3PxCQg2RuzufuTzuTL3hCC4KF8qwkCzz7BWSrQaEAfUpBZTCCku7woHvfN8RSEDVA8", hdKeyIDPair: HDKeyPairTestnet, }, { name: "test vector 1 chain m/0H/1/2H/2 - testnet", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2}, wantPub: "tpubDF4UP6P19R7WQZig25rbbzScG66Y3FtMV1eKAbWvBtYHuUYyh6g21TPmAKYVw1k9NkkmokuntQvkMEMQEWWmdvvBgTkcFACgSHfv1ze714s", wantPriv: "tprv8iNSEgLm13RqX6gt8SC1CanVh4absvhSui3Xt5Ucmcju4zJD4hrRpxmtzE4K7n2X3JDs7Epj8f9F3iq3QX23GtvDfV1NLowpVvCxb7VMUpG", hdKeyIDPair: HDKeyPairTestnet, }, { name: "test vector 1 chain m/0H/1/2H/2/1000000000 - testnet", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2, 1000000000}, wantPub: "tpubDHfgnLQ3kjvSk9JbGL883hfLdY9JSLRShxcqWztL7KoAY6dBLUaef4skNbkh4YVCYDFjgVGLAmpPuWiU1J7a2qgN5Ar8By2VJ2hYyEPjRX2", wantPriv: "tprv8kyedvMocNEmrgGoNgTXeJ1E4WdNH1EY8f24EUr2h3zmhcNQi5m4UaFtCUhtCD3TbEDxyAnNtVoznsoTB8xnvBzWuzsAkCXHFmgaQbL7CbH", hdKeyIDPair: HDKeyPairTestnet, }, } 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.hdKeyIDPair.PrivateKeyID) 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.Child(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.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) { // 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.Child(childNum) if err != nil { t.Errorf("err: %v", err) continue tests } } pubStr := extKey.String() if pubStr != test.wantPub { t.Errorf("Child #%d (%s): mismatched serialized "+ "public extended key -- got: %s, want: %s", i, test.name, pubStr, test.wantPub) continue } } } // TestGenenerateSeed ensures the GenerateSeed function works as intended. func TestGenenerateSeed(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 !testutil.AreErrorsEqual(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 privKey string privKeyErr error pubKey string address string }{ { name: "test vector 1 master node private", extKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", isPrivate: true, parentFP: 0, privKey: "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", pubKey: "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", address: "kaspa:qq6yyxf7rwmsj9hfz32jzukdfckme80czy6wxqe82z", }, { name: "test vector 1 chain m/0H/1/2H public", extKey: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", isPrivate: false, parentFP: 3203769081, privKeyErr: ErrNotPrivExtKey, pubKey: "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", address: "kaspa:qrh84wgvmet23s8zhvyx43yhfzudh8wwwgvqz64sfs", }, } 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 } 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(util.Bech32PrefixKaspa) 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 origHDKeyIDPair HDKeyIDPair newHDKeyIDPair HDKeyIDPair newPriv string newPub string isPrivate bool }{ // Private extended keys. { name: "mainnet -> simnet", key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", origHDKeyIDPair: HDKeyPairMainnet, newHDKeyIDPair: HDKeyPairSimnet, newPriv: "sprv8Erh3X3hFeKunvVdAGQQtambRPapECWiTDtvsTGdyrhzhbYgnSZajRRWbihzvq4AM4ivm6uso31VfKaukwJJUs3GYihXP8ebhMb3F2AHu3P", newPub: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", isPrivate: true, }, { name: "simnet -> mainnet", key: "sprv8Erh3X3hFeKunvVdAGQQtambRPapECWiTDtvsTGdyrhzhbYgnSZajRRWbihzvq4AM4ivm6uso31VfKaukwJJUs3GYihXP8ebhMb3F2AHu3P", origHDKeyIDPair: HDKeyPairSimnet, newHDKeyIDPair: HDKeyPairMainnet, newPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", isPrivate: true, }, { name: "mainnet -> regtest", key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", origHDKeyIDPair: HDKeyPairMainnet, newHDKeyIDPair: HDKeyPairRegressionNet, newPriv: "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", newPub: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", isPrivate: true, }, { name: "regtest -> mainnet", key: "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", origHDKeyIDPair: HDKeyPairRegressionNet, newHDKeyIDPair: HDKeyPairMainnet, newPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", isPrivate: true, }, // Public extended keys. { name: "mainnet -> simnet", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", origHDKeyIDPair: HDKeyPairMainnet, newHDKeyIDPair: HDKeyPairSimnet, newPub: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", isPrivate: false, }, { name: "simnet -> mainnet", key: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", origHDKeyIDPair: HDKeyPairSimnet, newHDKeyIDPair: HDKeyPairMainnet, newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", isPrivate: false, }, { name: "mainnet -> regtest", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", origHDKeyIDPair: HDKeyPairMainnet, newHDKeyIDPair: HDKeyPairRegressionNet, newPub: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", isPrivate: false, }, { name: "regtest -> mainnet", key: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", origHDKeyIDPair: HDKeyPairRegressionNet, newHDKeyIDPair: HDKeyPairMainnet, 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.origHDKeyIDPair) { t.Errorf("IsForNet #%d (%s): key is not for expected "+ "network %v", i, test.name, test.origHDKeyIDPair) continue } extKey.SetNet(test.newHDKeyIDPair) if !extKey.IsForNet(test.newHDKeyIDPair) { t.Errorf("SetNet/IsForNet #%d (%s): key is not for "+ "expected network %v", i, test.name, test.newHDKeyIDPair) 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. hdKeyIDPair := HDKeyPairMainnet _, err := NewMaster(bytes.Repeat([]byte{0x00}, 15), hdKeyIDPair.PrivateKeyID) 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), hdKeyIDPair.PrivateKeyID) 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, hdKeyIDPair.PrivateKeyID) 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.Child(HardenedKeyStart) if err != ErrDeriveHardFromPublic { t.Fatalf("Child: 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: ErrUnknownHDKeyID, }, } for i, test := range tests { extKey, err := NewKeyFromString(test.key) if !testutil.AreErrorsEqual(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 hdKeyIDPair HDKeyIDPair }{ // Test vector 1 { name: "test vector 1 chain m", master: "000102030405060708090a0b0c0d0e0f", extKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", hdKeyIDPair: HDKeyPairMainnet, }, // Test vector 2 { name: "test vector 2 chain m", master: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", extKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", hdKeyIDPair: HDKeyPairMainnet, }, } // 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 !testutil.AreErrorsEqual(err, wantErr) { t.Errorf("ECPubKey #%d (%s): mismatched error: want "+ "%v, got %v", i, testName, wantErr, err) return false } wantAddr := "kaspa:qz689gnx6z7cnsfhq6jpxtx0k9hhcwulev2ematvwr" addr, err := key.Address(util.Bech32PrefixKaspa) 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.hdKeyIDPair.PrivateKeyID) 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) { extKey, err := NewMaster([]byte(`abcd1234abcd1234abcd1234abcd1234`), HDKeyPairMainnet.PrivateKeyID) 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.Child(1) if err != nil { t.Fatalf("Child: unexpected error: %v", err) } extKey = newKey } noKey, err := extKey.Child(1) if err != ErrDeriveBeyondMaxDepth { t.Fatalf("Child: mismatched error: want %v, got %v", ErrDeriveBeyondMaxDepth, err) } if noKey != nil { t.Fatal("Child: deriving 256th key should not succeed") } }