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