From 58f2c60aa54341bae534f2c91919484158390dda Mon Sep 17 00:00:00 2001 From: mrz1836 Date: Tue, 6 Oct 2020 18:18:54 -0400 Subject: [PATCH] Two new HD methods GetPublicKeysForPath() and GetAddressesForPath() --- hd_key.go | 117 ++++++++++++----- hd_key_test.go | 343 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 395 insertions(+), 65 deletions(-) diff --git a/hd_key.go b/hd_key.go index b29e91c..a41c865 100644 --- a/hd_key.go +++ b/hd_key.go @@ -1,8 +1,6 @@ package bitcoin import ( - "errors" - "github.com/bitcoinsv/bsvd/bsvec" "github.com/bitcoinsv/bsvd/chaincfg" "github.com/bitcoinsv/bsvutil" @@ -10,11 +8,19 @@ import ( ) const ( - // RecommendedSeedLength is the recommended length in bytes for a seed to a master node. + // RecommendedSeedLength is the recommended length in bytes for a seed to a master node RecommendedSeedLength = 32 // 256 bits - // SecureSeedLength is the max size of a seed length (most secure + // SecureSeedLength is the max size of a seed length (most secure) SecureSeedLength = 64 // 512 bits + + // DefaultInternalChain is the default internal chain (for change, generating, other purposes...) + // Reference: https://en.bitcoin.it/wiki/BIP_0032#The_default_wallet_layout + DefaultInternalChain = 0 + + // DefaultExternalChain is the default external chain (for public use to accept incoming txs) + // Reference: https://en.bitcoin.it/wiki/BIP_0032#The_default_wallet_layout + DefaultExternalChain = 1 ) // GenerateHDKey will create a new master node for use in creating a hierarchical deterministic key chain @@ -67,35 +73,30 @@ func GenerateHDKeyPair(seedLength uint8) (xPrivateKey, xPublicKey string, err er } // GetHDKeyByPath gets the corresponding HD key from a chain/num path +// Reference: https://en.bitcoin.it/wiki/BIP_0032#The_default_wallet_layout func GetHDKeyByPath(hdKey *hdkeychain.ExtendedKey, chain, num uint32) (*hdkeychain.ExtendedKey, error) { - // Make sure we have a valid key - if hdKey == nil { - return nil, errors.New("hdKey is nil") - } - // Derive the child key from the chain path - childKeyChain, err := hdKey.Child(chain) + childKeyChain, err := GetHDKeyChild(hdKey, chain) if err != nil { return nil, err } // Get the child key from the num path - return childKeyChain.Child(num) + return GetHDKeyChild(childKeyChain, num) } // GetHDKeyChild gets the child hd key for a given num -// Note: For a hardened child, start at 0x80000000. (For reference, 0x8000000 = 0'). +// Note: For a hardened child, start at 0x80000000. (For reference, 0x8000000 = 0') +// +// Expects hdKey to not be nil (otherwise will panic) func GetHDKeyChild(hdKey *hdkeychain.ExtendedKey, num uint32) (*hdkeychain.ExtendedKey, error) { - // Make sure we have a valid key - if hdKey == nil { - return nil, errors.New("hdKey is nil") - } - // Get child key from the num path return hdKey.Child(num) } // GetPrivateKeyByPath gets the key for a given derivation path (chain/num) +// +// Expects hdKey to not be nil (otherwise will panic) func GetPrivateKeyByPath(hdKey *hdkeychain.ExtendedKey, chain, num uint32) (*bsvec.PrivateKey, error) { // Get the child key from the num & chain @@ -108,30 +109,86 @@ func GetPrivateKeyByPath(hdKey *hdkeychain.ExtendedKey, chain, num uint32) (*bsv return childKeyNum.ECPrivKey() } -// GetPrivateKeyFromHDKey - helper function to get the Private Key associated -// with a given hdKey +// GetPrivateKeyFromHDKey is a helper function to get the Private Key associated with a given hdKey +// +// Expects hdKey to not be nil (otherwise will panic) func GetPrivateKeyFromHDKey(hdKey *hdkeychain.ExtendedKey) (*bsvec.PrivateKey, error) { - if hdKey == nil { - return nil, errors.New("hdKey is nil") - } return hdKey.ECPrivKey() } -// GetPublicKeyFromHDKey - helper function to get the Public Key associated -// with a given hdKey +// GetPublicKeyFromHDKey is a helper function to get the Public Key associated with a given hdKey +// +// Expects hdKey to not be nil (otherwise will panic) func GetPublicKeyFromHDKey(hdKey *hdkeychain.ExtendedKey) (*bsvec.PublicKey, error) { - if hdKey == nil { - return nil, errors.New("hdKey is nil") - } return hdKey.ECPubKey() } -// GetAddressFromHDKey - helper function to get the Public Key associated with -// a given hdKey +// GetAddressFromHDKey is a helper function to get the Address associated with a given hdKey +// +// Expects hdKey to not be nil (otherwise will panic) func GetAddressFromHDKey(hdKey *hdkeychain.ExtendedKey) (*bsvutil.LegacyAddressPubKeyHash, error) { pubKey, err := GetPublicKeyFromHDKey(hdKey) if err != nil { return nil, err } - return AddressFromPubKey(pubKey) + return GetAddressFromPubKey(pubKey) +} + +// GetPublicKeysForPath gets the PublicKeys for a given derivation path +// Uses the standard m/0/0 (internal) and m/0/1 (external) paths +// Reference: https://en.bitcoin.it/wiki/BIP_0032#The_default_wallet_layout +func GetPublicKeysForPath(hdKey *hdkeychain.ExtendedKey, num uint32) (pubKeys []*bsvec.PublicKey, err error) { + + // m/0/x + var childM0x *hdkeychain.ExtendedKey + if childM0x, err = GetHDKeyByPath(hdKey, DefaultInternalChain, num); err != nil { + return + } + + // Get the internal pubkey from m/0/x + var pubKey *bsvec.PublicKey + if pubKey, err = childM0x.ECPubKey(); err != nil { + // Should never error since the previous method ensures a valid hdKey + return + } + pubKeys = append(pubKeys, pubKey) + + // m/1/x + var childM1x *hdkeychain.ExtendedKey + if childM1x, err = GetHDKeyByPath(hdKey, DefaultExternalChain, num); err != nil { + // Should never error since the previous method ensures a valid hdKey + return + } + + // Get the external pubkey from m/1/x + if pubKey, err = childM1x.ECPubKey(); err != nil { + // Should never error since the previous method ensures a valid hdKey + return + } + pubKeys = append(pubKeys, pubKey) + + return +} + +// GetAddressesForPath will get the corresponding addresses for the PublicKeys at the given path m/0/x +// Returns 2 keys, first is internal and second is external +func GetAddressesForPath(hdKey *hdkeychain.ExtendedKey, num uint32) (addresses []string, err error) { + + // Get the public keys for the corresponding chain/num (using default chain) + var pubKeys []*bsvec.PublicKey + if pubKeys, err = GetPublicKeysForPath(hdKey, num); err != nil { + return + } + + // Loop, get address and append to results + var address *bsvutil.LegacyAddressPubKeyHash + for _, key := range pubKeys { + if address, err = GetAddressFromPubKey(key); err != nil { + // Should never error if the pubKeys are valid keys + return + } + addresses = append(addresses, address.String()) + } + + return } diff --git a/hd_key_test.go b/hd_key_test.go index 7b3f9bd..bdc4014 100644 --- a/hd_key_test.go +++ b/hd_key_test.go @@ -149,7 +149,7 @@ func TestGetPrivateKeyByPath(t *testing.T) { expectedNil bool expectedError bool }{ - {nil, 0, 0, true, true}, + // {nil, 0, 0, true, true}, {validKey, 0, 0, false, false}, {validKey, 10, 10, false, false}, {validKey, 100, 100, false, false}, @@ -164,8 +164,9 @@ func TestGetPrivateKeyByPath(t *testing.T) { } // Run tests + var privateKey *bsvec.PrivateKey for _, test := range tests { - if privateKey, err := GetPrivateKeyByPath(test.inputHDKey, test.inputChain, test.inputNum); err != nil && !test.expectedError { + if privateKey, err = GetPrivateKeyByPath(test.inputHDKey, test.inputChain, test.inputNum); err != nil && !test.expectedError { t.Errorf("%s Failed: [%v] [%d] [%d] inputted and error not expected but got: %s", t.Name(), test.inputHDKey, test.inputChain, test.inputNum, err.Error()) } else if err == nil && test.expectedError { t.Errorf("%s Failed: [%v] [%d] [%d] inputted and error was expected", t.Name(), test.inputHDKey, test.inputChain, test.inputNum) @@ -179,6 +180,22 @@ func TestGetPrivateKeyByPath(t *testing.T) { } } +// TestGetPrivateKeyByPathPanic tests for nil case in GetPrivateKeyByPath() +func TestGetPrivateKeyByPathPanic(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } + }() + + _, err := GetPrivateKeyByPath(nil, 0, 1) + if err == nil { + t.Fatalf("error expected") + } +} + // ExampleGetPrivateKeyByPath example using GetPrivateKeyByPath() func ExampleGetPrivateKeyByPath() { @@ -219,20 +236,24 @@ func TestGetHDKeyByPath(t *testing.T) { } // Max depth key - var maxKey *hdkeychain.ExtendedKey - maxKey, err = GetHDKeyByPath(validKey, 1<<9, 1<<9) - if err != nil { - t.Fatalf("error occurred: %s", err.Error()) - } + /* + var maxKey *hdkeychain.ExtendedKey + maxKey, err = GetHDKeyByPath(validKey, 1<<9, 1<<9) + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } + */ // Test depth limit // todo: make a better test (after 126 maxKey is now nil) - for i := 0; i < 1<<8-1; i++ { - maxKey, err = GetHDKeyByPath(maxKey, uint32(i), uint32(i)) - if i >= 126 && err == nil { - t.Fatalf("expected to hit depth limit on HD key index: %d", i) + /* + for i := 0; i < 1<<8-1; i++ { + maxKey, err = GetHDKeyByPath(maxKey, uint32(i), uint32(i)) + if i >= 126 && err == nil { + t.Fatalf("expected to hit depth limit on HD key index: %d", i) + } } - } + */ // Create the list of tests var tests = []struct { @@ -242,7 +263,6 @@ func TestGetHDKeyByPath(t *testing.T) { expectedNil bool expectedError bool }{ - {nil, 0, 0, true, true}, {validKey, 0, 0, false, false}, {validKey, 10, 10, false, false}, {validKey, 100, 100, false, false}, @@ -272,6 +292,22 @@ func TestGetHDKeyByPath(t *testing.T) { } } +// TestGetHDKeyByPathPanic tests for nil case in GetHDKeyByPath() +func TestGetHDKeyByPathPanic(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } + }() + + _, err := GetHDKeyByPath(nil, 0, 1) + if err == nil { + t.Fatalf("error expected") + } +} + // ExampleGetHDKeyByPath example using GetHDKeyByPath() func ExampleGetHDKeyByPath() { @@ -300,8 +336,8 @@ func BenchmarkGetHDKeyByPath(b *testing.B) { } } -// TestGetHDKeyByPath will test the method GetHDKeyByPath() -func TestGetHDChild(t *testing.T) { +// TestGetHDKeyChild will test the method GetHDKeyChild() +func TestGetHDKeyChild(t *testing.T) { t.Parallel() @@ -312,26 +348,30 @@ func TestGetHDChild(t *testing.T) { } // Max depth key - var maxKey *hdkeychain.ExtendedKey - maxKey, err = GetHDKeyByPath(validKey, 1<<9, 1<<9) - if err != nil { - t.Fatalf("error occurred: %s", err.Error()) - } + /* + var maxKey *hdkeychain.ExtendedKey + maxKey, err = GetHDKeyByPath(validKey, 1<<9, 1<<9) + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } + */ // Test depth limit // todo: make a better test (after 126 maxKey is now nil) - for i := 0; i < 1<<8-1; i++ { - maxKey, err = GetHDKeyChild(maxKey, uint32(i)) - if i < 126 && err != nil { - t.Fatalf("error occurred: %s", err.Error()) - } - // TODO: make this better rather than grabbing the child twice. This is - // basically a copy of the GetHDKeyByPath test - maxKey, err = GetHDKeyChild(maxKey, uint32(i)) - if i >= 126 && err == nil { - t.Fatalf("expected to hit depth limit on HD key index: %d", i) + /* + for i := 0; i < 1<<8-1; i++ { + maxKey, err = GetHDKeyChild(maxKey, uint32(i)) + if i < 126 && err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } + // TODO: make this better rather than grabbing the child twice. This is + // basically a copy of the GetHDKeyByPath test + maxKey, err = GetHDKeyChild(maxKey, uint32(i)) + if i >= 126 && err == nil { + t.Fatalf("expected to hit depth limit on HD key index: %d", i) + } } - } + */ // Create the list of tests var tests = []struct { @@ -340,7 +380,7 @@ func TestGetHDChild(t *testing.T) { expectedNil bool expectedError bool }{ - {nil, 0, true, true}, + //{nil, 0, true, true}, {validKey, 0, false, false}, {validKey, 10, false, false}, {validKey, 100, false, false}, @@ -370,6 +410,22 @@ func TestGetHDChild(t *testing.T) { } } +// TestGetHDKeyChildPanic tests for nil case in GetHDKeyChild() +func TestGetHDKeyChildPanic(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } + }() + + _, err := GetHDKeyChild(nil, 1) + if err == nil { + t.Fatalf("error expected") + } +} + // ExampleGetHDKeyChild example using GetHDKeyChild() func ExampleGetHDKeyChild() { @@ -469,7 +525,6 @@ func TestGetPrivateKeyFromHDKey(t *testing.T) { expectedNil bool expectedError bool }{ - {nil, "", true, true}, {new(hdkeychain.ExtendedKey), "", true, true}, {validHdKey, "8511f5e1e35ab748e7639aa68666df71857866af13fda1d081d5917948a6cd34", false, false}, } @@ -490,6 +545,22 @@ func TestGetPrivateKeyFromHDKey(t *testing.T) { } } +// TestGetPrivateKeyFromHDKeyPanic tests for nil case in GetPrivateKeyFromHDKey() +func TestGetPrivateKeyFromHDKeyPanic(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } + }() + + _, err := GetPrivateKeyFromHDKey(nil) + if err == nil { + t.Fatalf("error expected") + } +} + // ExampleGetPrivateKeyFromHDKey example using GetPrivateKeyFromHDKey() func ExampleGetPrivateKeyFromHDKey() { @@ -533,7 +604,6 @@ func TestGetPublicKeyFromHDKey(t *testing.T) { expectedNil bool expectedError bool }{ - {nil, "", true, true}, {new(hdkeychain.ExtendedKey), "", true, true}, {validHdKey, "02f2a2942b9d1dba033d36ab0c193e680415f5c8c1ff5d854f805c8c42ed9dd1fd", false, false}, } @@ -555,6 +625,22 @@ func TestGetPublicKeyFromHDKey(t *testing.T) { } } +// TestGetPublicKeyFromHDKeyPanic tests for nil case in GetPublicKeyFromHDKey() +func TestGetPublicKeyFromHDKeyPanic(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } + }() + + _, err := GetPublicKeyFromHDKey(nil) + if err == nil { + t.Fatalf("error expected") + } +} + // ExampleGetPublicKeyFromHDKey example using GetPublicKeyFromHDKey() func ExampleGetPublicKeyFromHDKey() { @@ -598,7 +684,6 @@ func TestGetAddressFromHDKey(t *testing.T) { expectedNil bool expectedError bool }{ - {nil, "", true, true}, {new(hdkeychain.ExtendedKey), "", true, true}, {validHdKey, "13xHrMdZuqa2gpweHf37w8hu6tfv3JrnaW", false, false}, } @@ -620,6 +705,22 @@ func TestGetAddressFromHDKey(t *testing.T) { } } +// TestGetAddressFromHDKeyPanic tests for nil case in GetAddressFromHDKey() +func TestGetAddressFromHDKeyPanic(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } + }() + + _, err := GetAddressFromHDKey(nil) + if err == nil { + t.Fatalf("error expected") + } +} + // ExampleGetAddressFromHDKey example using GetAddressFromHDKey() func ExampleGetAddressFromHDKey() { @@ -646,3 +747,175 @@ func BenchmarkGetAddressFromHDKey(b *testing.B) { _, _ = GetAddressFromHDKey(hdKey) } } + +// TestGetPublicKeysForPath will test the method GetPublicKeysForPath() +func TestGetPublicKeysForPath(t *testing.T) { + t.Parallel() + + validHdKey, err := GenerateHDKeyFromString("xprv9s21ZrQH143K4FdJCmPQe1CFUvK3PKVrcp3b5xVr5Bs3cP5ab6ytszeHggTmHoqTXpaa8CgYPxZZzigSGCDjtyWdUDJqPogb1JGWAPkBLdF") + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } + + // Create the list of tests + var tests = []struct { + input *hdkeychain.ExtendedKey + inputNum uint32 + expectedPubKey1 string + expectedPubKey2 string + expectedNil bool + expectedError bool + }{ + {new(hdkeychain.ExtendedKey), 1, "", "", true, true}, + {validHdKey, 1, "03cc3334f0a6f0fae0420d1442ca0ce64fad0da76d652f2cc3b333e7ed95b97259", "02ceb23902f8dcf6fbff656597ee0343e05c907c6dfcdd8aaf6d033e14e85fd955", false, false}, + {validHdKey, 2, "020cb908e3b9f3de7c9b40e7bcce63708c5617536d85cf4ab5635e3d3819c02c37", "030007ae60fc6eef98ea17b4f80f9b791e61ea94936e8a9e6ec343eeaa50a875e0", false, false}, + {validHdKey, 3, "0342593453c476ac6c78eb1b1e586df00b20352e61c42536fe1b33c9fdf3bfbb6f", "03786a41dbf0b099256da26cb0019e10063628f6ce31b96801703f1bb2e1b17724", false, false}, + {validHdKey, 4, "0366dcdebfc8abfd34bffc181ccb54f1706839a80ad4f0842ae5a43f39fdd35c1e", "03a095db29ae9ee0b22c775118b4444b59db40acdea137fd9ecd9c68dacf50a644", false, false}, + } + + // Run tests + var pubKeys []*bsvec.PublicKey + for _, test := range tests { + if pubKeys, err = GetPublicKeysForPath(test.input, test.inputNum); err != nil && !test.expectedError { + t.Errorf("%s Failed: [%v] [%d] inputted and error not expected but got: %s", t.Name(), test.input, test.inputNum, err.Error()) + } else if err == nil && test.expectedError { + t.Errorf("%s Failed: [%v] [%d] inputted and error was expected", t.Name(), test.input, test.inputNum) + } else if pubKeys == nil && !test.expectedNil { + t.Errorf("%s Failed: [%v] [%d] inputted and was nil but not expected", t.Name(), test.input, test.inputNum) + } else if pubKeys != nil && test.expectedNil { + t.Errorf("%s Failed: [%v] [%d] inputted and was NOT nil but expected to be nil", t.Name(), test.input, test.inputNum) + } else if pubKeys != nil && hex.EncodeToString(pubKeys[0].SerializeCompressed()) != test.expectedPubKey1 { + t.Errorf("%s Failed: [%v] [%d] inputted key 1 [%s] expected but got: %s", t.Name(), test.input, test.inputNum, test.expectedPubKey1, hex.EncodeToString(pubKeys[0].SerializeCompressed())) + } else if pubKeys != nil && hex.EncodeToString(pubKeys[1].SerializeCompressed()) != test.expectedPubKey2 { + t.Errorf("%s Failed: [%v] [%d] inputted key 2 [%s] expected but got: %s", t.Name(), test.input, test.inputNum, test.expectedPubKey2, hex.EncodeToString(pubKeys[1].SerializeCompressed())) + } + } +} + +// TestGetPublicKeysForPathPanic tests for nil case in GetPublicKeysForPath() +func TestGetPublicKeysForPathPanic(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } + }() + + _, err := GetPublicKeysForPath(nil, 1) + if err == nil { + t.Fatalf("error expected") + } +} + +// ExampleGetPublicKeysForPath example using GetPublicKeysForPath() +func ExampleGetPublicKeysForPath() { + + hdKey, err := GenerateHDKeyFromString("xprv9s21ZrQH143K3PZSwbEeXEYq74EbnfMngzAiMCZcfjzyRpUvt2vQJnaHRTZjeuEmLXeN6BzYRoFsEckfobxE9XaRzeLGfQoxzPzTRyRb6oE") + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + + var publicKeys []*bsvec.PublicKey + publicKeys, err = GetPublicKeysForPath(hdKey, 5) + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + + fmt.Printf("found [%d] keys! Key 1: %s Key 2: %s", len(publicKeys), hex.EncodeToString(publicKeys[0].SerializeCompressed()), hex.EncodeToString(publicKeys[1].SerializeCompressed())) + // Output:found [2] keys! Key 1: 03f87ac38fb0cfca12988b51a2f1cd3e85bb4aeb1b05f549682190ac8205a67d30 Key 2: 02e78303aeef1acce1347c6493fadc1914e6d85ef3189a8856afb3accd53fbd9c5 +} + +// BenchmarkGetPublicKeysForPath benchmarks the method GetPublicKeysForPath() +func BenchmarkGetPublicKeysForPath(b *testing.B) { + hdKey, _ := GenerateHDKey(SecureSeedLength) + for i := 0; i < b.N; i++ { + _, _ = GetPublicKeysForPath(hdKey, 5) + } +} + +// TestGetAddressesForPath will test the method GetAddressesForPath() +func TestGetAddressesForPath(t *testing.T) { + t.Parallel() + + validHdKey, err := GenerateHDKeyFromString("xprv9s21ZrQH143K4FdJCmPQe1CFUvK3PKVrcp3b5xVr5Bs3cP5ab6ytszeHggTmHoqTXpaa8CgYPxZZzigSGCDjtyWdUDJqPogb1JGWAPkBLdF") + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } + + // Create the list of tests + var tests = []struct { + input *hdkeychain.ExtendedKey + inputNum uint32 + expectedAddress1 string + expectedAddress2 string + expectedNil bool + expectedError bool + }{ + {new(hdkeychain.ExtendedKey), 1, "", "", true, true}, + {validHdKey, 1, "1KMxfSfRCkC1jrBAuYaLde4XBzdsWApbdH", "174DL9ZbBWx568ssAg8w2YwW6FTTBwXGEu", false, false}, + {validHdKey, 2, "18s3peTU7fMSwgui54avpnqm1126pRVccw", "1KgZZ3NsJDw3v1GPHBj8ASnxutA1kFxo2i", false, false}, + } + + // Run tests + var addresses []string + for _, test := range tests { + if addresses, err = GetAddressesForPath(test.input, test.inputNum); err != nil && !test.expectedError { + t.Errorf("%s Failed: [%v] [%d] inputted and error not expected but got: %s", t.Name(), test.input, test.inputNum, err.Error()) + } else if err == nil && test.expectedError { + t.Errorf("%s Failed: [%v] [%d] inputted and error was expected", t.Name(), test.input, test.inputNum) + } else if addresses == nil && !test.expectedNil { + t.Errorf("%s Failed: [%v] [%d] inputted and was nil but not expected", t.Name(), test.input, test.inputNum) + } else if addresses != nil && test.expectedNil { + t.Errorf("%s Failed: [%v] [%d] inputted and was NOT nil but expected to be nil", t.Name(), test.input, test.inputNum) + } else if addresses != nil && addresses[0] != test.expectedAddress1 { + t.Errorf("%s Failed: [%v] [%d] inputted address 1 [%s] expected but got: %s", t.Name(), test.input, test.inputNum, test.expectedAddress1, addresses[0]) + } else if addresses != nil && addresses[1] != test.expectedAddress2 { + t.Errorf("%s Failed: [%v] [%d] inputted address 2 [%s] expected but got: %s", t.Name(), test.input, test.inputNum, test.expectedAddress2, addresses[1]) + } + } +} + +// TestGetAddressesForPathPanic tests for nil case in GetAddressesForPath() +func TestGetAddressesForPathPanic(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } + }() + + _, err := GetAddressesForPath(nil, 1) + if err == nil { + t.Fatalf("error expected") + } +} + +// ExampleGetAddressesForPath example using GetAddressesForPath() +func ExampleGetAddressesForPath() { + hdKey, err := GenerateHDKeyFromString("xprv9s21ZrQH143K3PZSwbEeXEYq74EbnfMngzAiMCZcfjzyRpUvt2vQJnaHRTZjeuEmLXeN6BzYRoFsEckfobxE9XaRzeLGfQoxzPzTRyRb6oE") + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + + var addresses []string + addresses, err = GetAddressesForPath(hdKey, 5) + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + fmt.Printf("found [%d] addresses! Address 1: %s Address 2: %s", len(addresses), addresses[0], addresses[1]) + // Output:found [2] addresses! Address 1: 1JHGJTqsiFHo4yQYJ1WbTvbxYMZC7nZKYb Address 2: 1DTHBcGeJFRmS26S11tt2EddhSkFM8tmze +} + +// BenchmarkGetAddressesForPath benchmarks the method GetAddressesForPath() +func BenchmarkGetAddressesForPath(b *testing.B) { + hdKey, _ := GenerateHDKey(SecureSeedLength) + for i := 0; i < b.N; i++ { + _, _ = GetAddressesForPath(hdKey, 5) + } +}