OSDN Git Service

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