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