-
Notifications
You must be signed in to change notification settings - Fork 80
/
keys.go
174 lines (161 loc) · 4.26 KB
/
keys.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
package spvwallet
import (
"github.com/OpenBazaar/wallet-interface"
"github.com/btcsuite/btcd/chaincfg"
hd "github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/goleveldb/leveldb/errors"
)
const LOOKAHEADWINDOW = 100
type KeyManager struct {
datastore wallet.Keys
params *chaincfg.Params
internalKey *hd.ExtendedKey
externalKey *hd.ExtendedKey
}
func NewKeyManager(db wallet.Keys, params *chaincfg.Params, masterPrivKey *hd.ExtendedKey) (*KeyManager, error) {
internal, external, err := Bip44Derivation(masterPrivKey)
if err != nil {
return nil, err
}
km := &KeyManager{
datastore: db,
params: params,
internalKey: internal,
externalKey: external,
}
if err := km.lookahead(); err != nil {
return nil, err
}
return km, nil
}
// m / purpose' / coin_type' / account' / change / address_index
func Bip44Derivation(masterPrivKey *hd.ExtendedKey) (internal, external *hd.ExtendedKey, err error) {
// Purpose = bip44
fourtyFour, err := masterPrivKey.Child(hd.HardenedKeyStart + 44)
if err != nil {
return nil, nil, err
}
// Cointype = bitcoin
bitcoin, err := fourtyFour.Child(hd.HardenedKeyStart + 0)
if err != nil {
return nil, nil, err
}
// Account = 0
account, err := bitcoin.Child(hd.HardenedKeyStart + 0)
if err != nil {
return nil, nil, err
}
// Change(0) = external
external, err = account.Child(0)
if err != nil {
return nil, nil, err
}
// Change(1) = internal
internal, err = account.Child(1)
if err != nil {
return nil, nil, err
}
return internal, external, nil
}
func (km *KeyManager) GetCurrentKey(purpose wallet.KeyPurpose) (*hd.ExtendedKey, error) {
i, err := km.datastore.GetUnused(purpose)
if err != nil {
return nil, err
}
if len(i) == 0 {
return nil, errors.New("No unused keys in database")
}
return km.generateChildKey(purpose, uint32(i[0]))
}
func (km *KeyManager) GetFreshKey(purpose wallet.KeyPurpose) (*hd.ExtendedKey, error) {
index, _, err := km.datastore.GetLastKeyIndex(purpose)
var childKey *hd.ExtendedKey
if err != nil {
index = 0
} else {
index += 1
}
for {
// There is a small possibility bip32 keys can be invalid. The procedure in such cases
// is to discard the key and derive the next one. This loop will continue until a valid key
// is derived.
childKey, err = km.generateChildKey(purpose, uint32(index))
if err == nil {
break
}
index += 1
}
addr, err := childKey.Address(km.params)
if err != nil {
return nil, err
}
p := wallet.KeyPath{wallet.KeyPurpose(purpose), index}
err = km.datastore.Put(addr.ScriptAddress(), p)
if err != nil {
return nil, err
}
return childKey, nil
}
func (km *KeyManager) GetKeys() []*hd.ExtendedKey {
var keys []*hd.ExtendedKey
keyPaths, err := km.datastore.GetAll()
if err != nil {
return keys
}
for _, path := range keyPaths {
k, err := km.generateChildKey(path.Purpose, uint32(path.Index))
if err != nil {
continue
}
keys = append(keys, k)
}
return keys
}
func (km *KeyManager) GetKeyForScript(scriptAddress []byte) (*hd.ExtendedKey, error) {
keyPath, err := km.datastore.GetPathForKey(scriptAddress)
if err != nil {
key, err := km.datastore.GetKey(scriptAddress)
if err != nil {
return nil, err
}
hdKey := hd.NewExtendedKey(
km.params.HDPrivateKeyID[:],
key.Serialize(),
make([]byte, 32),
[]byte{0x00, 0x00, 0x00, 0x00},
0,
0,
true)
return hdKey, nil
}
return km.generateChildKey(keyPath.Purpose, uint32(keyPath.Index))
}
// Mark the given key as used and extend the lookahead window
func (km *KeyManager) MarkKeyAsUsed(scriptAddress []byte) error {
if err := km.datastore.MarkKeyAsUsed(scriptAddress); err != nil {
return err
}
return km.lookahead()
}
func (km *KeyManager) generateChildKey(purpose wallet.KeyPurpose, index uint32) (*hd.ExtendedKey, error) {
if purpose == wallet.EXTERNAL {
return km.externalKey.Child(index)
} else if purpose == wallet.INTERNAL {
return km.internalKey.Child(index)
}
return nil, errors.New("Unknown key purpose")
}
func (km *KeyManager) lookahead() error {
lookaheadWindows := km.datastore.GetLookaheadWindows()
for purpose, size := range lookaheadWindows {
if size < LOOKAHEADWINDOW {
for i := 0; i < (LOOKAHEADWINDOW - size); i++ {
_, err := km.GetFreshKey(purpose)
if err != nil {
return err
}
}
}
}
return nil
}