OSDN Git Service

Add passwords and Remove xprv cache (#267)
[bytom/bytom.git] / blockchain / pseudohsm / keycache.go
1 package pseudohsm
2
3 import (
4         "bufio"
5         "encoding/hex"
6         "encoding/json"
7         "fmt"
8         "io/ioutil"
9         "os"
10         "path/filepath"
11         "sort"
12         "strings"
13         "sync"
14         "time"
15
16         "github.com/bytom/crypto/ed25519/chainkd"
17 )
18
19 // Minimum amount of time between cache reloads. This limit applies if the platform does
20 // not support change notifications. It also applies if the keystore directory does not
21 // exist yet, the code will attempt to create a watcher at most this often.
22 const minReloadInterval = 2 * time.Second
23
24 type keysByFile []XPub
25
26 func (s keysByFile) Len() int           { return len(s) }
27 func (s keysByFile) Less(i, j int) bool { return s[i].File < s[j].File }
28 func (s keysByFile) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
29
30 // AmbiguousKeyError is returned when attempting to unlock
31 // an XPub for which more than one file exists.
32 type AmbiguousKeyError struct {
33         Pubkey  string
34         Matches []XPub
35 }
36
37 func (err *AmbiguousKeyError) Error() string {
38         files := ""
39         for i, a := range err.Matches {
40                 files += a.File
41                 if i < len(err.Matches)-1 {
42                         files += ", "
43                 }
44         }
45         return fmt.Sprintf("multiple keys match keys (%s)", files)
46 }
47
48 // keyCache is a live index of all keys in the keystore.
49 type keyCache struct {
50         keydir   string
51         watcher  *watcher
52         mu       sync.Mutex
53         all      keysByFile
54         byPubs   map[chainkd.XPub][]XPub
55         throttle *time.Timer
56 }
57
58 func newKeyCache(keydir string) *keyCache {
59         kc := &keyCache{
60                 keydir: keydir,
61                 byPubs: make(map[chainkd.XPub][]XPub),
62         }
63         kc.watcher = newWatcher(kc)
64         return kc
65 }
66
67 func (kc *keyCache) hasKey(xpub chainkd.XPub) bool {
68         kc.maybeReload()
69         kc.mu.Lock()
70         defer kc.mu.Unlock()
71         return len(kc.byPubs[xpub]) > 0
72 }
73
74 func (kc *keyCache) hasAlias(alias string) bool {
75         xpubs := kc.keys()
76         for _, xpub := range xpubs {
77                 if xpub.Alias == alias {
78                         return true
79                 }
80         }
81         return false
82 }
83
84 func (kc *keyCache) add(newKey XPub) {
85         kc.mu.Lock()
86         defer kc.mu.Unlock()
87
88         i := sort.Search(len(kc.all), func(i int) bool { return kc.all[i].File >= newKey.File })
89         if i < len(kc.all) && kc.all[i] == newKey {
90                 return
91         }
92         // newKey is not in the cache.
93         kc.all = append(kc.all, XPub{})
94         copy(kc.all[i+1:], kc.all[i:])
95         kc.all[i] = newKey
96         kc.byPubs[newKey.XPub] = append(kc.byPubs[newKey.XPub], newKey)
97 }
98
99 func (kc *keyCache) keys() []XPub {
100         kc.maybeReload()
101         kc.mu.Lock()
102         defer kc.mu.Unlock()
103         cpy := make([]XPub, len(kc.all))
104         copy(cpy, kc.all)
105         return cpy
106 }
107
108 func (kc *keyCache) maybeReload() {
109         kc.mu.Lock()
110         defer kc.mu.Unlock()
111
112         if kc.watcher.running {
113                 return // A watcher is running and will keep the cache up-to-date.
114         }
115
116         if kc.throttle == nil {
117                 kc.throttle = time.NewTimer(0)
118         } else {
119                 select {
120                 case <-kc.throttle.C:
121                 default:
122                         return // The cache was reloaded recently.
123                 }
124         }
125         kc.watcher.start()
126         kc.reload()
127         kc.throttle.Reset(minReloadInterval)
128 }
129
130 // find returns the cached keys for alias if there is a unique match.
131 // The exact matching rules are explained by the documentation of Account.
132 // Callers must hold ac.mu.
133 func (kc *keyCache) find(xpub XPub) (XPub, error) {
134         // Limit search to xpub candidates if possible.
135         matches := kc.all
136         if (xpub.XPub != chainkd.XPub{}) {
137                 matches = kc.byPubs[xpub.XPub]
138         }
139         if xpub.File != "" {
140                 // If only the basename is specified, complete the path.
141                 if !strings.ContainsRune(xpub.File, filepath.Separator) {
142                         xpub.File = filepath.Join(kc.keydir, xpub.File)
143                 }
144                 for i := range matches {
145                         if matches[i].File == xpub.File {
146                                 return matches[i], nil
147                         }
148                 }
149                 if (xpub.XPub == chainkd.XPub{}) {
150                         return XPub{}, ErrLoadKey
151                 }
152         }
153         switch len(matches) {
154         case 1:
155                 return matches[0], nil
156         case 0:
157                 return XPub{}, ErrLoadKey
158         default:
159                 err := &AmbiguousKeyError{Pubkey: hex.EncodeToString(xpub.XPub[:]), Matches: make([]XPub, len(matches))}
160                 copy(err.Matches, matches)
161                 return XPub{}, err
162         }
163 }
164
165 // reload caches addresses of existing key.
166 // Callers must hold ac.mu.
167 func (kc *keyCache) reload() {
168         keys, err := kc.scan()
169         if err != nil {
170                 fmt.Printf("can't load keys: %v\n", err.Error())
171         }
172         kc.all = keys
173         sort.Sort(kc.all)
174         for k := range kc.byPubs {
175                 delete(kc.byPubs, k)
176         }
177         for _, k := range keys {
178                 kc.byPubs[k.XPub] = append(kc.byPubs[k.XPub], k)
179         }
180         fmt.Printf("reloaded keys, cache has %d keys\n", len(kc.all))
181 }
182
183 func (kc *keyCache) scan() ([]XPub, error) {
184         files, err := ioutil.ReadDir(kc.keydir)
185         if err != nil {
186                 return nil, err
187         }
188         var (
189                 buf     = new(bufio.Reader)
190                 keys    []XPub
191                 keyJSON struct {
192                         Alias string       `json:"alias"`
193                         XPub  chainkd.XPub `json:"xpub"`
194                 }
195         )
196         for _, fi := range files {
197                 path := filepath.Join(kc.keydir, fi.Name())
198                 if skipKeyFile(fi) {
199                         //log.Printf("ignoring file %v", path)
200                         //fmt.Printf("ignoring file %v", path)
201                         continue
202                 }
203                 fd, err := os.Open(path)
204                 if err != nil {
205                         //log.Printf(err)
206                         fmt.Printf("err")
207                         continue
208                 }
209                 buf.Reset(fd)
210                 // Parse the address.
211                 keyJSON.Alias = ""
212                 err = json.NewDecoder(buf).Decode(&keyJSON)
213                 switch {
214                 case err != nil:
215                         fmt.Printf("can't decode key %s: %v", path, err)
216                 case (keyJSON.Alias == ""):
217                         fmt.Printf("can't decode key %s: missing or void alias", path)
218                 default:
219                         keys = append(keys, XPub{XPub: keyJSON.XPub, Alias: keyJSON.Alias, File: path})
220                 }
221                 fd.Close()
222         }
223         return keys, err
224 }
225
226 func (kc *keyCache) delete(removed XPub) {
227         kc.mu.Lock()
228         defer kc.mu.Unlock()
229         kc.all = removeKey(kc.all, removed)
230         if ba := removeKey(kc.byPubs[removed.XPub], removed); len(ba) == 0 {
231                 delete(kc.byPubs, removed.XPub)
232         } else {
233                 kc.byPubs[removed.XPub] = ba
234         }
235 }
236
237 func removeKey(slice []XPub, elem XPub) []XPub {
238         for i := range slice {
239                 if slice[i] == elem {
240                         return append(slice[:i], slice[i+1:]...)
241                 }
242         }
243         return slice
244 }
245
246 func skipKeyFile(fi os.FileInfo) bool {
247         // Skip editor backups and UNIX-style hidden files.
248         if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
249                 return true
250         }
251         // Skip misc special files, directories (yes, symlinks too).
252         if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
253                 return true
254         }
255         return false
256 }
257
258 func (kc *keyCache) close() {
259         kc.mu.Lock()
260         kc.watcher.close()
261         if kc.throttle != nil {
262                 kc.throttle.Stop()
263         }
264         kc.mu.Unlock()
265 }