key.go 6.24 KB
Newer Older
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 6 7 8
// 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.
//
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 13 14
// GNU Lesser General Public License for more details.
//
// 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 accounts
18 19 20 21

import (
	"bytes"
	"crypto/ecdsa"
22
	"encoding/hex"
23
	"encoding/json"
24
	"fmt"
25
	"io"
26 27 28
	"io/ioutil"
	"os"
	"path/filepath"
29
	"strings"
30
	"time"
31

32
	"github.com/ethereum/go-ethereum/common"
33
	"github.com/ethereum/go-ethereum/crypto"
34
	"github.com/ethereum/go-ethereum/crypto/secp256k1"
35
	"github.com/pborman/uuid"
36 37
)

38
const (
39
	version = 3
40 41
)

42
type Key struct {
43 44
	Id uuid.UUID // Version 4 "random" for unique id not derived from key data
	// to simplify lookups we also store the address
45
	Address common.Address
46 47 48 49 50
	// we only store privkey as pubkey/address can be derived from it
	// privkey in this struct is always in plaintext
	PrivateKey *ecdsa.PrivateKey
}

51
type keyStore interface {
52 53 54 55 56 57
	// 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
58 59
}

60
type plainKeyJSON struct {
61 62 63
	Address    string `json:"address"`
	PrivateKey string `json:"privatekey"`
	Id         string `json:"id"`
64
	Version    int    `json:"version"`
65 66
}

67
type encryptedKeyJSONV3 struct {
68 69 70 71
	Address string     `json:"address"`
	Crypto  cryptoJSON `json:"crypto"`
	Id      string     `json:"id"`
	Version int        `json:"version"`
72 73 74
}

type encryptedKeyJSONV1 struct {
75 76 77 78
	Address string     `json:"address"`
	Crypto  cryptoJSON `json:"crypto"`
	Id      string     `json:"id"`
	Version string     `json:"version"`
79 80
}

81
type cryptoJSON struct {
82 83 84 85 86 87
	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"`
88 89
}

90 91
type cipherparamsJSON struct {
	IV string `json:"iv"`
92 93 94
}

type scryptParamsJSON struct {
95 96 97 98 99
	N     int    `json:"n"`
	R     int    `json:"r"`
	P     int    `json:"p"`
	DkLen int    `json:"dklen"`
	Salt  string `json:"salt"`
100 101 102
}

func (k *Key) MarshalJSON() (j []byte, err error) {
103
	jStruct := plainKeyJSON{
104
		hex.EncodeToString(k.Address[:]),
105
		hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)),
106 107
		k.Id.String(),
		version,
108
	}
109
	j, err = json.Marshal(jStruct)
110
	return j, err
111 112 113
}

func (k *Key) UnmarshalJSON(j []byte) (err error) {
114
	keyJSON := new(plainKeyJSON)
115 116
	err = json.Unmarshal(j, &keyJSON)
	if err != nil {
117
		return err
118 119 120
	}

	u := new(uuid.UUID)
121
	*u = uuid.Parse(keyJSON.Id)
122
	k.Id = *u
123 124 125 126 127 128 129 130 131 132 133
	addr, err := hex.DecodeString(keyJSON.Address)
	if err != nil {
		return err
	}

	privkey, err := hex.DecodeString(keyJSON.PrivateKey)
	if err != nil {
		return err
	}

	k.Address = common.BytesToAddress(addr)
134
	k.PrivateKey = crypto.ToECDSA(privkey)
135

136
	return nil
137 138
}

139
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
zelig's avatar
zelig committed
140 141 142
	id := uuid.NewRandom()
	key := &Key{
		Id:         id,
143
		Address:    crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
zelig's avatar
zelig committed
144 145 146 147 148
		PrivateKey: privateKeyECDSA,
	}
	return key
}

149 150 151
// 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.
152 153 154 155 156 157 158
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)
159
	privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader)
160 161 162
	if err != nil {
		panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
	}
163
	key := newKeyFromECDSA(privateKeyECDSA)
164 165 166 167 168
	if !strings.HasPrefix(key.Address.Hex(), "0x00") {
		return NewKeyForDirectICAP(rand)
	}
	return key
}
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229

func newKey(rand io.Reader) (*Key, error) {
	privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), rand)
	if err != nil {
		return nil, err
	}
	return newKeyFromECDSA(privateKeyECDSA), nil
}

func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, Account, error) {
	key, err := newKey(rand)
	if err != nil {
		return nil, Account{}, err
	}
	a := Account{Address: key.Address, File: ks.JoinPath(keyFileName(key.Address))}
	if err := ks.StoreKey(a.File, key, auth); err != nil {
		zeroKey(key.PrivateKey)
		return nil, a, err
	}
	return key, a, err
}

func writeKeyFile(file string, content []byte) 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 := ioutil.TempFile(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 os.Rename(f.Name(), file)
}

// keyFileName implements the naming convention for keyfiles:
// UTC--<created_at UTC ISO8601>-<address hex>
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)
}