// This example demonstrates how to generate a cryptographically random seed // then use it to create a new master node (extended key). func ExampleNewMaster() { // Generate a random seed at the recommended length. seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { fmt.Println(err) return } // Generate a new master node using the seed. key, err := hdkeychain.NewMaster(seed) if err != nil { fmt.Println(err) return } // Show that the generated master node extended key is private. fmt.Println("Private Extended Key?:", key.IsPrivate()) // Output: // Private Extended Key?: true }
// 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. _, err := hdkeychain.NewMaster(bytes.Repeat([]byte{0x00}, 15)) if err != hdkeychain.ErrInvalidSeedLen { t.Errorf("NewMaster: mismatched error -- got: %v, want: %v", err, hdkeychain.ErrInvalidSeedLen) } // Should get an error when seed has too many bytes. _, err = hdkeychain.NewMaster(bytes.Repeat([]byte{0x00}, 65)) if err != hdkeychain.ErrInvalidSeedLen { t.Errorf("NewMaster: mismatched error -- got: %v, want: %v", err, hdkeychain.ErrInvalidSeedLen) } // Generate a new key and neuter it to a public extended key. seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { t.Errorf("GenerateSeed: unexpected error: %v", err) return } extKey, err := hdkeychain.NewMaster(seed) if err != nil { t.Errorf("NewMaster: unexpected error: %v", err) return } pubKey, err := extKey.Neuter() if err != nil { t.Errorf("Neuter: unexpected error: %v", err) return } // Deriving a hardened child extended key should fail from a public key. _, err = pubKey.Child(hdkeychain.HardenedKeyStart) if err != hdkeychain.ErrDeriveHardFromPublic { t.Errorf("Child: mismatched error -- got: %v, want: %v", err, hdkeychain.ErrDeriveHardFromPublic) } // NewKeyFromString failure tests. tests := []struct { name string key string err error neuter bool neuterErr error }{ { name: "invalid key length", key: "xpub1234", err: hdkeychain.ErrInvalidKeyLen, }, { name: "bad checksum", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EBygr15", err: hdkeychain.ErrBadChecksum, }, { name: "pubkey not on curve", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ1hr9Rwbk95YadvBkQXxzHBSngB8ndpW6QH7zhhsXZ2jHyZqPjk", err: errors.New("pubkey isn't on secp256k1 curve"), }, { name: "unsupported version", key: "xbad4LfUL9eKmA66w2GJdVMqhvDmYGJpTGjWRAtjHqoUY17sGaymoMV9Cm3ocn9Ud6Hh2vLFVC7KSKCRVVrqc6dsEdsTjRV1WUmkK85YEUujAPX", err: nil, neuter: true, neuterErr: chaincfg.ErrUnknownHDKeyID, }, } for i, test := range tests { extKey, err := hdkeychain.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 }{ // Test vector 1 { name: "test vector 1 chain m", master: "000102030405060708090a0b0c0d0e0f", extKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", }, // Test vector 2 { name: "test vector 2 chain m", master: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", extKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", }, } // 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 *hdkeychain.ExtendedKey) bool { // Zeroing a key should result in it no longer being private if key.IsPrivate() != false { 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 := hdkeychain.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 := "1HT7xU2Ngenf7D4yocz2SAcnNLW7rK8d4E" 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 := hdkeychain.NewMaster(masterSeed) 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 = hdkeychain.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 } } }
// 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" hkStart := uint32(0x80000000) tests := []struct { name string master string path []uint32 wantPub string wantPriv string }{ // Test vector 1 { name: "test vector 1 chain m", master: testVec1MasterHex, path: []uint32{}, wantPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", wantPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", }, { name: "test vector 1 chain m/0H", master: testVec1MasterHex, path: []uint32{hkStart}, wantPub: "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", wantPriv: "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", }, { name: "test vector 1 chain m/0H/1", master: testVec1MasterHex, path: []uint32{hkStart, 1}, wantPub: "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", wantPriv: "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", }, { name: "test vector 1 chain m/0H/1/2H", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2}, wantPub: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", wantPriv: "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", }, { name: "test vector 1 chain m/0H/1/2H/2", master: testVec1MasterHex, path: []uint32{hkStart, 1, hkStart + 2, 2}, wantPub: "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", wantPriv: "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", }, { 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", }, // Test vector 2 { name: "test vector 2 chain m", master: testVec2MasterHex, path: []uint32{}, wantPub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", wantPriv: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", }, { name: "test vector 2 chain m/0", master: testVec2MasterHex, path: []uint32{0}, wantPub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", wantPriv: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", }, { name: "test vector 2 chain m/0/2147483647H", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647}, wantPub: "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", wantPriv: "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", }, { name: "test vector 2 chain m/0/2147483647H/1", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647, 1}, wantPub: "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", wantPriv: "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", }, { name: "test vector 2 chain m/0/2147483647H/1/2147483646H", master: testVec2MasterHex, path: []uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646}, wantPub: "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", wantPriv: "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", }, { 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", }, } 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 := hdkeychain.NewMaster(masterSeed) 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 } } 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 } } }