OSDN Git Service

Merge pull request #41 from Bytom/dev
[bytom/vapor.git] / blockchain / pseudohsm / pseudohsm.go
1 // Package pseudohsm provides a pseudo HSM for development environments.
2 package pseudohsm
3
4 import (
5         "bytes"
6         "encoding/json"
7         "io/ioutil"
8         "os"
9         "path/filepath"
10         "strings"
11         "sync"
12
13         "github.com/pborman/uuid"
14
15         "github.com/vapor/crypto/ed25519/chainkd"
16         "github.com/vapor/errors"
17         mnem "github.com/vapor/wallet/mnemonic"
18 )
19
20 // pre-define errors for supporting bytom errorFormatter
21 var (
22         ErrDuplicateKeyAlias = errors.New("duplicate key alias")
23         ErrLoadKey           = errors.New("key not found or wrong password ")
24         ErrDecrypt           = errors.New("could not decrypt key with given passphrase")
25         ErrMnemonicLength    = errors.New("mnemonic length error")
26 )
27
28 // EntropyLength random entropy length to generate mnemonics.
29 const EntropyLength = 128
30
31 // HSM type for storing pubkey and privatekey
32 type HSM struct {
33         cacheMu  sync.Mutex
34         keyStore keyStore
35         cache    *keyCache
36         //kdCache  map[chainkd.XPub]chainkd.XPrv
37 }
38
39 // XPub type for pubkey for anyone can see
40 type XPub struct {
41         Alias string       `json:"alias"`
42         XPub  chainkd.XPub `json:"xpub"`
43         File  string       `json:"file"`
44 }
45
46 // New method for HSM struct
47 func New(keypath string) (*HSM, error) {
48         keydir, _ := filepath.Abs(keypath)
49         return &HSM{
50                 keyStore: &keyStorePassphrase{keydir, LightScryptN, LightScryptP},
51                 cache:    newKeyCache(keydir),
52                 //kdCache:  make(map[chainkd.XPub]chainkd.XPrv),
53         }, nil
54 }
55
56 // XCreate produces a new random xprv and stores it in the db.
57 func (h *HSM) XCreate(alias string, auth string, language string) (*XPub, *string, error) {
58         h.cacheMu.Lock()
59         defer h.cacheMu.Unlock()
60
61         normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
62         if ok := h.cache.hasAlias(normalizedAlias); ok {
63                 return nil, nil, ErrDuplicateKeyAlias
64         }
65
66         xpub, mnemonic, err := h.createChainKDKey(normalizedAlias, auth, language)
67         if err != nil {
68                 return nil, nil, err
69         }
70         h.cache.add(*xpub)
71         return xpub, mnemonic, err
72 }
73
74 // ImportKeyFromMnemonic produces a xprv from mnemonic and stores it in the db.
75 func (h *HSM) ImportKeyFromMnemonic(alias string, auth string, mnemonic string, language string) (*XPub, error) {
76         h.cacheMu.Lock()
77         defer h.cacheMu.Unlock()
78
79         // checksum length = entropy length /32
80         // mnemonic length = (entropy length + checksum length)/11
81         if len(strings.Fields(mnemonic)) != (EntropyLength+EntropyLength/32)/11 {
82                 return nil, ErrMnemonicLength
83         }
84
85         normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
86         if ok := h.cache.hasAlias(normalizedAlias); ok {
87                 return nil, ErrDuplicateKeyAlias
88         }
89
90         // Pre validate that the mnemonic is well formed and only contains words that
91         // are present in the word list
92         if !mnem.IsMnemonicValid(mnemonic, language) {
93                 return nil, mnem.ErrInvalidMnemonic
94         }
95
96         xpub, err := h.createKeyFromMnemonic(alias, auth, mnemonic)
97         if err != nil {
98                 return nil, err
99         }
100
101         h.cache.add(*xpub)
102         return xpub, nil
103 }
104
105 func (h *HSM) createKeyFromMnemonic(alias string, auth string, mnemonic string) (*XPub, error) {
106         // Generate a Bip32 HD wallet for the mnemonic and a user supplied password
107         seed := mnem.NewSeed(mnemonic, "")
108         xprv, xpub, err := chainkd.NewXKeys(bytes.NewBuffer(seed))
109         if err != nil {
110                 return nil, err
111         }
112         id := uuid.NewRandom()
113         key := &XKey{
114                 ID:      id,
115                 KeyType: "bytom_kd",
116                 XPub:    xpub,
117                 XPrv:    xprv,
118                 Alias:   alias,
119         }
120         file := h.keyStore.JoinPath(keyFileName(key.ID.String()))
121         if err := h.keyStore.StoreKey(file, key, auth); err != nil {
122                 return nil, errors.Wrap(err, "storing keys")
123         }
124         return &XPub{XPub: xpub, Alias: alias, File: file}, nil
125 }
126
127 func (h *HSM) createChainKDKey(alias string, auth string, language string) (*XPub, *string, error) {
128         // Generate a mnemonic for memorization or user-friendly seeds
129         entropy, err := mnem.NewEntropy(EntropyLength)
130         if err != nil {
131                 return nil, nil, err
132         }
133         mnemonic, err := mnem.NewMnemonic(entropy, language)
134         if err != nil {
135                 return nil, nil, err
136         }
137         xpub, err := h.createKeyFromMnemonic(alias, auth, mnemonic)
138         if err != nil {
139                 return nil, nil, err
140         }
141         return xpub, &mnemonic, nil
142 }
143
144 // UpdateKeyAlias update key alias
145 func (h *HSM) UpdateKeyAlias(xpub chainkd.XPub, newAlias string) error {
146         h.cacheMu.Lock()
147         defer h.cacheMu.Unlock()
148
149         h.cache.maybeReload()
150         h.cache.mu.Lock()
151         xpb, err := h.cache.find(XPub{XPub: xpub})
152         h.cache.mu.Unlock()
153         if err != nil {
154                 return err
155         }
156
157         keyjson, err := ioutil.ReadFile(xpb.File)
158         if err != nil {
159                 return err
160         }
161
162         encrptKeyJSON := new(encryptedKeyJSON)
163         if err := json.Unmarshal(keyjson, encrptKeyJSON); err != nil {
164                 return err
165         }
166
167         normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
168         if ok := h.cache.hasAlias(normalizedAlias); ok {
169                 return ErrDuplicateKeyAlias
170         }
171
172         encrptKeyJSON.Alias = normalizedAlias
173         keyJSON, err := json.Marshal(encrptKeyJSON)
174         if err != nil {
175                 return err
176         }
177
178         if err := writeKeyFile(xpb.File, keyJSON); err != nil {
179                 return err
180         }
181
182         // update key alias
183         h.cache.delete(xpb)
184         xpb.Alias = normalizedAlias
185         h.cache.add(xpb)
186
187         return nil
188 }
189
190 // ListKeys returns a list of all xpubs from the store
191 func (h *HSM) ListKeys() []XPub {
192         xpubs := h.cache.keys()
193         return xpubs
194 }
195
196 // XSign looks up the xprv given the xpub, optionally derives a new
197 // xprv with the given path (but does not store the new xprv), and
198 // signs the given msg.
199 func (h *HSM) XSign(xpub chainkd.XPub, path [][]byte, msg []byte, auth string) ([]byte, error) {
200         xprv, err := h.LoadChainKDKey(xpub, auth)
201         if err != nil {
202                 return nil, err
203         }
204         if len(path) > 0 {
205                 xprv = xprv.Derive(path)
206         }
207         return xprv.Sign(msg), nil
208 }
209
210 //LoadChainKDKey get xprv from xpub
211 func (h *HSM) LoadChainKDKey(xpub chainkd.XPub, auth string) (xprv chainkd.XPrv, err error) {
212         h.cacheMu.Lock()
213         defer h.cacheMu.Unlock()
214
215         //if xprv, ok := h.kdCache[xpub]; ok {
216         //      return xprv, nil
217         //}
218
219         _, xkey, err := h.loadDecryptedKey(xpub, auth)
220         if err != nil {
221                 return xprv, ErrLoadKey
222         }
223         //h.kdCache[xpb.XPub] = xkey.XPrv
224         return xkey.XPrv, nil
225 }
226
227 // XDelete deletes the key matched by xpub if the passphrase is correct.
228 // If a contains no filename, the address must match a unique key.
229 func (h *HSM) XDelete(xpub chainkd.XPub, auth string) error {
230         // Decrypting the key isn't really necessary, but we do
231         // it anyway to check the password and zero out the key
232         // immediately afterwards.
233
234         xpb, xkey, err := h.loadDecryptedKey(xpub, auth)
235         if xkey != nil {
236                 zeroKey(xkey)
237         }
238         if err != nil {
239                 return err
240         }
241
242         h.cacheMu.Lock()
243         // The order is crucial here. The key is dropped from the
244         // cache after the file is gone so that a reload happening in
245         // between won't insert it into the cache again.
246         err = os.Remove(xpb.File)
247         if err == nil {
248                 h.cache.delete(xpb)
249         }
250         h.cacheMu.Unlock()
251         return err
252 }
253
254 func (h *HSM) loadDecryptedKey(xpub chainkd.XPub, auth string) (XPub, *XKey, error) {
255         h.cache.maybeReload()
256         h.cache.mu.Lock()
257         xpb, err := h.cache.find(XPub{XPub: xpub})
258
259         h.cache.mu.Unlock()
260         if err != nil {
261                 return xpb, nil, err
262         }
263         xkey, err := h.keyStore.GetKey(xpb.Alias, xpb.File, auth)
264         return xpb, xkey, err
265 }
266
267 // ResetPassword reset passphrase for an existing xpub
268 func (h *HSM) ResetPassword(xpub chainkd.XPub, oldAuth, newAuth string) error {
269         xpb, xkey, err := h.loadDecryptedKey(xpub, oldAuth)
270         if err != nil {
271                 return err
272         }
273         return h.keyStore.StoreKey(xpb.File, xkey, newAuth)
274 }
275
276 // HasAlias check whether the key alias exists
277 func (h *HSM) HasAlias(alias string) bool {
278         return h.cache.hasAlias(alias)
279 }
280
281 // HasKey check whether the private key exists
282 func (h *HSM) HasKey(xprv chainkd.XPrv) bool {
283         return h.cache.hasKey(xprv.XPub())
284 }