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