-
Notifications
You must be signed in to change notification settings - Fork 81
/
keypath.go
209 lines (181 loc) · 5.99 KB
/
keypath.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// Copyright 2018 Shift Devices AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package signing
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/digitalbitbox/bitbox-wallet-app/util/errp"
)
const (
hardenedKeySymbol = "'"
// Hardened denotes a hardened key derivation.
Hardened = true
// NonHardened denotes a non-hardened key derivation.
NonHardened = false
)
// This type is called node because keyNode is marked by the linter as a misspelling.
type keyNode struct {
index uint32
hardened bool
}
func (node keyNode) encode() string {
suffix := ""
if node.hardened {
suffix = hardenedKeySymbol
}
return fmt.Sprint(node.index, suffix)
}
type keypath []keyNode
func newKeypath(input string) (keypath, error) {
splits := strings.Split(input, "/")
path := make(keypath, 0, len(splits))
for _, split := range splits {
split = strings.TrimSpace(split)
if len(split) == 0 {
continue
}
hardened := strings.HasSuffix(split, hardenedKeySymbol)
if hardened {
split = strings.TrimSpace(split[:len(split)-len(hardenedKeySymbol)])
}
index, err := strconv.Atoi(split)
if err != nil {
return nil, errp.Wrap(err, "A path node is not a number.")
}
if index < 0 {
return nil, errp.New("A path index may not be negative.")
}
path = append(path, keyNode{uint32(index), hardened})
}
return path, nil
}
func (path keypath) encode() string {
nodes := make([]string, len(path))
for index, node := range path {
nodes[index] = node.encode()
}
return strings.Join(nodes, "/")
}
func (path keypath) derive(extendedKey *hdkeychain.ExtendedKey) (*hdkeychain.ExtendedKey, error) {
for _, node := range path {
offset := uint32(0)
if node.hardened {
offset = hdkeychain.HardenedKeyStart
}
var err error
extendedKey, err = extendedKey.Child(offset + node.index)
if err != nil {
return nil, err
}
}
return extendedKey, nil
}
// RelativeKeypath models a relative keypath according to BIP32.
type RelativeKeypath keypath
// NewEmptyRelativeKeypath creates a new empty relative keypath.
func NewEmptyRelativeKeypath() RelativeKeypath {
return RelativeKeypath{}
}
// NewRelativeKeypath creates a new relative keypath from a string like `1/2'/3`.
func NewRelativeKeypath(input string) (RelativeKeypath, error) {
input = strings.TrimSpace(input)
if strings.HasPrefix(input, "m") {
return nil, errp.New("A relative keypath may not start with 'm'.")
}
path, err := newKeypath(input)
if err != nil {
return nil, err
}
return RelativeKeypath(path), nil
}
// Encode encodes the relative keypath as a string.
func (relativeKeypath RelativeKeypath) Encode() string {
return keypath(relativeKeypath).encode()
}
// Child appends the given node to this relative keypath.
func (relativeKeypath RelativeKeypath) Child(index uint32, hardened bool) RelativeKeypath {
newKeypath := append(RelativeKeypath{}, relativeKeypath...)
return append(newKeypath, keyNode{index, hardened})
}
// Hardened returns whether the keypath contains a hardened derivation.
func (relativeKeypath RelativeKeypath) Hardened() bool {
for _, node := range relativeKeypath {
if node.hardened {
return true
}
}
return false
}
// Derive derives the extended key at this path from the given extended key.
func (relativeKeypath RelativeKeypath) Derive(
extendedKey *hdkeychain.ExtendedKey,
) (*hdkeychain.ExtendedKey, error) {
return keypath(relativeKeypath).derive(extendedKey)
}
// AbsoluteKeypath models an absolute keypath according to BIP32.
type AbsoluteKeypath keypath
// NewEmptyAbsoluteKeypath creates a new empty absolute keypath.
func NewEmptyAbsoluteKeypath() AbsoluteKeypath {
return AbsoluteKeypath{}
}
// NewAbsoluteKeypath creates a new absolute keypath from a string like `m/44'/1'`.
func NewAbsoluteKeypath(input string) (AbsoluteKeypath, error) {
input = strings.TrimSpace(input)
if !strings.HasPrefix(input, "m") {
return nil, errp.New("An absolute keypath has to start with 'm'.")
}
input = input[strings.Index(input, "/")+1:]
path, err := newKeypath(input)
if err != nil {
return nil, err
}
return AbsoluteKeypath(path), nil
}
// Encode encodes the absolute keypath as a string.
func (absoluteKeypath AbsoluteKeypath) Encode() string {
return "m/" + keypath(absoluteKeypath).encode()
}
// Child appends the given node to this absolute keypath.
func (absoluteKeypath AbsoluteKeypath) Child(index uint32, hardened bool) AbsoluteKeypath {
newKeypath := append(AbsoluteKeypath{}, absoluteKeypath...)
return append(newKeypath, keyNode{index, hardened})
}
// Append appends a relative keypath to this absolute keypath.
func (absoluteKeypath AbsoluteKeypath) Append(suffix RelativeKeypath) AbsoluteKeypath {
newKeypath := append(AbsoluteKeypath{}, absoluteKeypath...)
return append(newKeypath, suffix...)
}
// Derive derives the extended key at this path from the given extended key.
func (absoluteKeypath AbsoluteKeypath) Derive(
extendedKey *hdkeychain.ExtendedKey,
) (*hdkeychain.ExtendedKey, error) {
return keypath(absoluteKeypath).derive(extendedKey)
}
// MarshalJSON implements json.Marshaler.
func (absoluteKeypath AbsoluteKeypath) MarshalJSON() ([]byte, error) {
return json.Marshal(absoluteKeypath.Encode())
}
// UnmarshalJSON implements json.Unmarshaler.
func (absoluteKeypath *AbsoluteKeypath) UnmarshalJSON(bytes []byte) error {
var input string
if err := json.Unmarshal(bytes, &input); err != nil {
return errp.Wrap(err, "Could not unmarshal an absolute keypath.")
}
var err error
*absoluteKeypath, err = NewAbsoluteKeypath(input)
return err
}