forked from Wollac/iota-crypto-demo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
path.go
107 lines (93 loc) · 3.04 KB
/
path.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/*
Package bip32path provides utilities for BIP-0032 chains.
These chains define an iterative way to successively derive child keys from the
extended master key. A BIP-0032 path refers to the shortened notion of such
derivation and they correspond to a list of child indices where an index >= 2^31
is used to describe hardened child derivation.
This package supports several of the most commonly used path notation formats.
See the documentation of ParsePath for more info.
*/
package bip32path
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
// ErrInvalidPathFormat is returned when a path string could not be parsed due to a different general structure.
var ErrInvalidPathFormat = errors.New("invalid path format")
// hardened denotes the first hardened index.
const hardened uint32 = 1 << 31
// keyReg is the regular expression for a single key.
var keyReg = regexp.MustCompile(`(\d+)([H']?)`) // any number of digits plus an optional H or '
// A Path is a BIP-32 key derivation path, a slice of uint32.
type Path []uint32
// ParsePath parses s as a BIP-32 path, returning the result.
// The string s can be in the form where the apostrophe means hardened key ("m/44'/0'/0'/0/0")
// or where "H" means hardened key ("m/44H/0H/0H/0/0"). The "m/" prefix is mandatory.
func ParsePath(s string) (Path, error) {
if s == "" || s == "m" {
return Path{}, nil
}
// remove master prefix if present
s = strings.TrimPrefix(s, "m/")
//nolint:prealloc
var path []uint32
for i, key := range strings.Split(s, "/") {
matches := keyReg.FindStringSubmatch(key)
// check whether the entire key matches and there is a digit
if len(matches) < 2 || matches[0] != key {
return nil, fmt.Errorf("invalid key %d: %w", i, ErrInvalidPathFormat)
}
// parse the digits
v, err := parseUint31(matches[1])
if err != nil {
return nil, fmt.Errorf("invalid key %d: %w", i, err)
}
// the key is hardened if the second capture group was matched
if len(matches) > 2 && len(matches[2]) > 0 {
v |= hardened
}
path = append(path, v)
}
return path, nil
}
// String returns the string form of the BIP-32 path.
// It returns:
// - "m" for an empty path
// - apostrophe for hardened keys ("m/44'/0'/0'/0/0").
func (p Path) String() string {
var builder strings.Builder
builder.WriteByte('m')
for _, idx := range p {
builder.WriteString(fmt.Sprintf("/%d", idx&^hardened))
if idx >= hardened {
builder.WriteByte('\'')
}
}
return builder.String()
}
// MarshalText implements the encoding.TextMarshaler interface.
// The encoding is the same as returned by String.
func (p Path) MarshalText() ([]byte, error) {
return []byte(p.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The BIP-32 path is expected in a form accepted by ParsePath.
func (p *Path) UnmarshalText(text []byte) (err error) {
s := string(text)
x, err := ParsePath(s)
if err != nil {
return err
}
*p = x
return nil
}
func parseUint31(s string) (uint32, error) {
n, err := strconv.ParseUint(s, 0, 31)
if err != nil {
return 0, err
}
return uint32(n), nil
}