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