// EmpowerSeries adds the given extended private key (in raw format) to the // series with the given ID, thus allowing it to sign deposit/withdrawal // scripts. The series with the given ID must exist, the key must be a valid // private extended key and must match one of the series' extended public keys. // // This method must be called with the Pool's manager unlocked. func (p *Pool) EmpowerSeries(seriesID uint32, rawPrivKey string) error { // make sure this series exists series := p.Series(seriesID) if series == nil { str := fmt.Sprintf("series %d does not exist for this voting pool", seriesID) return newError(ErrSeriesNotExists, str, nil) } // Check that the private key is valid. privKey, err := hdkeychain.NewKeyFromString(rawPrivKey) if err != nil { str := fmt.Sprintf("invalid extended private key %v", rawPrivKey) return newError(ErrKeyChain, str, err) } if !privKey.IsPrivate() { str := fmt.Sprintf( "to empower a series you need the extended private key, not an extended public key %v", privKey) return newError(ErrKeyIsPublic, str, err) } pubKey, err := privKey.Neuter() if err != nil { str := fmt.Sprintf("invalid extended private key %v, can't convert to public key", rawPrivKey) return newError(ErrKeyNeuter, str, err) } lookingFor := pubKey.String() found := false // Make sure the private key has the corresponding public key in the series, // to be able to empower it. for i, publicKey := range series.publicKeys { if publicKey.String() == lookingFor { found = true series.privateKeys[i] = privKey } } if !found { str := fmt.Sprintf( "private Key does not have a corresponding public key in this series") return newError(ErrKeysPrivatePublicMismatch, str, nil) } if err = p.saveSeriesToDisk(seriesID, series); err != nil { return err } return nil }
// BenchmarkSerialize benchmarks how long it takes to serialize a private // extended key. func BenchmarkSerialize(b *testing.B) { b.StopTimer() masterKey, err := hdkeychain.NewKeyFromString(bip0032MasterPriv1) if err != nil { b.Errorf("Failed to decode master seed: %v", err) } b.StartTimer() for i := 0; i < b.N; i++ { masterKey.String() } }
// BenchmarkDeriveHardened benchmarks how long it takes to derive a hardened // child from a master private extended key. func BenchmarkDeriveHardened(b *testing.B) { b.StopTimer() masterKey, err := hdkeychain.NewKeyFromString(bip0032MasterPriv1) if err != nil { b.Errorf("Failed to decode master seed: %v", err) } b.StartTimer() for i := 0; i < b.N; i++ { masterKey.Child(hdkeychain.HardenedKeyStart) } }
// decryptExtendedKey uses Manager.Decrypt() to decrypt the encrypted byte slice and return // an extended (public or private) key representing it. // // This method must be called with the Pool's manager unlocked. func (p *Pool) decryptExtendedKey(keyType waddrmgr.CryptoKeyType, encrypted []byte) (*hdkeychain.ExtendedKey, error) { decrypted, err := p.manager.Decrypt(keyType, encrypted) if err != nil { str := fmt.Sprintf("cannot decrypt key %v", encrypted) return nil, newError(ErrCrypto, str, err) } result, err := hdkeychain.NewKeyFromString(string(decrypted)) zero.Bytes(decrypted) if err != nil { str := fmt.Sprintf("cannot get key from string %v", decrypted) return nil, newError(ErrKeyChain, str, err) } return result, nil }
func createTestPubKeys(t *testing.T, number, offset int) []*hdkeychain.ExtendedKey { xpubRaw := "xpub661MyMwAqRbcFwdnYF5mvCBY54vaLdJf8c5ugJTp5p7PqF9J1USgBx12qYMnZ9yUiswV7smbQ1DSweMqu8wn7Jociz4PWkuJ6EPvoVEgMw7" xpubKey, err := hdkeychain.NewKeyFromString(xpubRaw) if err != nil { t.Fatalf("Failed to generate new key: %v", err) } keys := make([]*hdkeychain.ExtendedKey, number) for i := uint32(0); i < uint32(len(keys)); i++ { chPubKey, err := xpubKey.Child(i + uint32(offset)) if err != nil { t.Fatalf("Failed to generate child key: %v", err) } keys[i] = chPubKey } return keys }
// This example demonstrates the audits use case in BIP0032. func Example_audits() { // The audits use case described in BIP0032 is: // // In case an auditor needs full access to the list of incoming and // outgoing payments, one can share all account public extended keys. // This will allow the auditor to see all transactions from and to the // wallet, in all accounts, but not a single secret key. // // * N(m/*) // corresponds to the neutered master extended key (also called // the master public extended key) // Ordinarily this would either be read from some encrypted source // and be decrypted or generated as the NewMaster example shows, but // for the purposes of this example, the private extended key for the // master node is being hard coded here. master := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jP" + "PqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" // Start by getting an extended key instance for the master node. // This gives the path: // m masterKey, err := hdkeychain.NewKeyFromString(master) if err != nil { fmt.Println(err) return } // Neuter the master key to generate a master public extended key. This // gives the path: // N(m/*) masterPubKey, err := masterKey.Neuter() if err != nil { fmt.Println(err) return } // Share the master public extended key with the auditor. fmt.Println("Audit key N(m/*):", masterPubKey) // Output: // Audit key N(m/*): xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8 }
// Convert the given slice of strings into a slice of ExtendedKeys, // checking that all of them are valid public (and not private) keys, // and that there are no duplicates. func convertAndValidatePubKeys(rawPubKeys []string) ([]*hdkeychain.ExtendedKey, error) { seenKeys := make(map[string]bool) keys := make([]*hdkeychain.ExtendedKey, len(rawPubKeys)) for i, rawPubKey := range rawPubKeys { if _, seen := seenKeys[rawPubKey]; seen { str := fmt.Sprintf("duplicated public key: %v", rawPubKey) return nil, newError(ErrKeyDuplicate, str, nil) } seenKeys[rawPubKey] = true key, err := hdkeychain.NewKeyFromString(rawPubKey) if err != nil { str := fmt.Sprintf("invalid extended public key %v", rawPubKey) return nil, newError(ErrKeyChain, str, err) } if key.IsPrivate() { str := fmt.Sprintf("private keys not accepted: %v", rawPubKey) return nil, newError(ErrKeyIsPrivate, str, nil) } keys[i] = key } return keys, nil }
// BenchmarkDeserialize benchmarks how long it takes to deserialize a private // extended key. func BenchmarkDeserialize(b *testing.B) { for i := 0; i < b.N; i++ { hdkeychain.NewKeyFromString(bip0032MasterPriv1) } }
// This example demonstrates the default hierarchical deterministic wallet // layout as described in BIP0032. func Example_defaultWalletLayout() { // The default wallet layout described in BIP0032 is: // // Each account is composed of two keypair chains: an internal and an // external one. The external keychain is used to generate new public // addresses, while the internal keychain is used for all other // operations (change addresses, generation addresses, ..., anything // that doesn't need to be communicated). // // * m/iH/0/k // corresponds to the k'th keypair of the external chain of account // number i of the HDW derived from master m. // * m/iH/1/k // corresponds to the k'th keypair of the internal chain of account // number i of the HDW derived from master m. // Ordinarily this would either be read from some encrypted source // and be decrypted or generated as the NewMaster example shows, but // for the purposes of this example, the private extended key for the // master node is being hard coded here. master := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jP" + "PqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" // Start by getting an extended key instance for the master node. // This gives the path: // m masterKey, err := hdkeychain.NewKeyFromString(master) if err != nil { fmt.Println(err) return } // Derive the extended key for account 0. This gives the path: // m/0H acct0, err := masterKey.Child(hdkeychain.HardenedKeyStart + 0) if err != nil { fmt.Println(err) return } // Derive the extended key for the account 0 external chain. This // gives the path: // m/0H/0 acct0Ext, err := acct0.Child(0) if err != nil { fmt.Println(err) return } // Derive the extended key for the account 0 internal chain. This gives // the path: // m/0H/1 acct0Int, err := acct0.Child(1) if err != nil { fmt.Println(err) return } // At this point, acct0Ext and acct0Int are ready to derive the keys for // the external and internal wallet chains. // Derive the 10th extended key for the account 0 external chain. This // gives the path: // m/0H/0/10 acct0Ext10, err := acct0Ext.Child(10) if err != nil { fmt.Println(err) return } // Derive the 1st extended key for the account 0 internal chain. This // gives the path: // m/0H/1/0 acct0Int0, err := acct0Int.Child(0) if err != nil { fmt.Println(err) return } // Get and show the address associated with the extended keys for the // main bitcoin network. acct0ExtAddr, err := acct0Ext10.Address(&chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } acct0IntAddr, err := acct0Int0.Address(&chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } fmt.Println("Account 0 External Address 10:", acct0ExtAddr) fmt.Println("Account 0 Internal Address 0:", acct0IntAddr) // Output: // Account 0 External Address 10: 1HVccubUT8iKTapMJ5AnNA4sLRN27xzQ4F // Account 0 Internal Address 0: 1J5rebbkQaunJTUoNVREDbeB49DqMNFFXk }
// 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 *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, 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 = 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 } } }
// 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 := hdkeychain.NewMaster(bytes.Repeat([]byte{0x00}, 15), net) 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), net) 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, net) 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 } } } }
// 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 := hdkeychain.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 } } }
// 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: "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma", }, { name: "test vector 1 chain m/0H/1/2H public", extKey: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", isPrivate: false, parentFP: 3203769081, privKeyErr: hdkeychain.ErrNotPrivExtKey, pubKey: "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", address: "1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x", }, } for i, test := range tests { key, err := hdkeychain.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(&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 } } }
// 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 := 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 } } 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 } } }