Skip to content

Commit e4daab4

Browse files
authored
Allow multiple hdPath derivation during generation (#4)
* Allow multiple hdPath derivation during generation
1 parent 7e43eb5 commit e4daab4

File tree

6 files changed

+85
-72
lines changed

6 files changed

+85
-72
lines changed

cmd/extkey/commands/decode.go

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,41 @@ import (
1111
)
1212

1313
var CmdDecode = &cobra.Command{
14-
Use: "decode",
15-
Args: cobra.ExactArgs(1),
14+
Use: "decode [xprv..|xpub..]",
15+
Short: "Decode an xprv/xpub extended key",
16+
Args: cobra.ExactArgs(1),
1617
RunE: func(cmd *cobra.Command, args []string) error {
18+
hrp, err := cmd.PersistentFlags().GetString("hrp")
19+
if err != nil {
20+
return err
21+
}
1722
formatter, err := formatize(strings.TrimSpace(cmd.Flag("format").Value.String()))
1823
if err != nil {
1924
return err
2025
}
2126
extkey := strings.TrimSpace(args[0])
22-
return decode(extkey, os.Stdout, formatter)
27+
return decode(hrp, extkey, os.Stdout, formatter)
2328
},
2429
}
2530

2631
func init() {
2732
addFlags(CmdDecode, flagHRP, flagFormat)
2833
}
2934

30-
func decode(xkey string, w io.Writer, formatter Formatter) error {
35+
func decode(hrp, xkey string, w io.Writer, formatter Formatter) error {
3136
key, err := bip32.B58Deserialize(xkey)
3237
if err != nil {
3338
return err
3439
}
3540

3641
info := decodedKeyInfo{}
3742
info.Address = toAddress(hrp, key)
38-
info.XKey.Depth = key.Depth
39-
info.XKey.DepthLoc = depthString(key.Depth)
40-
info.XKey.Chaincode = fmt.Sprintf("%X", key.ChainCode)
41-
info.XKey.Fingerprint = fmt.Sprintf("%X", key.FingerPrint)
42-
43-
if key.IsPrivate {
44-
info.XKey.Private = key.B58Serialize()
45-
info.XKey.Public = key.PublicKey().B58Serialize()
46-
} else {
47-
info.XKey.Public = key.B58Serialize()
43+
info.XKey = SomeKey{
44+
Seed: "",
45+
Mnemonic: "",
46+
Hrp: hrp,
47+
RootKey: NewExtKeyData(key, hrp, ""),
48+
ChildKeys: nil,
4849
}
4950

5051
output, err := formatter(info)
@@ -56,13 +57,6 @@ func decode(xkey string, w io.Writer, formatter Formatter) error {
5657
}
5758

5859
type decodedKeyInfo struct {
59-
Address string `json:"address" yaml:"address"`
60-
XKey struct {
61-
Private string `json:"private,omitempty" yaml:"private,omitempty"`
62-
Public string `json:"public" yaml:"public"`
63-
Depth byte `json:"depth" yaml:"depth"`
64-
DepthLoc string `json:"depthLoc" yaml:"depthLoc"`
65-
Chaincode string `json:"chaincode" yaml:"chaincode"`
66-
Fingerprint string `json:"fingerprint" yaml:"fingerprint"`
67-
} `json:"xkey" yaml:"xkey"`
60+
Address string `json:"address" yaml:"address"`
61+
XKey SomeKey `json:"xkey" yaml:"xkey"`
6862
}

cmd/extkey/commands/encode.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import (
1313
)
1414

1515
var CmdEncode = &cobra.Command{
16-
Use: "encode",
16+
Use: "encode",
17+
Short: "Output an xprv/xpub from a mnemonic, passphrase, hd-path, and hrp",
1718
RunE: func(cmd *cobra.Command, args []string) error {
1819
format, err := formatize(cmd.Flag("format").Value.String())
1920
if err != nil {
@@ -23,17 +24,20 @@ var CmdEncode = &cobra.Command{
2324
if pHrp == "" {
2425
return fmt.Errorf("hrp is required")
2526
}
26-
hdPath := strings.TrimSpace(cmd.Flag("hd-path").Value.String())
27+
hdPaths, err := cmd.PersistentFlags().GetStringArray("hd-path")
28+
if err != nil {
29+
return err
30+
}
2731
seedB64 := strings.TrimSpace(cmd.Flag("seed").Value.String())
28-
return encode(hdPath, format, os.Stdout, pHrp, seedB64)
32+
return encode(hdPaths, format, os.Stdout, pHrp, seedB64)
2933
},
3034
}
3135

3236
func init() {
3337
addFlags(CmdEncode, flagFormat, flagHDPath, flagHRP, flagSeed)
3438
}
3539

36-
func encode(path string, formatter Formatter, w io.Writer, hrp, seedB64 string) error {
40+
func encode(paths []string, formatter Formatter, w io.Writer, hrp, seedB64 string) error {
3741
var seed []byte
3842
var err error
3943
if seedB64 == "" {
@@ -65,15 +69,15 @@ func encode(path string, formatter Formatter, w io.Writer, hrp, seedB64 string)
6569
Seed: base64.URLEncoding.EncodeToString(seed),
6670
Mnemonic: "",
6771
Hrp: hrp,
68-
RootKey: NewExtKeyData(rootKey, hrp),
72+
RootKey: NewExtKeyData(rootKey, hrp, ""),
6973
}
70-
if hdPath != "" {
74+
for _, path := range paths {
7175
var childKey *bip32.Key
7276
childKey, err = DeriveChildKey(rootKey, path)
7377
if err != nil {
7478
return err
7579
}
76-
key.ChildKey = NewExtKeyData(childKey, hrp)
80+
key.ChildKeys = append(key.ChildKeys, NewExtKeyData(childKey, hrp, path))
7781
}
7882
output, err := formatter(key)
7983
if err != nil {

cmd/extkey/commands/flags.go

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,37 +18,31 @@ func addFlags(cmd *cobra.Command, opts ...func(cmd *cobra.Command)) {
1818
}
1919
}
2020

21-
var format string
22-
var hdPath string
23-
var hrp string
24-
var seed string
25-
var laddr string
26-
var mnemonic string
27-
2821
var flagHRP = func(cmd *cobra.Command) {
29-
cmd.PersistentFlags().StringVar(&hrp, "hrp", "", "Human readable prefix")
22+
cmd.PersistentFlags().String("hrp", "", "Human readable prefix")
3023
_ = cmd.MarkFlagRequired("hrp")
3124
}
3225

3326
var flagFormat = func(cmd *cobra.Command) {
34-
cmd.PersistentFlags().StringVar(&format, "format", "", "The format out output the keys [json|yaml|plain]")
27+
cmd.PersistentFlags().String("format", "", "The format out output the keys [json|yaml|plain]")
3528
}
3629

3730
var flagHDPath = func(cmd *cobra.Command) {
38-
cmd.PersistentFlags().StringVar(&hdPath, "hd-path", "", "The bip44 hd path used to derive the extended Key")
31+
cmd.PersistentFlags().StringArray("hd-path", []string{}, "The bip44 hd path used to derive the extended Key")
3932
}
4033

4134
var flagSeed = func(cmd *cobra.Command) {
42-
cmd.PersistentFlags().StringVar(&seed, "seed", "", "The base64 url encoded seed to use for the key derivation")
35+
cmd.PersistentFlags().String("seed", "", "The base64 url encoded seed to use for the key derivation")
4336
}
4437

4538
var flagLAddr = func(cmd *cobra.Command) {
46-
cmd.PersistentFlags().StringVar(&laddr, "laddr", "0.0.0.0:9000", "The address:port to listen on")
39+
cmd.PersistentFlags().String("laddr", "0.0.0.0:9000", "The address:port to listen on")
4740
}
4841

49-
// var flagMnemonic = func(cmd *cobra.Command) {
50-
// cmd.PersistentFlags().StringVar(&mnemonic, "mnemonic", "", "The mnemonic to use to generate the seed")
51-
// }
42+
// nolint
43+
var flagMnemonic = func(cmd *cobra.Command) {
44+
cmd.PersistentFlags().String("mnemonic", "", "The mnemonic to use to generate the seed")
45+
}
5246

5347
func formatize(format string) (Formatter, error) {
5448
switch format {

cmd/extkey/commands/generate.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ var CmdGenerate = &cobra.Command{
1919
if hrp == "" {
2020
return fmt.Errorf("--hrp is required")
2121
}
22-
hdPath := strings.TrimSpace(cmd.Flag("hd-path").Value.String())
23-
if hdPath == "" {
22+
hdPaths, err := cmd.PersistentFlags().GetStringArray("hd-path")
23+
if err != nil {
24+
return err
25+
}
26+
if len(hdPaths) == 0 {
2427
return fmt.Errorf("--hd-path is required")
2528
}
2629
var seed string
@@ -32,15 +35,15 @@ var CmdGenerate = &cobra.Command{
3235
if err != nil {
3336
return err
3437
}
35-
return generate(hrp, hdPath, seed, formatter, os.Stdout)
38+
return generate(hrp, seed, hdPaths, formatter, os.Stdout)
3639
},
3740
}
3841

3942
func init() {
4043
addFlags(CmdGenerate, flagHRP, flagFormat, flagHDPath, flagSeed)
4144
}
4245

43-
func generate(hrp, hdPath, seed string, formatter Formatter, w io.Writer) error {
46+
func generate(hrp, seed string, paths []string, formatter Formatter, w io.Writer) error {
4447
var seedBz []byte
4548
var err error
4649
if seed != "" {
@@ -49,7 +52,7 @@ func generate(hrp, hdPath, seed string, formatter Formatter, w io.Writer) error
4952
return err
5053
}
5154
}
52-
key, err := GenerateExtKey(hrp, hdPath, seedBz)
55+
key, err := GenerateExtKey(hrp, paths, seedBz)
5356
if err != nil {
5457
return err
5558
}
@@ -61,9 +64,10 @@ func generate(hrp, hdPath, seed string, formatter Formatter, w io.Writer) error
6164
return nil
6265
}
6366

64-
func GenerateExtKey(hrp, hdPath string, seedBz []byte) (SomeKey, error) {
67+
func GenerateExtKey(hrp string, paths []string, seedBz []byte) (SomeKey, error) {
6568
var seed []byte
6669
var err error
70+
var mnemonic string
6771
if seedBz == nil {
6872
var entropy []byte
6973
entropy, err = bip39.NewEntropy(256)
@@ -87,14 +91,14 @@ func GenerateExtKey(hrp, hdPath string, seedBz []byte) (SomeKey, error) {
8791
Hrp: hrp,
8892
Seed: base64.URLEncoding.EncodeToString(seed),
8993
Mnemonic: mnemonic,
90-
RootKey: NewExtKeyData(rootKey, hrp),
94+
RootKey: NewExtKeyData(rootKey, hrp, ""),
9195
}
92-
if hdPath != "" {
93-
childKey, err := DeriveChildKey(rootKey, hdPath)
96+
for _, path := range paths {
97+
childKey, err := DeriveChildKey(rootKey, path)
9498
if err != nil {
9599
return SomeKey{}, err
96100
}
97-
key.ChildKey = NewExtKeyData(childKey, hrp)
101+
key.ChildKeys = append(key.ChildKeys, NewExtKeyData(childKey, hrp, path))
98102
}
99103
return key, nil
100104
}

cmd/extkey/commands/keys.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,22 @@ func DeriveChildKey(parentKey *bip32.Key, path string) (*bip32.Key, error) {
2626
}
2727

2828
type SomeKey struct {
29-
Seed string `json:"seed,omitempty" yaml:"seed,omitempty"`
30-
Mnemonic string `json:"mnemonic,omitempty" yaml:"mnemonic,omitempty"`
31-
Hrp string `json:"hrp,omitempty" yaml:"hrp,omitempty"`
32-
RootKey *ExtKeyData `json:"rootKey,omitempty" yaml:"rootKey,omitempty"`
33-
ChildKey *ExtKeyData `json:"childKey,omitempty" yaml:"childKey,omitempty"`
29+
Seed string `json:"seed,omitempty" yaml:"seed,omitempty"`
30+
Mnemonic string `json:"mnemonic,omitempty" yaml:"mnemonic,omitempty"`
31+
Hrp string `json:"hrp,omitempty" yaml:"hrp,omitempty"`
32+
RootKey *ExtKeyData `json:"rootKey,omitempty" yaml:"rootKey,omitempty"`
33+
ChildKeys []*ExtKeyData `json:"childKey,omitempty" yaml:"childKey,omitempty"`
3434
}
3535

3636
type ExtKeyData struct {
37-
Address string `json:"address" yaml:"address"`
38-
PrivateKey InnerKeyData `json:"privateKey" yaml:"privateKey"`
39-
PublicKey InnerKeyData `json:"publicKey" yaml:"publicKey"`
37+
Address string `json:"address" yaml:"address"`
38+
Path string `json:"path,omitempty" yaml:"path,omitempty"`
39+
PrivateKey InnerKeyData `json:"privateKey" yaml:"privateKey"`
40+
PublicKey InnerKeyData `json:"publicKey" yaml:"publicKey"`
41+
Depth byte `json:"depth" yaml:"depth"`
42+
DepthLoc string `json:"depthLoc" yaml:"depthLoc"`
43+
Chaincode string `json:"chaincode" yaml:"chaincode"`
44+
Fingerprint string `json:"fingerprint" yaml:"fingerprint"`
4045
}
4146

4247
type InnerKeyData struct {
@@ -54,10 +59,18 @@ func NewInnerKeyData(key *bip32.Key) InnerKeyData {
5459
}
5560
}
5661

57-
func NewExtKeyData(key *bip32.Key, hrp string) *ExtKeyData {
58-
return &ExtKeyData{
59-
Address: toAddress(hrp, key),
60-
PrivateKey: NewInnerKeyData(key),
61-
PublicKey: NewInnerKeyData(key.PublicKey()),
62+
func NewExtKeyData(key *bip32.Key, hrp, path string) *ExtKeyData {
63+
data := &ExtKeyData{
64+
Address: toAddress(hrp, key),
65+
Path: path,
66+
PublicKey: NewInnerKeyData(key.PublicKey()),
67+
Depth: key.Depth,
68+
DepthLoc: depthString(key.Depth),
69+
Chaincode: fmt.Sprintf("%X", key.ChainCode),
70+
Fingerprint: fmt.Sprintf("%X", key.FingerPrint),
6271
}
72+
if key.IsPrivate {
73+
data.PrivateKey = NewInnerKeyData(key)
74+
}
75+
return data
6376
}

cmd/extkey/commands/serve.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import (
1010
var CmdServe = &cobra.Command{
1111
Use: "serve",
1212
RunE: func(cmd *cobra.Command, args []string) error {
13-
return server()
13+
laddr, err := cmd.PersistentFlags().GetString("laddr")
14+
if err != nil {
15+
return err
16+
}
17+
return server(laddr)
1418
},
1519
}
1620

@@ -50,8 +54,8 @@ func generateKeys(c *gin.Context) {
5054
}
5155
}
5256

53-
hdPath := c.Query("hdPath")
54-
key, err := GenerateExtKey(hrp, hdPath, seedBz)
57+
hdPaths := c.Request.URL.Query()["hdPath"]
58+
key, err := GenerateExtKey(hrp, hdPaths, seedBz)
5559
if err != nil {
5660
_ = c.AbortWithError(505, err)
5761
return
@@ -60,7 +64,7 @@ func generateKeys(c *gin.Context) {
6064
c.JSON(200, key)
6165
}
6266

63-
func server() error {
67+
func server(laddr string) error {
6468
router := newRouter()
6569
err := router.Run(laddr)
6670
if err != nil {

0 commit comments

Comments
 (0)