Created
October 17, 2019 05:33
-
-
Save islishude/25459c23e2128c1c21581b3b6b90a8b4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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