15 "github.com/bytom/crypto/ed25519/chainkd"
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
24 type keysByFile []XPub
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] }
30 // AmbiguousAddrError is returned when attempting to unlock
31 // an XPub for which more than one file exists.
32 type AmbiguousKeyError struct {
37 func (err *AmbiguousKeyError) Error() string {
39 for i, a := range err.Matches {
41 if i < len(err.Matches)-1 {
45 return fmt.Sprintf("multiple keys match keys (%s)", files)
48 // keyCache is a live index of all keys in the keystore.
49 type keyCache struct {
54 byPubs map[chainkd.XPub][]XPub
58 func newKeyCache(keydir string) *keyCache {
61 byPubs: make(map[chainkd.XPub][]XPub),
63 kc.watcher = newWatcher(kc)
67 func (kc *keyCache) hasKey(xpub chainkd.XPub) bool {
71 return len(kc.byPubs[xpub]) > 0
74 func (kc *keyCache) add(newKey XPub) {
78 i := sort.Search(len(kc.all), func(i int) bool { return kc.all[i].File >= newKey.File })
79 if i < len(kc.all) && kc.all[i] == newKey {
82 // newKey is not in the cache.
83 kc.all = append(kc.all, XPub{})
84 copy(kc.all[i+1:], kc.all[i:])
86 kc.byPubs[newKey.XPub] = append(kc.byPubs[newKey.XPub], newKey)
89 func (kc *keyCache) keys() []XPub {
93 cpy := make([]XPub, len(kc.all))
98 func (kc *keyCache) maybeReload() {
102 if kc.watcher.running {
103 return // A watcher is running and will keep the cache up-to-date.
106 if kc.throttle == nil {
107 kc.throttle = time.NewTimer(0)
110 case <-kc.throttle.C:
112 return // The cache was reloaded recently.
117 kc.throttle.Reset(minReloadInterval)
120 // find returns the cached keys for alias if there is a unique match.
121 // The exact matching rules are explained by the documentation of Account.
122 // Callers must hold ac.mu.
123 func (kc *keyCache) find(xpub XPub) (XPub, error) {
124 // Limit search to xpub candidates if possible.
126 if (xpub.XPub != chainkd.XPub{}) {
127 matches = kc.byPubs[xpub.XPub]
130 // If only the basename is specified, complete the path.
131 if !strings.ContainsRune(xpub.File, filepath.Separator) {
132 xpub.File = filepath.Join(kc.keydir, xpub.File)
134 for i := range matches {
135 if matches[i].File == xpub.File {
136 return matches[i], nil
139 if (xpub.XPub == chainkd.XPub{}) {
140 return XPub{}, ErrNoKey
143 switch len(matches) {
145 return matches[0], nil
147 return XPub{}, ErrNoKey
149 err := &AmbiguousKeyError{Pubkey: hex.EncodeToString(xpub.XPub[:]), Matches: make([]XPub, len(matches))}
150 copy(err.Matches, matches)
155 // reload caches addresses of existing key.
156 // Callers must hold ac.mu.
157 func (kc *keyCache) reload() {
158 keys, err := kc.scan()
160 fmt.Printf("can't load keys: %v\n", err.Error())
165 for k := range kc.byPubs {
168 for _, k := range keys {
169 kc.byPubs[k.XPub] = append(kc.byPubs[k.XPub], k)
171 //log.Printf("reloaded keys, cache has %d keys", len(ac.all))
172 fmt.Printf("reloaded keys, cache has %d keys\n", len(kc.all))
175 func (kc *keyCache) scan() ([]XPub, error) {
176 files, err := ioutil.ReadDir(kc.keydir)
181 buf = new(bufio.Reader)
184 Alias string `json:"alias"`
185 XPub chainkd.XPub `json:"xpub"`
188 for _, fi := range files {
189 path := filepath.Join(kc.keydir, fi.Name())
191 //log.Printf("ignoring file %v", path)
192 //fmt.Printf("ignoring file %v", path)
195 fd, err := os.Open(path)
202 // Parse the address.
204 err = json.NewDecoder(buf).Decode(&keyJSON)
207 fmt.Printf("can't decode key %s: %v", path, err)
208 case (keyJSON.Alias == ""):
209 fmt.Printf("can't decode key %s: missing or void alias", path)
211 keys = append(keys, XPub{XPub: keyJSON.XPub, Alias: keyJSON.Alias, File: path})
218 func (kc *keyCache) delete(removed XPub) {
221 kc.all = removeKey(kc.all, removed)
222 if ba := removeKey(kc.byPubs[removed.XPub], removed); len(ba) == 0 {
223 delete(kc.byPubs, removed.XPub)
225 kc.byPubs[removed.XPub] = ba
229 func removeKey(slice []XPub, elem XPub) []XPub {
230 for i := range slice {
231 if slice[i] == elem {
232 return append(slice[:i], slice[i+1:]...)
238 func skipKeyFile(fi os.FileInfo) bool {
239 // Skip editor backups and UNIX-style hidden files.
240 if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
243 // Skip misc special files, directories (yes, symlinks too).
244 if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
250 func (kc *keyCache) close() {
253 if kc.throttle != nil {