diff --git a/README.md b/README.md index 102c703..4701c60 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,10 @@ View the generated [documentation](https://pkg.go.dev/github.com/bitcoinschema/g - [Script from Address](script.go) - [WIF to PrivateKey](private_key.go) - [PrivateKey to WIF](private_key.go) +- [Generate HD Keys](hd_key.go) + +### ToDo +- Support `testnet` addresses & keys
diff --git a/examples/generate_hd_key/generate_hd_key.go b/examples/generate_hd_key/generate_hd_key.go new file mode 100644 index 0000000..a1e143a --- /dev/null +++ b/examples/generate_hd_key/generate_hd_key.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + + "github.com/bitcoinschema/go-bitcoin" +) + +func main() { + xPrivateKey, xPublicKey, err := bitcoin.GenerateHDKeyPair(bitcoin.SecureSeedLength) + if err != nil { + log.Fatalf("error occurred: %s", err.Error()) + } + + // Success! + log.Printf("xPrivateKey: %s \n xPublicKey: %s", xPrivateKey, xPublicKey) +} diff --git a/hd_key.go b/hd_key.go new file mode 100644 index 0000000..b5d5f54 --- /dev/null +++ b/hd_key.go @@ -0,0 +1,57 @@ +package bitcoin + +import ( + "github.com/bitcoinsv/bsvd/chaincfg" + "github.com/bitcoinsv/bsvutil/hdkeychain" +) + +const ( + // 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 = 64 // 512 bits +) + +// GenerateHDKey will create a new master node for use in creating a hierarchical deterministic key chain +func GenerateHDKey(seedLength uint8) (hdKey *hdkeychain.ExtendedKey, err error) { + + // Missing or invalid seed length + if seedLength == 0 { + seedLength = RecommendedSeedLength + } + + // Generate a new seed (added extra security from 256 to 512 bits for seed length) + var seed []byte + if seed, err = hdkeychain.GenerateSeed(seedLength); err != nil { + return + } + + // Generate a new master key + return hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) +} + +// GenerateHDKeyPair will generate a new xPub HD master node (xPrivateKey & xPublicKey) +func GenerateHDKeyPair(seedLength uint8) (xPrivateKey, xPublicKey string, err error) { + + // Generate an HD master key + var masterKey *hdkeychain.ExtendedKey + if masterKey, err = GenerateHDKey(seedLength); err != nil { + return + } + + // Set the xPriv + xPrivateKey = masterKey.String() + + // Create the extended public key + var pubKey *hdkeychain.ExtendedKey + if pubKey, err = masterKey.Neuter(); err != nil { + // Error should nearly never occur since it's using a safely derived masterKey + return + } + + // Set the actual xPub + xPublicKey = pubKey.String() + + return +} diff --git a/hd_key_test.go b/hd_key_test.go new file mode 100644 index 0000000..1db6fb4 --- /dev/null +++ b/hd_key_test.go @@ -0,0 +1,125 @@ +package bitcoin + +import ( + "fmt" + "testing" +) + +// TestGenerateHDKey will test the method GenerateHDKey() +func TestGenerateHDKey(t *testing.T) { + + t.Parallel() + + // Create the list of tests + var tests = []struct { + inputSeed uint8 + expectedNil bool + expectedError bool + }{ + {0, false, false}, + {1, true, true}, + {15, true, true}, + {65, true, true}, + {RecommendedSeedLength, false, false}, + {SecureSeedLength, false, false}, + } + + // Run tests + for _, test := range tests { + if hdKey, err := GenerateHDKey(test.inputSeed); err != nil && !test.expectedError { + t.Errorf("%s Failed: [%d] inputted and error not expected but got: %s", t.Name(), test.inputSeed, err.Error()) + } else if err == nil && test.expectedError { + t.Errorf("%s Failed: [%d] inputted and error was expected", t.Name(), test.inputSeed) + } else if hdKey == nil && !test.expectedNil { + t.Errorf("%s Failed: [%d] inputted and was nil but not expected", t.Name(), test.inputSeed) + } else if hdKey != nil && test.expectedNil { + t.Errorf("%s Failed: [%d] inputted and was NOT nil but expected to be nil", t.Name(), test.inputSeed) + } + } +} + +// ExampleGenerateHDKey example using GenerateHDKey() +func ExampleGenerateHDKey() { + hdKey, err := GenerateHDKey(SecureSeedLength) + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + // Cannot show the private/public key since they change each time + fmt.Printf("created HD key successfully! (length: %d)", len(hdKey.String())) + + // Output:created HD key successfully! (length: 111) +} + +// BenchmarkGenerateHDKey benchmarks the method GenerateHDKey() +func BenchmarkGenerateHDKey(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = GenerateHDKey(RecommendedSeedLength) + } +} + +// BenchmarkGenerateHDKeySecure benchmarks the method GenerateHDKey() +func BenchmarkGenerateHDKeySecure(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = GenerateHDKey(SecureSeedLength) + } +} + +// TestGenerateHDKeyPair will test the method GenerateHDKeyPair() +func TestGenerateHDKeyPair(t *testing.T) { + + t.Parallel() + + // Create the list of tests + var tests = []struct { + inputSeed uint8 + expectedError bool + }{ + {0, false}, + {1, true}, + {15, true}, + {65, true}, + {RecommendedSeedLength, false}, + {SecureSeedLength, false}, + } + + // Run tests + for _, test := range tests { + if privateKey, publicKey, err := GenerateHDKeyPair(test.inputSeed); err != nil && !test.expectedError { + t.Errorf("%s Failed: [%d] inputted and error not expected but got: %s", t.Name(), test.inputSeed, err.Error()) + } else if err == nil && test.expectedError { + t.Errorf("%s Failed: [%d] inputted and error was expected", t.Name(), test.inputSeed) + } else if err == nil && len(privateKey) == 0 { + t.Errorf("%s Failed: [%d] inputted and private key was empty", t.Name(), test.inputSeed) + } else if err == nil && len(publicKey) == 0 { + t.Errorf("%s Failed: [%d] inputted and pubic key was empty", t.Name(), test.inputSeed) + } + } +} + +// ExampleGenerateHDKeyPair example using GenerateHDKeyPair() +func ExampleGenerateHDKeyPair() { + xPrivateKey, xPublicKey, err := GenerateHDKeyPair(SecureSeedLength) + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + // Cannot show the private/public key since they change each time + fmt.Printf("created HD key successfully! (xPrivateKey length: %d) (xPublicKey length: %d)", len(xPrivateKey), len(xPublicKey)) + + // Output:created HD key successfully! (xPrivateKey length: 111) (xPublicKey length: 111) +} + +// BenchmarkGenerateHDKeyPair benchmarks the method GenerateHDKeyPair() +func BenchmarkGenerateHDKeyPair(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _ = GenerateHDKeyPair(RecommendedSeedLength) + } +} + +// BenchmarkGenerateHDKeyPairSecure benchmarks the method GenerateHDKeyPair() +func BenchmarkGenerateHDKeyPairSecure(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _ = GenerateHDKeyPair(SecureSeedLength) + } +}