github.com/ava-labs/subnet-evm@v0.6.4/accounts/keystore/key.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2014 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package keystore 28 29 import ( 30 "bytes" 31 "crypto/ecdsa" 32 "encoding/hex" 33 "encoding/json" 34 "fmt" 35 "io" 36 "os" 37 "path/filepath" 38 "strings" 39 "time" 40 41 "github.com/ava-labs/subnet-evm/accounts" 42 "github.com/ethereum/go-ethereum/common" 43 "github.com/ethereum/go-ethereum/crypto" 44 "github.com/google/uuid" 45 ) 46 47 const ( 48 version = 3 49 ) 50 51 type Key struct { 52 Id uuid.UUID // Version 4 "random" for unique id not derived from key data 53 // to simplify lookups we also store the address 54 Address common.Address 55 // we only store privkey as pubkey/address can be derived from it 56 // privkey in this struct is always in plaintext 57 PrivateKey *ecdsa.PrivateKey 58 } 59 60 type keyStore interface { 61 // Loads and decrypts the key from disk. 62 GetKey(addr common.Address, filename string, auth string) (*Key, error) 63 // Writes and encrypts the key. 64 StoreKey(filename string, k *Key, auth string) error 65 // Joins filename with the key directory unless it is already absolute. 66 JoinPath(filename string) string 67 } 68 69 type plainKeyJSON struct { 70 Address string `json:"address"` 71 PrivateKey string `json:"privatekey"` 72 Id string `json:"id"` 73 Version int `json:"version"` 74 } 75 76 type encryptedKeyJSONV3 struct { 77 Address string `json:"address"` 78 Crypto CryptoJSON `json:"crypto"` 79 Id string `json:"id"` 80 Version int `json:"version"` 81 } 82 83 type encryptedKeyJSONV1 struct { 84 Address string `json:"address"` 85 Crypto CryptoJSON `json:"crypto"` 86 Id string `json:"id"` 87 Version string `json:"version"` 88 } 89 90 type CryptoJSON struct { 91 Cipher string `json:"cipher"` 92 CipherText string `json:"ciphertext"` 93 CipherParams cipherparamsJSON `json:"cipherparams"` 94 KDF string `json:"kdf"` 95 KDFParams map[string]interface{} `json:"kdfparams"` 96 MAC string `json:"mac"` 97 } 98 99 type cipherparamsJSON struct { 100 IV string `json:"iv"` 101 } 102 103 func (k *Key) MarshalJSON() (j []byte, err error) { 104 jStruct := plainKeyJSON{ 105 hex.EncodeToString(k.Address[:]), 106 hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)), 107 k.Id.String(), 108 version, 109 } 110 j, err = json.Marshal(jStruct) 111 return j, err 112 } 113 114 func (k *Key) UnmarshalJSON(j []byte) (err error) { 115 keyJSON := new(plainKeyJSON) 116 err = json.Unmarshal(j, &keyJSON) 117 if err != nil { 118 return err 119 } 120 121 u := new(uuid.UUID) 122 *u, err = uuid.Parse(keyJSON.Id) 123 if err != nil { 124 return err 125 } 126 k.Id = *u 127 addr, err := hex.DecodeString(keyJSON.Address) 128 if err != nil { 129 return err 130 } 131 privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey) 132 if err != nil { 133 return err 134 } 135 136 k.Address = common.BytesToAddress(addr) 137 k.PrivateKey = privkey 138 139 return nil 140 } 141 142 func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { 143 return newKeyFromECDSA(privateKeyECDSA) 144 } 145 146 func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { 147 id, err := uuid.NewRandom() 148 if err != nil { 149 panic(fmt.Sprintf("Could not create random uuid: %v", err)) 150 } 151 key := &Key{ 152 Id: id, 153 Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), 154 PrivateKey: privateKeyECDSA, 155 } 156 return key 157 } 158 159 // NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit 160 // into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we 161 // retry until the first byte is 0. 162 func NewKeyForDirectICAP(rand io.Reader) *Key { 163 randBytes := make([]byte, 64) 164 _, err := rand.Read(randBytes) 165 if err != nil { 166 panic("key generation: could not read from random source: " + err.Error()) 167 } 168 reader := bytes.NewReader(randBytes) 169 privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader) 170 if err != nil { 171 panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) 172 } 173 key := newKeyFromECDSA(privateKeyECDSA) 174 if !strings.HasPrefix(key.Address.Hex(), "0x00") { 175 return NewKeyForDirectICAP(rand) 176 } 177 return key 178 } 179 180 func NewKey(rand io.Reader) (*Key, error) { 181 privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand) 182 if err != nil { 183 return nil, err 184 } 185 return newKeyFromECDSA(privateKeyECDSA), nil 186 } 187 188 func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { 189 key, err := NewKey(rand) 190 if err != nil { 191 return nil, accounts.Account{}, err 192 } 193 a := accounts.Account{ 194 Address: key.Address, 195 URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}, 196 } 197 if err := ks.StoreKey(a.URL.Path, key, auth); err != nil { 198 zeroKey(key.PrivateKey) 199 return nil, a, err 200 } 201 return key, a, err 202 } 203 204 func writeTemporaryKeyFile(file string, content []byte) (string, error) { 205 // Create the keystore directory with appropriate permissions 206 // in case it is not present yet. 207 const dirPerm = 0700 208 if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil { 209 return "", err 210 } 211 // Atomic write: create a temporary hidden file first 212 // then move it into place. TempFile assigns mode 0600. 213 f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp") 214 if err != nil { 215 return "", err 216 } 217 if _, err := f.Write(content); err != nil { 218 f.Close() 219 os.Remove(f.Name()) 220 return "", err 221 } 222 f.Close() 223 return f.Name(), nil 224 } 225 226 func writeKeyFile(file string, content []byte) error { 227 name, err := writeTemporaryKeyFile(file, content) 228 if err != nil { 229 return err 230 } 231 return os.Rename(name, file) 232 } 233 234 // keyFileName implements the naming convention for keyfiles: 235 // UTC--<created_at UTC ISO8601>-<address hex> 236 func keyFileName(keyAddr common.Address) string { 237 ts := time.Now().UTC() 238 return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:])) 239 } 240 241 func toISO8601(t time.Time) string { 242 var tz string 243 name, offset := t.Zone() 244 if name == "UTC" { 245 tz = "Z" 246 } else { 247 tz = fmt.Sprintf("%03d00", offset/3600) 248 } 249 return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", 250 t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) 251 }