Skip to content

Instantly share code, notes, and snippets.

@islishude
Created October 17, 2019 05:33
Show Gist options
  • Save islishude/25459c23e2128c1c21581b3b6b90a8b4 to your computer and use it in GitHub Desktop.
Save islishude/25459c23e2128c1c21581b3b6b90a8b4 to your computer and use it in GitHub Desktop.
package hdkey
import (
"errors"
"strconv"
"strings"
"github.com/btcsuite/btcutil/hdkeychain"
)
// DeriveFromPath derives extends key from bip32 path
func DerivePath(xprv *hdkeychain.ExtendedKey, path string) (*hdkeychain.ExtendedKey, error) {
const (
bip32MasterSymbol = `m`
bip32HardenedSymbol = `'`
bip32PathDelimiter = `/`
)
splitPath := strings.Split(path, bip32PathDelimiter)
if splitPath[0] == bip32MasterSymbol {
if xprv.ParentFingerprint() > 0 {
return nil, errors.New("Derive private key from pubkey")
}
if len(splitPath) == 1 {
return xprv, nil
}
splitPath = splitPath[1:]
}
next := xprv
for _, item := range splitPath {
if item == "" {
continue
}
var err error
var index uint64
lastIndex := len(item) - 1
if string(item[lastIndex]) == bip32HardenedSymbol {
index, err = strconv.ParseUint(item[:lastIndex], 10, 32)
index += hdkeychain.HardenedKeyStart
} else {
index, err = strconv.ParseUint(item, 10, 32)
}
if err != nil {
return nil, errors.New("Not a positive integer")
}
next, err = next.Child(uint32(index))
if err != nil {
return nil, err
}
}
return next, nil
}
package hdkey
import (
"reflect"
"testing"
"github.com/btcsuite/btcutil/hdkeychain"
)
func TestDerivePath(t *testing.T) {
var mustFromSeed = func(seed string) *hdkeychain.ExtendedKey {
res, err := hdkeychain.NewKeyFromString(seed)
if err != nil {
t.Errorf("Decode %s failed", seed)
return nil
}
return res
}
type args struct {
xprv *hdkeychain.ExtendedKey
path string
}
tests := []struct {
name string
args args
want *hdkeychain.ExtendedKey
wantErr bool
}{
{
name: "valid",
args: args{
xprv: mustFromSeed("xprv9s21ZrQH143K2PunsF7pUyYnUBzJLRkg9RNYhVDRCGqJpKkAys6tH1LA49zw9t4jA61oh5AA6jCyXWTXbbLti6QSELWiTvcUTwKZcG4V8iA"),
path: "m/44'/0'/0'/0",
},
want: mustFromSeed("xprv9zc7gcUh94ThWj8kU4zkR3E8nKb5B7aNGYMW7ho3DqVdVfgfF2LZWUU2ykXLirVKxsjrC64AmopY23gZMV7t2gK582JWnpz8HVD73iPXD9P"),
wantErr: false,
},
{
name: "has redundant backslash",
args: args{
xprv: mustFromSeed("xprv9s21ZrQH143K2PunsF7pUyYnUBzJLRkg9RNYhVDRCGqJpKkAys6tH1LA49zw9t4jA61oh5AA6jCyXWTXbbLti6QSELWiTvcUTwKZcG4V8iA"),
path: "m/44'/0'/0'/0/",
},
want: mustFromSeed("xprv9zc7gcUh94ThWj8kU4zkR3E8nKb5B7aNGYMW7ho3DqVdVfgfF2LZWUU2ykXLirVKxsjrC64AmopY23gZMV7t2gK582JWnpz8HVD73iPXD9P"),
wantErr: false,
},
{
name: "only master symbol",
args: args{
xprv: mustFromSeed("xprv9s21ZrQH143K2PunsF7pUyYnUBzJLRkg9RNYhVDRCGqJpKkAys6tH1LA49zw9t4jA61oh5AA6jCyXWTXbbLti6QSELWiTvcUTwKZcG4V8iA"),
path: "m",
},
want: mustFromSeed("xprv9s21ZrQH143K2PunsF7pUyYnUBzJLRkg9RNYhVDRCGqJpKkAys6tH1LA49zw9t4jA61oh5AA6jCyXWTXbbLti6QSELWiTvcUTwKZcG4V8iA"),
wantErr: false,
},
{
name: "ErrDeriveNotMaster",
args: args{
xprv: mustFromSeed("xprvA19DxK9gkX44JVprp97UZEfVNCs587r8M6715rRaVkiy5azYHjDrJxMqeD8EWeSVupVz9NycS57gE17URk3TCNTdyMMuWw2VrXUg83iRfHN"),
path: "m/44'/0'/0'/0",
},
want: nil,
wantErr: true,
},
{
name: "invalid numberic",
args: args{
xprv: mustFromSeed("xprv9s21ZrQH143K3E3GR69HdDJZ3B6ncLa4sCdGeFJe4EoF1rnQSxmsAtqdQyBF1vZGga5VpCVjUZ6sba8RMihUzYVppAQiPAotbCkE6bmPGPF"),
path: "abc/abc/abc/efg",
},
want: nil,
wantErr: true,
},
{
name: "derive privkey from pubkey",
args: args{
xprv: mustFromSeed("xpub6FJ1fCip99xH43PykfeBhSwR2rh5WQtX8Tt3XSh7fWLZ71Rw1RNqtCLBPS2oGzWUoL4aBGPah2b92uV4E8wqUtp4HF17ZkWe3Yh8SMYhGYq"),
path: `0'/0'/1`,
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := DerivePath(tt.args.xprv, tt.args.path)
if (err != nil) != tt.wantErr {
t.Errorf("DerivePath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("DerivePath() = %v, want %v", got, tt.want)
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment