// 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) } }
// 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() } }
// 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 }
// 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 exteded key for the // master node is being hard coded here. master := "dprv3hCznBesA6jBushjx7y9NrfheE4ZshnaKYtsoLXefmLPzrXgEiXkd" + "RMD6UngnmBYZzgNhdEd4K3PidxcaCiR6HC9hmpj8FcrP4Cv7zBwELA" // 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. mpks, err := masterPubKey.String() if err != nil { panic("unexpected error creating string of extended public key") } fmt.Println("Audit key N(m/*):", mpks) // Output: // Audit key N(m/*): dpubZ9169KDAEUnypHbWCe2Vu5TxGEcqJeNeX6XCYFU1fqw2iQZK7fsMhzsEFArbLmyUdprUw9aXHneUNd92bjc31TqC6sUduMY6PK2z4JXDS8j }
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 }
// 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) } }
// 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: "dprv3hCznBesA6jBtmoyVFPfyMSZ1qYZ3WdjdebquvkEfmRfxC9VFEFi2YDaJqHnx7uGe75eGSa3Mn3oHK11hBW7KZUrPxwbCPBmuCi1nwm182s", net: &chaincfg.MainNetParams, }, // Test vector 2 { name: "test vector 2 chain m", master: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", extKey: "dprv3hCznBesA6jBtPKJbQTxRZAKG2gyj8tZKEPaCsV4e9YYFBAgRP2eTSPAeu4r8dTMt9q51j2Vdt5zNqj7jbtovvocrP1qLj6WUTLF9xYQt4y", 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" _, errZeroed := key.String() if errZeroed.Error() != wantKey { t.Errorf("String #%d (%s): mismatched serialized key "+ "-- want %s, got %s", i, testName, wantKey, errZeroed) 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 := "DsWuefL3Rgj6NXoMFqqBzxY2nmh87RZyPkv" 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: "dpub1234", err: hdkeychain.ErrInvalidKeyLen, }, { name: "bad checksum", key: "dpubZF6AWaFizAuUcbkZSs8cP8Gxzr6Sg5tLYYM7gEjZMC5GDaSHB4rW4F51zkWyo9U19BnXhc99kkEiPg248bYin8m9b8mGss9nxV6N2QpU8vj", err: hdkeychain.ErrBadChecksum, }, { name: "pubkey not on curve", key: "dpubZ9169KDAEUnyoTzA7pDGtXbxpji5LuUk8johUPVGY2CDsz6S7hahGNL6QkeYrUeAPnaJD1MBmrsUnErXScGZdjL6b2gjCRX1Z1GNhLdVCjv", err: errors.New("pubkey [0,50963827496501355358210603252497135226159332537351223778668747140855667399507] isn't on secp256k1 curve"), }, { name: "unsupported version", key: "4s9bfpYH9CkJboPNLFC4BhTENPrjfmKwUxesnqxHBjv585bCLzVdQKuKQ5TouA57FkdDskrR695Z5U2wWwDUUVWXPg7V57sLpc9dMgx74LsVZGEB", 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: "dprv3hCznBesA6jBu46PsJ9vNJoiCj9ouxtfwCBNjUYuXwbbAS4oEkF6Bnp5G3QbBAjRXy4uWWZYmC5Y71s3ovCyPLrCjEkYGPErrueuPPjvNWh", origNet: &chaincfg.MainNetParams, newNet: &chaincfg.SimNetParams, newPriv: "sprvZ9xkGEZkBei2p9e1uBZRQMGtfGEQNGApP1W19PyNRqg9nuEs2X4ynkvAXWaBiGb5WKiaqcbiKgmyB1HYgcX3mnxiUs7UWeWEfe4tnSpbXLv", newPub: "spubVNx6fk6e22GL2diV1D6RmVDdDJ4tmitfkERbwnNyzBD8fha1a4PELZEeNoUfNofdyJS2Y19tFgHZQ62tzKwELiBA3xVeZowLr4DJQ7xGuao", isPrivate: true, }, { name: "simnet -> mainnet", key: "sprvZ9xkGEZkBei2p9e1uBZRQMGtfGEQNGApP1W19PyNRqg9nuEs2X4ynkvAXWaBiGb5WKiaqcbiKgmyB1HYgcX3mnxiUs7UWeWEfe4tnSpbXLv", origNet: &chaincfg.SimNetParams, newNet: &chaincfg.MainNetParams, newPriv: "dprv3hCznBesA6jBu46PsJ9vNJoiCj9ouxtfwCBNjUYuXwbbAS4oEkF6Bnp5G3QbBAjRXy4uWWZYmC5Y71s3ovCyPLrCjEkYGPErrueuPPjvNWh", newPub: "dpubZ9169KDAEUnyoTzA7pDGtXbxpji5LuUk8johUPVGY2CDsz6S7hahGNL6QmyavE5fgonsepiACAa7FQPsCDeLFnoSSAGiQEQhimBGGK84nye", isPrivate: true, }, // Public extended keys. { name: "mainnet -> simnet", key: "dpubZ9169KDAEUnyoTzA7pDGtXbxpji5LuUk8johUPVGY2CDsz6S7hahGNL6QmyavE5fgonsepiACAa7FQPsCDeLFnoSSAGiQEQhimBGGK84nye", origNet: &chaincfg.MainNetParams, newNet: &chaincfg.SimNetParams, newPub: "spubVNx6fk6e22GL2diV1D6RmVDdDJ4tmitfkERbwnNyzBD8fha1a4PELZEeNoUfNofdyJS2Y19tFgHZQ62tzKwELiBA3xVeZowLr4DJQ7xGuao", isPrivate: false, }, { name: "simnet -> mainnet", key: "spubVNx6fk6e22GL2diV1D6RmVDdDJ4tmitfkERbwnNyzBD8fha1a4PELZEeNoUfNofdyJS2Y19tFgHZQ62tzKwELiBA3xVeZowLr4DJQ7xGuao", origNet: &chaincfg.SimNetParams, newNet: &chaincfg.MainNetParams, newPub: "dpubZ9169KDAEUnyoTzA7pDGtXbxpji5LuUk8johUPVGY2CDsz6S7hahGNL6QmyavE5fgonsepiACAa7FQPsCDeLFnoSSAGiQEQhimBGGK84nye", 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: "dprv3hCznBesA6jBteCheVrorP4Nix6oEWFwtk79FaLy1rPzedNGg9Jhy9y596C9uCPMeeKJMvtEGZRaxPKxGuCFgR4uo2EySsA29GsnXcrsby8", isPrivate: true, parentFP: 0, privKey: "33a63922ea4e6686c9fc31daf136888297537f66c1aabe3363df06af0b8274c7", pubKey: "039f2e1d7b50b8451911c64cf745f9ba16193b319212a64096e5679555449d8f37", address: "Dsk8SfRLF2hssYuLcb6Gu4zh19rg2QBEDGs", }, { name: "test vector 2 chain m/0/2147483647/1/2147483646/2", extKey: "dpubZRuRErXqhdJaZWD1AzXB6d5w2zw7UZ7ALxiS1gHbnQbVEohBzQzsVwGRzq97pmuE7ToA6DGn2QTH4DexxzdnMvkiYUpk8Nh2KEuYUM2RCeU", isPrivate: false, parentFP: 4220580796, privKeyErr: hdkeychain.ErrNotPrivExtKey, pubKey: "03dceb0b07698ec3d6ac08ae7297e7f5e63d7fda99d3fce1ded31d36badcdd4d36", address: "DsZcjfdSKUrEQxoyjkWEo7dM4YZKhma8wCa", }, } 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 := "dpubZF8BRmciAzYoTjXZ3bbRWLVCwUKtTquact3Tr6ye77Rgmw76VyqMb9TB9KpfrvUYEM5d1Au4fQzE2BbtxRjwzGsqnWHmtQP9UV1kxZaqvb6" testVec2MasterPubKey := "dpubZF4LSCdF9YKZfNzTVYhz4RBxsjYXqms8AQnMBHXZ8GUKoRSigG7kQnKiJt5pzk93Q8FxcdVBEkQZruSXduGtWnkwXzGnjbSovQ97dCxqaXc" tests := []struct { name string master string path []uint32 wantPub string }{ // Test vector 1 { name: "test vector 1 chain m", master: testVec1MasterPubKey, path: []uint32{}, wantPub: "dpubZF8BRmciAzYoTjXZ3bbRWLVCwUKtTquact3Tr6ye77Rgmw76VyqMb9TB9KpfrvUYEM5d1Au4fQzE2BbtxRjwzGsqnWHmtQP9UV1kxZaqvb6", }, { name: "test vector 1 chain m/0", master: testVec1MasterPubKey, path: []uint32{0}, wantPub: "dpubZHm6cmVU9pvfDCe3BY7iESzsEnV6xfi4DfoYvycnWLM9cryzKA84DqJ2CphYq6cfiEXgo9C3YLJA4ou81mavw9NDtNc3bLCWVqJz8Fx8qxB", }, { name: "test vector 1 chain m/0/1", master: testVec1MasterPubKey, path: []uint32{0, 1}, wantPub: "dpubZKtA6UTDuxeXV2PcYqoe68u7cgDhbTNbA4dUJoaAvfWzuCcRQCyG5S6dbpDZb2p3B5Y2XxLtD94Nemc8QRV4RspmvGwHvE2FZsfE5Pqpeor", }, { name: "test vector 1 chain m/0/1/2", master: testVec1MasterPubKey, path: []uint32{0, 1, 2}, wantPub: "dpubZMwLXm5dRVEJRvJHU8gNV7RwHeXMRRUnYFD4f6C8uNFfqksD1FCDARTwNPsQB3Pg4LuoKXkZbPnE6woUyedwNYVPvZToT5x4Kt6rs4GKa9c", }, { name: "test vector 1 chain m/0/1/2/2", master: testVec1MasterPubKey, path: []uint32{0, 1, 2, 2}, wantPub: "dpubZPfASfojwk6MhtAtkM6wPdQBr1ycVjoyqs3N51zR1keK6FcBhjBTtdW3Wn3kDLBZqgLnGozu8Gh3FV8GrFGpu3knmGVoF1Z6yGdqLU1Rz1S", }, { name: "test vector 1 chain m/0/1/2/2/1000000000", master: testVec1MasterPubKey, path: []uint32{0, 1, 2, 2, 1000000000}, wantPub: "dpubZR5Pf8cbUGikESevygwydenBaTsgcvoYnRSi7tygu23PxmVEG4GeMQj54oHFoPyRdt7Pg4sMad56yprQszbNyZVewaNEhDkn112C3mqB1fd", }, // Test vector 2 { name: "test vector 2 chain m", master: testVec2MasterPubKey, path: []uint32{}, wantPub: "dpubZF4LSCdF9YKZfNzTVYhz4RBxsjYXqms8AQnMBHXZ8GUKoRSigG7kQnKiJt5pzk93Q8FxcdVBEkQZruSXduGtWnkwXzGnjbSovQ97dCxqaXc", }, { name: "test vector 2 chain m/0", master: testVec2MasterPubKey, path: []uint32{0}, wantPub: "dpubZHJs2Z3PtHbbpaXQCi5wBKPhU8tC5ztBKUYBCYNGKk8eZ1EmBs3MhnLJbxHFMAahGnDnZT7qZxC7AXKP8PB6BDNUZgkG77moNMRmXyQ6s6s", }, { name: "test vector 2 chain m/0/2147483647", master: testVec2MasterPubKey, path: []uint32{0, 2147483647}, wantPub: "dpubZJgFEUcAZawGaLZdFEX6FfQBQVgU4bUC5qvDERUTD5dfcB2AQPnJ1dKp1R2DrAzC36BznZG43317s2oBJv3PuaZmA6HqmwMu6vNna4Gfumf", }, { name: "test vector 2 chain m/0/2147483647/1", master: testVec2MasterPubKey, path: []uint32{0, 2147483647, 1}, wantPub: "dpubZLbgtFNyjt3k2cJtg4a3dD2iXPKFTLgNKP8rLC1p5UE3AyfRHLTcYrZ6brg8eUmGvKRrXZ7A3XyVfwGxvYtjfz8514dUoJPkmSnBmC6qQK6", }, { name: "test vector 2 chain m/0/2147483647/1/2147483646", master: testVec2MasterPubKey, path: []uint32{0, 2147483647, 1, 2147483646}, wantPub: "dpubZNyWTupEG35S6d4uN93vWXpGxQuxtW9zuThQbnWpWTHwRCzxREqSSc9eDYivRGiZnEkEhPece5ciSoHtW6Khc729f6eAxjPnBgU38U9hgYw", }, { name: "test vector 2 chain m/0/2147483647/1/2147483646/2", master: testVec2MasterPubKey, path: []uint32{0, 2147483647, 1, 2147483646, 2}, wantPub: "dpubZRuRErXqhdJaZWD1AzXB6d5w2zw7UZ7ALxiS1gHbnQbVEohBzQzsVwGRzq97pmuE7ToA6DGn2QTH4DexxzdnMvkiYUpk8Nh2KEuYUM2RCeU", }, } 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 } } }
// 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 := "dprv3hCznBesA6jBucms1ZhyGeFfvJfBSwfs7ZFrxS8tdYzbjDZe2UwSaL7EbYo1qa88DmtyyG5cL9tdGxHkD89JmeZTbz5sVYU4Dgtijiio4Sc" testVec2MasterPrivKey := "dprv3hCznBesA6jBtPKJbQTxRZAKG2gyj8tZKEPaCsV4e9YYFBAgRP2eTSPAeu4r8dTMt9q51j2Vdt5zNqj7jbtovvocrP1qLj6WUTLF9xYQt4y" tests := []struct { name string master string path []uint32 wantPriv string }{ // Test vector 1 { name: "test vector 1 chain m", master: testVec1MasterPrivKey, path: []uint32{}, wantPriv: "dprv3hCznBesA6jBucms1ZhyGeFfvJfBSwfs7ZFrxS8tdYzbjDZe2UwSaL7EbYo1qa88DmtyyG5cL9tdGxHkD89JmeZTbz5sVYU4Dgtijiio4Sc", }, { name: "test vector 1 chain m/0", master: testVec1MasterPrivKey, path: []uint32{0}, wantPriv: "dprv3jFfEhxvVxy6NJWopujhfg7syQL71xCRgNoGUpQTtjTpCwzigwtCwssQGbRQsby7PBs1Yp8Wu7isu396qeNof13EZuxbCTJVF1xkoFAQHWj", }, { name: "test vector 1 chain m/0/1", master: testVec1MasterPrivKey, path: []uint32{0, 1}, wantPriv: "dprv3mWLns1v1fdLhxStaJHh3BqxmTi14RHHeWdNU6oU8sSkTDmAr54yK6La2APy3rAZr9ZJAdm5asTJaqBZ3vBYVSPHqyL8kbcCp5jgqfxBs4x", }, { name: "test vector 1 chain m/0/1/2", master: testVec1MasterPrivKey, path: []uint32{0, 1, 2}, wantPriv: "dprv3oDxSziXR1rQVWwWWBRKgCQU3vN6dnR3ekzHzvZRdgfVvrYSE35saJh8UdfSxCgtMn7pnbeMXWbyBbwxoncC9LMrnuH1AoJSB259c4XgmnN", }, { name: "test vector 1 chain m/0/1/2/2", master: testVec1MasterPrivKey, path: []uint32{0, 1, 2, 2}, wantPriv: "dprv3rYHNih25i8MqeRjhFq8mLnK4a3J63a9zkYTCFJd8kaJuNc5aDAAuG1XopkU7h93HvfbNvQaWdQLtwFmUEbDN3GCZ2Mxw6tq5ZSh8d1Chyw", }, { name: "test vector 1 chain m/0/1/2/2/1000000000", master: testVec1MasterPrivKey, path: []uint32{0, 1, 2, 2, 1000000000}, wantPriv: "dprv3tKkzgLFKaX2VcQTir9JeNHWxikiKokSJtdyj7sYoiDkU3np2rc3DGYPRVmDhb2FFaAk98fnqRotQYTVCRaoyAZiHaoyNoPCFeYA9pEshBT", }, // Test vector 2 { name: "test vector 2 chain m", master: testVec2MasterPrivKey, path: []uint32{}, wantPriv: "dprv3hCznBesA6jBtPKJbQTxRZAKG2gyj8tZKEPaCsV4e9YYFBAgRP2eTSPAeu4r8dTMt9q51j2Vdt5zNqj7jbtovvocrP1qLj6WUTLF9xYQt4y", }, { name: "test vector 2 chain m/0", master: testVec2MasterPrivKey, path: []uint32{0}, wantPriv: "dprv3jMy45BuuDETfxi59P8NTSjHPrNVq4wPRfLgRd57923L2hosj5NUEqiLYQ4i7fJtUpiXZLr2wUeToJY2Tm5sCpAJdajEHDmieVJiPQNXwu9", }, { name: "test vector 2 chain m/0/2147483647", master: testVec2MasterPrivKey, path: []uint32{0, 2147483647}, wantPriv: "dprv3mgHPRgAnNboAb9edL4RPscKYrNLG77BhPvFe3eiTGPSiigDeXct3WeiZ2QqRrm9TiseBuYWGEG79xkBzazpBGfym1vRXjcEo5KUi4rbhZ1", }, { name: "test vector 2 chain m/0/2147483647/1", master: testVec2MasterPrivKey, path: []uint32{0, 2147483647, 1}, wantPriv: "dprv3onqUUAjN1xQUAW1BzwiBa5wisDCE8hX8YcAEgVe1DPvbgHVLauDt2NsZPQX6tJs6ozcQSU9GdsffhueTbxTxFMPEMPxM2iHiooMz2oQHWS", }, { name: "test vector 2 chain m/0/2147483647/1/2147483646", master: testVec2MasterPrivKey, path: []uint32{0, 2147483647, 1, 2147483646}, wantPriv: "dprv3r8NTJgGzAjY5cU7sLo5rhT8o2wdco2iqjks6nJoiDACTucrPMcsciccv9skwGMX69uRa8EaZofskV7YyzBDbVi6v4RXbJ4DyeZ6JpUgdUi", }, { name: "test vector 2 chain m/0/2147483647/1/2147483646/2", master: testVec2MasterPrivKey, path: []uint32{0, 2147483647, 1, 2147483646, 2}, wantPriv: "dprv3sp4xvFP9mL9UUEddSZUxrtNnhe5UcHs5wrpxdZVEFCXoT4EpYeHZJjCDhvVEQFK2KfSHXFmew6MeBuvtrJfQv1BnkiSV7xxUji66uvWasp", }, // Custom tests to trigger specific conditions. { // Seed 000000000000000000000000000000da. name: "Derived privkey with zero high byte m/0", master: "dprv3jFfEhxvVxy6NJWopujhfg7syQL71xCRgNoGUpQTtjTpCwzigwtCwssQGbRQsby7PBs1Yp8Wu7isu396qeNof13EZuxbCTJVF1xkoFAQHWj", path: []uint32{0}, wantPriv: "dprv3mWLns1v1fdLfeu5DKTA6NWQHLF6pFsPSwKCS6q4h4nkjm2DfuH5X2iDnW15jhHTGa3rzxSpvskuXugcbBcUUVWCETKKzjW7ja4V2jL4aw4", }, } tests: for i, test := range tests { extKey, err := hdkeychain.NewKeyFromString(test.master) if err != nil { t.Errorf("NewKeyFromString #%d (%s): unexpected error "+ "creating extended key: %v", i, test.name, err) continue } for _, childNum := range test.path { var err error extKey, err = extKey.Child(childNum) if err != nil { t.Errorf("err: %v", err) continue tests } } privStr, _ := extKey.String() if privStr != test.wantPriv { t.Errorf("Child #%d (%s): mismatched serialized "+ "private extended key -- got: %s, want: %s", i, test.name, privStr, test.wantPriv) continue } } }
// 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, err := pubKey.String() if err != nil { return err } 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 { key, err := publicKey.String() if err != nil { return err } if key == 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 }
// 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 exteded key for the // master node is being hard coded here. master := "dprv3hCznBesA6jBushjx7y9NrfheE4ZshnaKYtsoLXefmLPzrXgEiXkd" + "RMD6UngnmBYZzgNhdEd4K3PidxcaCiR6HC9hmpj8FcrP4Cv7zBwELA" // 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 decred 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: DshMmJ3bfvMDdk1mkXRD3x5xDuPwSxoYGfi // Account 0 Internal Address 0: DsoTyktAyEDkYpgKSex6zx5rrkFDi2gAsHr }