Skip to content

Commit

Permalink
Added basic HD key generation
Browse files Browse the repository at this point in the history
  • Loading branch information
mrz1836 committed Oct 1, 2020
1 parent 7d4caf0 commit b118de3
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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


<details>
Expand Down
17 changes: 17 additions & 0 deletions examples/generate_hd_key/generate_hd_key.go
Original file line number Diff line number Diff line change
@@ -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)
}
57 changes: 57 additions & 0 deletions hd_key.go
Original file line number Diff line number Diff line change
@@ -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
}
125 changes: 125 additions & 0 deletions hd_key_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit b118de3

Please sign in to comment.