// Copyright 2014 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package keystore import ( "bytes" "crypto/ecdsa" "encoding/hex" "encoding/json" "fmt" "io" "os" "path/filepath" "strings" "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" "github.com/status-im/status-go/extkeys" ) const ( version = 3 ) type Key struct { Id uuid.UUID // Version 4 "random" for unique id not derived from key data // to simplify lookups we also store the address Address common.Address // we only store privkey as pubkey/address can be derived from it // privkey in this struct is always in plaintext PrivateKey *ecdsa.PrivateKey // ExtendedKey is the extended key of the PrivateKey itself, and it's used // to derive child keys. ExtendedKey *extkeys.ExtendedKey // SubAccountIndex is DEPRECATED // It was use in Status to keep track of the number of sub-account created // before having multi-account support. SubAccountIndex uint32 } type keyStore interface { // Loads and decrypts the key from disk. GetKey(addr common.Address, filename string, auth string) (*Key, error) // Writes and encrypts the key. StoreKey(filename string, k *Key, auth string) error // Joins filename with the key directory unless it is already absolute. JoinPath(filename string) string } type plainKeyJSON struct { Address string `json:"address"` PrivateKey string `json:"privatekey"` Id string `json:"id"` Version int `json:"version"` } type encryptedKeyJSONV3 struct { Address string `json:"address"` Crypto CryptoJSON `json:"crypto"` Id string `json:"id"` Version int `json:"version"` ExtendedKey CryptoJSON `json:"extendedkey"` SubAccountIndex uint32 `json:"subaccountindex"` } type encryptedKeyJSONV1 struct { Address string `json:"address"` Crypto CryptoJSON `json:"crypto"` Id string `json:"id"` Version string `json:"version"` } type CryptoJSON struct { Cipher string `json:"cipher"` CipherText string `json:"ciphertext"` CipherParams cipherparamsJSON `json:"cipherparams"` KDF string `json:"kdf"` KDFParams map[string]interface{} `json:"kdfparams"` MAC string `json:"mac"` } type cipherparamsJSON struct { IV string `json:"iv"` } func (k *Key) MarshalJSON() (j []byte, err error) { jStruct := plainKeyJSON{ hex.EncodeToString(k.Address[:]), hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)), k.Id.String(), version, } j, err = json.Marshal(jStruct) return j, err } func (k *Key) UnmarshalJSON(j []byte) (err error) { keyJSON := new(plainKeyJSON) err = json.Unmarshal(j, &keyJSON) if err != nil { return err } u := new(uuid.UUID) *u, err = uuid.Parse(keyJSON.Id) if err != nil { return err } k.Id = *u addr, err := hex.DecodeString(keyJSON.Address) if err != nil { return err } privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey) if err != nil { return err } k.Address = common.BytesToAddress(addr) k.PrivateKey = privkey return nil } func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { id, err := uuid.NewRandom() if err != nil { panic(fmt.Sprintf("Could not create random uuid: %v", err)) } key := &Key{ Id: id, Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), PrivateKey: privateKeyECDSA, } return key } func newKeyForPurposeFromExtendedKey(keyPurpose extkeys.KeyPurpose, extKey *extkeys.ExtendedKey) (*Key, error) { var ( extChild1, extChild2 *extkeys.ExtendedKey err error ) if extKey.Depth == 0 { // we are dealing with master key // CKD#1 - main account extChild1, err = extKey.ChildForPurpose(keyPurpose, 0) if err != nil { return &Key{}, err } // CKD#2 - sub-accounts root extChild2, err = extKey.ChildForPurpose(keyPurpose, 1) if err != nil { return &Key{}, err } } else { // we are dealing with non-master key, so it is safe to persist and extend from it extChild1 = extKey extChild2 = extKey } privateKeyECDSA := extChild1.ToECDSA() id, err := uuid.NewRandom() if err != nil { return nil, err } key := &Key{ Id: id, Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), PrivateKey: privateKeyECDSA, ExtendedKey: extChild2, } return key, nil } // NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit // into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we // retry until the first byte is 0. func NewKeyForDirectICAP(rand io.Reader) *Key { randBytes := make([]byte, 64) _, err := rand.Read(randBytes) if err != nil { panic("key generation: could not read from random source: " + err.Error()) } reader := bytes.NewReader(randBytes) privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader) if err != nil { panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) } key := newKeyFromECDSA(privateKeyECDSA) if !strings.HasPrefix(key.Address.Hex(), "0x00") { return NewKeyForDirectICAP(rand) } return key } func newKey(rand io.Reader) (*Key, error) { privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand) if err != nil { return nil, err } return newKeyFromECDSA(privateKeyECDSA), nil } func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { key, err := newKey(rand) if err != nil { return nil, accounts.Account{}, err } a := accounts.Account{ Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}, } if err := ks.StoreKey(a.URL.Path, key, auth); err != nil { zeroKey(key.PrivateKey) return nil, a, err } return key, a, err } func writeTemporaryKeyFile(file string, content []byte) (string, error) { // Create the keystore directory with appropriate permissions // in case it is not present yet. const dirPerm = 0700 if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil { return "", err } // Atomic write: create a temporary hidden file first // then move it into place. TempFile assigns mode 0600. f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp") if err != nil { return "", err } if _, err := f.Write(content); err != nil { f.Close() os.Remove(f.Name()) return "", err } f.Close() return f.Name(), nil } func writeKeyFile(file string, content []byte) error { name, err := writeTemporaryKeyFile(file, content) if err != nil { return err } return os.Rename(name, file) } // keyFileName implements the naming convention for keyfiles: // UTC---
func keyFileName(keyAddr common.Address) string { ts := time.Now().UTC() return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:])) } func toISO8601(t time.Time) string { var tz string name, offset := t.Zone() if name == "UTC" { tz = "Z" } else { tz = fmt.Sprintf("%03d00", offset/3600) } return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) }