forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 24
/
keychain.go
135 lines (111 loc) · 4.03 KB
/
keychain.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
package channeldb
import (
"bytes"
"errors"
"github.com/decred/dcrlnd/kvdb"
)
const (
// lastUsableKeyFamily is the last key family index that can be stored
// by the database. This value matches the last account number that can
// be used to create an account in HD wallets, assuming accounts are
// created as hardened branches.
lastUsableKeyFamily = 0x7fffffff
// lastUsableFamilyIndex is the last index that can be returned by a
// given key family.
lastUsableKeyFamilyIndex = 0x7fffffff
)
var (
// errInvalidKeyFamily is returned when an invalid key family is
// requested.
errInvalidKeyFamily = errors.New("invalid key family")
// errKeyFamilyExchausted is returned when a given keyfamily has
// generated enough indexes that no more can be generated.
errKeyFamilyExhausted = errors.New("keyfamily indexes exhausted")
// errDifferentAccountID is returned when the account ID provided to
// CompareAndStoreAccountID is not the same as the one stored in the
// database.
errDifferentAccountID = errors.New("account ID is different than stored in the database")
// keychainBucket is the root bucket used to store keychain/keyring
// data.
keychainBucket = []byte("keychain")
// keyFamilyIndexesBucket is the bucket used to store the current index
// of each requested key famiy.
//
// Keys are byte-ordered uint32 slices, and values are byte-ordered
// uint32 values that represent the last returned index for a family.
keyFamilyIndexesBucket = []byte("kfidxs")
// keyAccountIDBucket is the bucket used to store the identifier of the
// account previously used with this keychain. By convention, this is
// the first pubkey of the first keyfamily of the keychain/account.
keyAccountIDBucket = []byte("acctid")
)
// NextFamilyIndex returns the next index for a given family of keys from the
// database-backed keyring.
//
// A _KeyFamily_ is an uint32 that maps to the key families of the keychain
// package, while the returned index can be considered the index of a
// (possibly) unused key.
//
// Repeated calls to NextKeyFamilyIndex will return different values. This
// function errors if the requested family would create an invalid HD extended
// key or if it the key family has been exhausted and no more keys can be
// generated for it.
func (d *DB) NextKeyFamilyIndex(keyFamily uint32) (uint32, error) {
var index uint32
// Key families higher than this limit would cause a numeric overflow
// due to accounts using hardened HD branches.
if keyFamily > lastUsableKeyFamily {
return 0, errInvalidKeyFamily
}
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
keychain, err := tx.CreateTopLevelBucket(keychainBucket)
if err != nil {
return err
}
keyFamilies, err := keychain.CreateBucketIfNotExists(
keyFamilyIndexesBucket,
)
if err != nil {
return err
}
// Attempt to read the existing value for the given family.
var k [4]byte
var v [4]byte
byteOrder.PutUint32(k[:], keyFamily)
oldv := keyFamilies.Get(k[:])
// If there is a value, decode it to get the next usable index.
if len(oldv) == 4 {
index = byteOrder.Uint32(oldv)
// If we've passed the usable range for this keyfamily,
// return an error.
if index >= lastUsableKeyFamilyIndex {
return errKeyFamilyExhausted
}
}
// Update the database with the next usable index.
byteOrder.PutUint32(v[:], index+1)
keyFamilies.Put(k[:], v[:])
return nil
}, func() {})
return index, err
}
// CompareAndStoreAccountID attempts to compare an existing account ID to a
// given parameter if the ID exists in the database and returns an error if the
// IDs don't match. If the database is empty, then the ID is stored.
func (d *DB) CompareAndStoreAccountID(id []byte) error {
return kvdb.Update(d, func(tx kvdb.RwTx) error {
keychain, err := tx.CreateTopLevelBucket(keychainBucket)
if err != nil {
return err
}
acctId := keychain.Get(keyAccountIDBucket)
if acctId == nil {
keychain.Put(keyAccountIDBucket, id)
return nil
}
if !bytes.Equal(acctId, id) {
return errDifferentAccountID
}
return nil
}, func() {})
}