From 512ffa2bf4308b44aa6f43f25238b375b58d7dbc Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Sun, 25 Jan 2015 02:07:20 +0100 Subject: Add accounts package and refactor key stores * Add initial UserAccount and AccountManager structs * Add NewAccount, Sign and Accounts functions * Refactor key stores to use key address as main identifier while keeping the UUID. * Use key address as file/dir names instead of UUID --- crypto/crypto.go | 10 +++++++-- crypto/key.go | 25 ++++++++++----------- crypto/key_store_passphrase.go | 40 +++++++++++++++++++--------------- crypto/key_store_plain.go | 49 +++++++++++++++++++++++++++++------------- crypto/key_store_test.go | 18 ++++++++-------- 5 files changed, 87 insertions(+), 55 deletions(-) (limited to 'crypto') diff --git a/crypto/crypto.go b/crypto/crypto.go index 4b2cc7bb4..f8d6139a8 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -134,7 +134,7 @@ func ImportPreSaleKey(keyStore KeyStore2, keyJSON []byte, password string) (*Key return nil, err } id := uuid.NewRandom() - key.Id = &id + key.Id = id err = keyStore.StoreKey(key, password) return key, err } @@ -167,9 +167,10 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error ecKey := ToECDSA(ethPriv) key = &Key{ Id: nil, + Address: pubkeyToAddress(ecKey.PublicKey), PrivateKey: ecKey, } - derivedAddr := ethutil.Bytes2Hex(key.Address()) + derivedAddr := ethutil.Bytes2Hex(key.Address) expectedAddr := preSaleKeyStruct.EthAddr if derivedAddr != expectedAddr { err = errors.New("decrypted addr not equal to expected addr") @@ -223,3 +224,8 @@ func PKCS7Unpad(in []byte) []byte { } return in[:len(in)-int(padding)] } + +func pubkeyToAddress(p ecdsa.PublicKey) []byte { + pubBytes := FromECDSAPub(&p) + return Sha3(pubBytes[1:])[12:] +} diff --git a/crypto/key.go b/crypto/key.go index ca29b691f..f8f64c35c 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -33,7 +33,9 @@ import ( ) type Key struct { - Id *uuid.UUID // Version 4 "random" for unique id not derived from key data + Id uuid.UUID // Version 4 "random" for unique id not derived from key data + // to simplify lookups we also store the address + Address []byte // we only store privkey as pubkey/address can be derived from it // privkey in this struct is always in plaintext PrivateKey *ecdsa.PrivateKey @@ -41,6 +43,7 @@ type Key struct { type plainKeyJSON struct { Id []byte + Address []byte PrivateKey []byte } @@ -51,18 +54,15 @@ type cipherJSON struct { } type encryptedKeyJSON struct { - Id []byte - Crypto cipherJSON -} - -func (k *Key) Address() []byte { - pubBytes := FromECDSAPub(&k.PrivateKey.PublicKey) - return Sha3(pubBytes[1:])[12:] + Id []byte + Address []byte + Crypto cipherJSON } func (k *Key) MarshalJSON() (j []byte, err error) { jStruct := plainKeyJSON{ - *k.Id, + k.Id, + k.Address, FromECDSA(k.PrivateKey), } j, err = json.Marshal(jStruct) @@ -78,8 +78,8 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { u := new(uuid.UUID) *u = keyJSON.Id - k.Id = u - + k.Id = *u + k.Address = keyJSON.Address k.PrivateKey = ToECDSA(keyJSON.PrivateKey) return err @@ -101,7 +101,8 @@ func NewKey(rand io.Reader) *Key { id := uuid.NewRandom() key := &Key{ - Id: &id, + Id: id, + Address: pubkeyToAddress(privateKeyECDSA.PublicKey), PrivateKey: privateKeyECDSA, } return key diff --git a/crypto/key_store_passphrase.go b/crypto/key_store_passphrase.go index 80bf49d68..807a91397 100644 --- a/crypto/key_store_passphrase.go +++ b/crypto/key_store_passphrase.go @@ -69,6 +69,7 @@ import ( "crypto/aes" "crypto/cipher" crand "crypto/rand" + "encoding/hex" "encoding/json" "errors" "io" @@ -96,18 +97,23 @@ func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *K return GenerateNewKeyDefault(ks, rand, auth) } -func (ks keyStorePassphrase) GetKey(keyId *uuid.UUID, auth string) (key *Key, err error) { - keyBytes, err := DecryptKey(ks, keyId, auth) +func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *Key, err error) { + keyBytes, keyId, err := DecryptKey(ks, keyAddr, auth) if err != nil { return nil, err } key = &Key{ - Id: keyId, + Id: uuid.UUID(keyId), + Address: keyAddr, PrivateKey: ToECDSA(keyBytes), } return key, err } +func (ks keyStorePassphrase) GetKeyAddresses() (addresses [][]byte, err error) { + return GetKeyAddresses(ks.keysDirPath) +} + func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { authArray := []byte(auth) salt := getEntropyCSPRNG(32) @@ -136,7 +142,8 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { cipherText, } keyStruct := encryptedKeyJSON{ - *key.Id, + key.Id, + key.Address, cipherStruct, } keyJSON, err := json.Marshal(keyStruct) @@ -144,51 +151,50 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { return err } - return WriteKeyFile(key.Id.String(), ks.keysDirPath, keyJSON) + return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON) } -func (ks keyStorePassphrase) DeleteKey(keyId *uuid.UUID, auth string) (err error) { +func (ks keyStorePassphrase) DeleteKey(keyAddr []byte, auth string) (err error) { // only delete if correct passphrase is given - _, err = DecryptKey(ks, keyId, auth) + _, _, err = DecryptKey(ks, keyAddr, auth) if err != nil { return err } - keyDirPath := path.Join(ks.keysDirPath, keyId.String()) + keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr)) return os.RemoveAll(keyDirPath) } -func DecryptKey(ks keyStorePassphrase, keyId *uuid.UUID, auth string) (keyBytes []byte, err error) { - fileContent, err := GetKeyFile(ks.keysDirPath, keyId) +func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []byte, keyId []byte, err error) { + fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr) if err != nil { - return nil, err + return nil, nil, err } keyProtected := new(encryptedKeyJSON) err = json.Unmarshal(fileContent, keyProtected) + keyId = keyProtected.Id salt := keyProtected.Crypto.Salt - iv := keyProtected.Crypto.IV - cipherText := keyProtected.Crypto.CipherText authArray := []byte(auth) derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) if err != nil { - return nil, err + return nil, nil, err } plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) if err != nil { - return nil, err + return nil, nil, err } keyBytes = plainText[:len(plainText)-32] keyBytesHash := plainText[len(plainText)-32:] if !bytes.Equal(Sha3(keyBytes), keyBytesHash) { err = errors.New("Decryption failed: checksum mismatch") - return nil, err + return nil, nil, err } - return keyBytes, err + return keyBytes, keyId, err } func getEntropyCSPRNG(n int) []byte { diff --git a/crypto/key_store_plain.go b/crypto/key_store_plain.go index b6e2a309a..6b76962a0 100644 --- a/crypto/key_store_plain.go +++ b/crypto/key_store_plain.go @@ -24,7 +24,7 @@ package crypto import ( - "code.google.com/p/go-uuid/uuid" + "encoding/hex" "encoding/json" "fmt" "io" @@ -38,9 +38,10 @@ import ( type KeyStore2 interface { // create new key using io.Reader entropy source and optionally using auth string GenerateNewKey(io.Reader, string) (*Key, error) - GetKey(*uuid.UUID, string) (*Key, error) // key from id and auth string - StoreKey(*Key, string) error // store key optionally using auth string - DeleteKey(*uuid.UUID, string) error // delete key by id and auth string + GetKey([]byte, string) (*Key, error) // key from addr and auth string + GetKeyAddresses() ([][]byte, error) // get all addresses + StoreKey(*Key, string) error // store key optionally using auth string + DeleteKey([]byte, string) error // delete key by addr and auth string } type keyStorePlain struct { @@ -72,8 +73,8 @@ func GenerateNewKeyDefault(ks KeyStore2, rand io.Reader, auth string) (key *Key, return key, err } -func (ks keyStorePlain) GetKey(keyId *uuid.UUID, auth string) (key *Key, err error) { - fileContent, err := GetKeyFile(ks.keysDirPath, keyId) +func (ks keyStorePlain) GetKey(keyAddr []byte, auth string) (key *Key, err error) { + fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr) if err != nil { return nil, err } @@ -83,32 +84,50 @@ func (ks keyStorePlain) GetKey(keyId *uuid.UUID, auth string) (key *Key, err err return key, err } +func (ks keyStorePlain) GetKeyAddresses() (addresses [][]byte, err error) { + return GetKeyAddresses(ks.keysDirPath) +} + func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) { keyJSON, err := json.Marshal(key) if err != nil { return err } - err = WriteKeyFile(key.Id.String(), ks.keysDirPath, keyJSON) + err = WriteKeyFile(key.Address, ks.keysDirPath, keyJSON) return err } -func (ks keyStorePlain) DeleteKey(keyId *uuid.UUID, auth string) (err error) { - keyDirPath := path.Join(ks.keysDirPath, keyId.String()) +func (ks keyStorePlain) DeleteKey(keyAddr []byte, auth string) (err error) { + keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr)) err = os.RemoveAll(keyDirPath) return err } -func GetKeyFile(keysDirPath string, keyId *uuid.UUID) (fileContent []byte, err error) { - id := keyId.String() - return ioutil.ReadFile(path.Join(keysDirPath, id, id)) +func GetKeyFile(keysDirPath string, keyAddr []byte) (fileContent []byte, err error) { + fileName := hex.EncodeToString(keyAddr) + return ioutil.ReadFile(path.Join(keysDirPath, fileName, fileName)) } -func WriteKeyFile(id string, keysDirPath string, content []byte) (err error) { - keyDirPath := path.Join(keysDirPath, id) - keyFilePath := path.Join(keyDirPath, id) +func WriteKeyFile(addr []byte, keysDirPath string, content []byte) (err error) { + addrHex := hex.EncodeToString(addr) + keyDirPath := path.Join(keysDirPath, addrHex) + keyFilePath := path.Join(keyDirPath, addrHex) err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user if err != nil { return err } return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user } + +func GetKeyAddresses(keysDirPath string) (addresses [][]byte, err error) { + fileInfos, err := ioutil.ReadDir(keysDirPath) + if err != nil { + return nil, err + } + addresses = make([][]byte, len(fileInfos)) + for i, fileInfo := range fileInfos { + addresses[i] = make([]byte, 40) + addresses[i] = []byte(fileInfo.Name()) + } + return addresses, err +} diff --git a/crypto/key_store_test.go b/crypto/key_store_test.go index 54efc739a..0d229ab65 100644 --- a/crypto/key_store_test.go +++ b/crypto/key_store_test.go @@ -15,12 +15,12 @@ func TestKeyStorePlain(t *testing.T) { } k2 := new(Key) - k2, err = ks.GetKey(k1.Id, pass) + k2, err = ks.GetKey(k1.Address, pass) if err != nil { t.Fatal(err) } - if !reflect.DeepEqual(k1.Id, k2.Id) { + if !reflect.DeepEqual(k1.Address, k2.Address) { t.Fatal(err) } @@ -28,7 +28,7 @@ func TestKeyStorePlain(t *testing.T) { t.Fatal(err) } - err = ks.DeleteKey(k2.Id, pass) + err = ks.DeleteKey(k2.Address, pass) if err != nil { t.Fatal(err) } @@ -42,11 +42,11 @@ func TestKeyStorePassphrase(t *testing.T) { t.Fatal(err) } k2 := new(Key) - k2, err = ks.GetKey(k1.Id, pass) + k2, err = ks.GetKey(k1.Address, pass) if err != nil { t.Fatal(err) } - if !reflect.DeepEqual(k1.Id, k2.Id) { + if !reflect.DeepEqual(k1.Address, k2.Address) { t.Fatal(err) } @@ -54,7 +54,7 @@ func TestKeyStorePassphrase(t *testing.T) { t.Fatal(err) } - err = ks.DeleteKey(k2.Id, pass) // also to clean up created files + err = ks.DeleteKey(k2.Address, pass) // also to clean up created files if err != nil { t.Fatal(err) } @@ -68,17 +68,17 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) { t.Fatal(err) } - _, err = ks.GetKey(k1.Id, "bar") // wrong passphrase + _, err = ks.GetKey(k1.Address, "bar") // wrong passphrase if err == nil { t.Fatal(err) } - err = ks.DeleteKey(k1.Id, "bar") // wrong passphrase + err = ks.DeleteKey(k1.Address, "bar") // wrong passphrase if err == nil { t.Fatal(err) } - err = ks.DeleteKey(k1.Id, pass) // to clean up + err = ks.DeleteKey(k1.Address, pass) // to clean up if err != nil { t.Fatal(err) } -- cgit v1.2.3 From 8d9752a557e33341a5fb73239dbae664b2f8aaa0 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Wed, 28 Jan 2015 05:12:57 +0100 Subject: Address pull request comments * Use crypto.Sign instead of directly calling secp256k1 lib * Rename UserAccount to Account and Addr to Address (for consistency) * Change AccountManager.Sign to take ptr to Account instead of address byte array * Simplify copying of Accounts in Accounts() * PubkeyToAddress and GetEntropyCSPRNG now exported --- crypto/crypto.go | 7 +++---- crypto/key.go | 2 +- crypto/key_store_passphrase.go | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) (limited to 'crypto') diff --git a/crypto/crypto.go b/crypto/crypto.go index f8d6139a8..effa703d0 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -133,8 +133,7 @@ func ImportPreSaleKey(keyStore KeyStore2, keyJSON []byte, password string) (*Key if err != nil { return nil, err } - id := uuid.NewRandom() - key.Id = id + key.Id = uuid.NewRandom() err = keyStore.StoreKey(key, password) return key, err } @@ -167,7 +166,7 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error ecKey := ToECDSA(ethPriv) key = &Key{ Id: nil, - Address: pubkeyToAddress(ecKey.PublicKey), + Address: PubkeyToAddress(ecKey.PublicKey), PrivateKey: ecKey, } derivedAddr := ethutil.Bytes2Hex(key.Address) @@ -225,7 +224,7 @@ func PKCS7Unpad(in []byte) []byte { return in[:len(in)-int(padding)] } -func pubkeyToAddress(p ecdsa.PublicKey) []byte { +func PubkeyToAddress(p ecdsa.PublicKey) []byte { pubBytes := FromECDSAPub(&p) return Sha3(pubBytes[1:])[12:] } diff --git a/crypto/key.go b/crypto/key.go index f8f64c35c..b9ad34f47 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -102,7 +102,7 @@ func NewKey(rand io.Reader) *Key { id := uuid.NewRandom() key := &Key{ Id: id, - Address: pubkeyToAddress(privateKeyECDSA.PublicKey), + Address: PubkeyToAddress(privateKeyECDSA.PublicKey), PrivateKey: privateKeyECDSA, } return key diff --git a/crypto/key_store_passphrase.go b/crypto/key_store_passphrase.go index 807a91397..0862b7886 100644 --- a/crypto/key_store_passphrase.go +++ b/crypto/key_store_passphrase.go @@ -116,7 +116,7 @@ func (ks keyStorePassphrase) GetKeyAddresses() (addresses [][]byte, err error) { func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { authArray := []byte(auth) - salt := getEntropyCSPRNG(32) + salt := GetEntropyCSPRNG(32) derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) if err != nil { return err @@ -131,7 +131,7 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { return err } - iv := getEntropyCSPRNG(aes.BlockSize) // 16 + iv := GetEntropyCSPRNG(aes.BlockSize) // 16 AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv) cipherText := make([]byte, len(toEncrypt)) AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt) @@ -197,7 +197,7 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes [] return keyBytes, keyId, err } -func getEntropyCSPRNG(n int) []byte { +func GetEntropyCSPRNG(n int) []byte { mainBuff := make([]byte, n) _, err := io.ReadFull(crand.Reader, mainBuff) if err != nil { -- cgit v1.2.3 From c48644490f039fb9756b4cd1fedf11fbb1c4a16f Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 30 Jan 2015 13:24:20 +0100 Subject: Fixed whisper pub key bug * Unrecoverable messages would cause segfault when recovering invalid pub key --- crypto/crypto.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crypto') diff --git a/crypto/crypto.go b/crypto/crypto.go index effa703d0..d56b9112f 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -92,7 +92,7 @@ func ToECDSAPub(pub []byte) *ecdsa.PublicKey { } func FromECDSAPub(pub *ecdsa.PublicKey) []byte { - if pub == nil { + if pub == nil || pub.X == nil || pub.Y == nil { return nil } return elliptic.Marshal(S256(), pub.X, pub.Y) -- cgit v1.2.3 From a008c21cf0a0f458ef112b99048b459618dd0cdc Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 31 Jan 2015 17:44:34 +0100 Subject: Fixed Sign nonce --- crypto/secp256k1/secp256.go | 6 +----- crypto/secp256k1/secp256_rand.go | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'crypto') diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 53ad9b477..c01598b84 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -124,11 +124,7 @@ int secp256k1_ecdsa_sign_compact(const unsigned char *msg, int msglen, */ func Sign(msg []byte, seckey []byte) ([]byte, error) { - //var nonce []byte = RandByte(32) - nonce := make([]byte, 32) - for i := range msg { - nonce[i] = msg[i] ^ seckey[i] - } + nonce := RandByte(32) var sig []byte = make([]byte, 65) var recid C.int diff --git a/crypto/secp256k1/secp256_rand.go b/crypto/secp256k1/secp256_rand.go index 5e8035e0f..027b5f386 100644 --- a/crypto/secp256k1/secp256_rand.go +++ b/crypto/secp256k1/secp256_rand.go @@ -75,7 +75,7 @@ func RandByte(n int) []byte { return nil } - buff2 := RandByteWeakCrypto(n) + buff2 := saltByte(n) for i := 0; i < n; i++ { buff[i] ^= buff2[2] } -- cgit v1.2.3 From 19cff8eccafbb6dfbb8cb9038bfb46d95dfdf60d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 31 Jan 2015 17:50:28 +0100 Subject: Fixed n --- crypto/secp256k1/secp256_rand.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'crypto') diff --git a/crypto/secp256k1/secp256_rand.go b/crypto/secp256k1/secp256_rand.go index 027b5f386..bb10025fc 100644 --- a/crypto/secp256k1/secp256_rand.go +++ b/crypto/secp256k1/secp256_rand.go @@ -49,7 +49,8 @@ func init() { _rand = mrand.New(mrand.NewSource(int64(seed1 ^ seed2 ^ seed3))) } -func saltByte(buff []byte) []byte { +func saltByte(n int) []byte { + buff := make([]byte, n) for i := 0; i < len(buff); i++ { var v uint64 = uint64(_rand.Int63()) var b byte -- cgit v1.2.3 From 410b35e9135baa86e92bc07e0ef85d04e3ac0561 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 27 Jan 2015 14:29:33 +0100 Subject: crypto: make it easier to run Sha3 on multiple inputs crypto.Sha3(append(foo, bar)) --> crypto.Sha3(foo, bar) crypto.Sha3([]byte{}) --> crypto.Sha3() --- crypto/crypto.go | 7 ++++--- crypto/crypto_test.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'crypto') diff --git a/crypto/crypto.go b/crypto/crypto.go index d56b9112f..42e6036b5 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -27,10 +27,11 @@ func init() { ecies.AddParamsForCurve(S256(), ecies.ECIES_AES128_SHA256) } -func Sha3(data []byte) []byte { +func Sha3(data ...[]byte) []byte { d := sha3.NewKeccak256() - d.Write(data) - + for _, b := range data { + d.Write(b) + } return d.Sum(nil) } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 441733f93..c68856622 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -18,7 +18,7 @@ import ( func TestSha3(t *testing.T) { msg := []byte("abc") exp, _ := hex.DecodeString("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") - checkhash(t, "Sha3-256", Sha3, msg, exp) + checkhash(t, "Sha3-256", func(in []byte) []byte { return Sha3(in) }, msg, exp) } func TestSha256(t *testing.T) { -- cgit v1.2.3 From 0c7df37351ab85c577fe815d6d22f4627832b0c3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Feb 2015 12:29:50 +0100 Subject: crypto: add key loading functions --- crypto/crypto.go | 28 ++++++++++++++++++++++++++++ crypto/key.go | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) (limited to 'crypto') diff --git a/crypto/crypto.go b/crypto/crypto.go index 42e6036b5..2c8f82977 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -8,6 +8,8 @@ import ( "crypto/rand" "crypto/sha256" "fmt" + "io" + "os" "encoding/hex" "encoding/json" @@ -99,6 +101,32 @@ func FromECDSAPub(pub *ecdsa.PublicKey) []byte { return elliptic.Marshal(S256(), pub.X, pub.Y) } +// HexToECDSA parses a secp256k1 private key. +func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error) { + b, err := hex.DecodeString(hexkey) + if err != nil { + return nil, errors.New("invalid hex string") + } + if len(b) != 32 { + return nil, errors.New("invalid length, need 256 bits") + } + return ToECDSA(b), nil +} + +// LoadECDSA loads a secp256k1 private key from the given file. +func LoadECDSA(file string) (*ecdsa.PrivateKey, error) { + buf := make([]byte, 32) + fd, err := os.Open(file) + if err != nil { + return nil, err + } + defer fd.Close() + if _, err := io.ReadFull(fd, buf); err != nil { + return nil, err + } + return ToECDSA(buf), nil +} + func GenerateKey() (*ecdsa.PrivateKey, error) { return ecdsa.GenerateKey(S256(), rand.Reader) } diff --git a/crypto/key.go b/crypto/key.go index b9ad34f47..ec4908c30 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -25,11 +25,12 @@ package crypto import ( "bytes" - "code.google.com/p/go-uuid/uuid" "crypto/ecdsa" "crypto/elliptic" "encoding/json" "io" + + "code.google.com/p/go-uuid/uuid" ) type Key struct { -- cgit v1.2.3 From 8c056aebe10c8c56f7c25889780b04e00f9ca00b Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Wed, 4 Feb 2015 17:06:06 +0100 Subject: Set both key generation and ECDSA nonce to use mixed entropy * Move random entropy functions to new package randentropy * Add function to get n bytes entropy where up to first 32 bytes are mixed with OS entropy sources --- crypto/key_store_passphrase.go | 15 ++----- crypto/key_store_test.go | 8 ++-- crypto/randentropy/rand_entropy.go | 82 ++++++++++++++++++++++++++++++++++++++ crypto/secp256k1/secp256.go | 5 ++- 4 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 crypto/randentropy/rand_entropy.go (limited to 'crypto') diff --git a/crypto/key_store_passphrase.go b/crypto/key_store_passphrase.go index 0862b7886..74408f874 100644 --- a/crypto/key_store_passphrase.go +++ b/crypto/key_store_passphrase.go @@ -68,10 +68,10 @@ import ( "code.google.com/p/go.crypto/scrypt" "crypto/aes" "crypto/cipher" - crand "crypto/rand" "encoding/hex" "encoding/json" "errors" + "github.com/ethereum/go-ethereum/crypto/randentropy" "io" "os" "path" @@ -116,7 +116,7 @@ func (ks keyStorePassphrase) GetKeyAddresses() (addresses [][]byte, err error) { func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { authArray := []byte(auth) - salt := GetEntropyCSPRNG(32) + salt := randentropy.GetEntropyMixed(32) derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) if err != nil { return err @@ -131,7 +131,7 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { return err } - iv := GetEntropyCSPRNG(aes.BlockSize) // 16 + iv := randentropy.GetEntropyMixed(aes.BlockSize) // 16 AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv) cipherText := make([]byte, len(toEncrypt)) AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt) @@ -196,12 +196,3 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes [] } return keyBytes, keyId, err } - -func GetEntropyCSPRNG(n int) []byte { - mainBuff := make([]byte, n) - _, err := io.ReadFull(crand.Reader, mainBuff) - if err != nil { - panic("key generation: reading from crypto/rand failed: " + err.Error()) - } - return mainBuff -} diff --git a/crypto/key_store_test.go b/crypto/key_store_test.go index 0d229ab65..a136ba992 100644 --- a/crypto/key_store_test.go +++ b/crypto/key_store_test.go @@ -1,7 +1,7 @@ package crypto import ( - crand "crypto/rand" + "github.com/ethereum/go-ethereum/crypto/randentropy" "reflect" "testing" ) @@ -9,7 +9,7 @@ import ( func TestKeyStorePlain(t *testing.T) { ks := NewKeyStorePlain(DefaultDataDir()) pass := "" // not used but required by API - k1, err := ks.GenerateNewKey(crand.Reader, pass) + k1, err := ks.GenerateNewKey(new(randentropy.RandEntropy), pass) if err != nil { t.Fatal(err) } @@ -37,7 +37,7 @@ func TestKeyStorePlain(t *testing.T) { func TestKeyStorePassphrase(t *testing.T) { ks := NewKeyStorePassphrase(DefaultDataDir()) pass := "foo" - k1, err := ks.GenerateNewKey(crand.Reader, pass) + k1, err := ks.GenerateNewKey(new(randentropy.RandEntropy), pass) if err != nil { t.Fatal(err) } @@ -63,7 +63,7 @@ func TestKeyStorePassphrase(t *testing.T) { func TestKeyStorePassphraseDecryptionFail(t *testing.T) { ks := NewKeyStorePassphrase(DefaultDataDir()) pass := "foo" - k1, err := ks.GenerateNewKey(crand.Reader, pass) + k1, err := ks.GenerateNewKey(new(randentropy.RandEntropy), pass) if err != nil { t.Fatal(err) } diff --git a/crypto/randentropy/rand_entropy.go b/crypto/randentropy/rand_entropy.go new file mode 100644 index 000000000..28181030c --- /dev/null +++ b/crypto/randentropy/rand_entropy.go @@ -0,0 +1,82 @@ +package randentropy + +import ( + crand "crypto/rand" + "encoding/binary" + "github.com/ethereum/go-ethereum/crypto/sha3" + "io" + "os" + "strings" + "time" +) + +type RandEntropy struct { +} + +func (*RandEntropy) Read(bytes []byte) (n int, err error) { + readBytes := GetEntropyMixed(len(bytes)) + copy(bytes, readBytes) + return len(bytes), nil +} + +// TODO: copied from crypto.go , move to sha3 package? +func Sha3(data []byte) []byte { + d := sha3.NewKeccak256() + d.Write(data) + + return d.Sum(nil) +} + +// TODO: verify. this needs to be audited +// we start with crypt/rand, then XOR in additional entropy from OS +func GetEntropyMixed(n int) []byte { + startTime := time.Now().UnixNano() + // for each source, we take SHA3 of the source and use it as seed to math/rand + // then read bytes from it and XOR them onto the bytes read from crypto/rand + mainBuff := GetEntropyCSPRNG(n) + // 1. OS entropy sources + startTimeBytes := make([]byte, 32) + binary.PutVarint(startTimeBytes, startTime) + startTimeHash := Sha3(startTimeBytes) + mixBytes(mainBuff, startTimeHash) + + pid := os.Getpid() + pidBytes := make([]byte, 32) + binary.PutUvarint(pidBytes, uint64(pid)) + pidHash := Sha3(pidBytes) + mixBytes(mainBuff, pidHash) + + osEnv := os.Environ() + osEnvBytes := []byte(strings.Join(osEnv, "")) + osEnvHash := Sha3(osEnvBytes) + mixBytes(mainBuff, osEnvHash) + + // not all OS have hostname in env variables + osHostName, err := os.Hostname() + if err != nil { + osHostNameBytes := []byte(osHostName) + osHostNameHash := Sha3(osHostNameBytes) + mixBytes(mainBuff, osHostNameHash) + } + return mainBuff +} + +func GetEntropyCSPRNG(n int) []byte { + mainBuff := make([]byte, n) + _, err := io.ReadFull(crand.Reader, mainBuff) + if err != nil { + panic("reading from crypto/rand failed: " + err.Error()) + } + return mainBuff +} + +func mixBytes(buff []byte, mixBuff []byte) []byte { + bytesToMix := len(buff) + if bytesToMix > 32 { + bytesToMix = 32 + } + for i := 0; i < bytesToMix; i++ { + buff[i] ^= mixBuff[i] + } + return buff +} diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index c01598b84..c1e37629e 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -15,6 +15,7 @@ import "C" import ( "bytes" "errors" + "github.com/ethereum/go-ethereum/crypto/randentropy" "unsafe" ) @@ -68,7 +69,7 @@ func GenerateKeyPair() ([]byte, []byte) { const seckey_len = 32 var pubkey []byte = make([]byte, pubkey_len) - var seckey []byte = RandByte(seckey_len) + var seckey []byte = randentropy.GetEntropyMixed(seckey_len) var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0])) var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) @@ -124,7 +125,7 @@ int secp256k1_ecdsa_sign_compact(const unsigned char *msg, int msglen, */ func Sign(msg []byte, seckey []byte) ([]byte, error) { - nonce := RandByte(32) + nonce := randentropy.GetEntropyMixed(32) var sig []byte = make([]byte, 65) var recid C.int -- cgit v1.2.3 From 39434e383b9e6fee30371afd5a9841de75671f56 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Fri, 13 Feb 2015 15:38:18 +0100 Subject: Unexport randEntropy type and use exported Reader instead --- crypto/key_store_test.go | 6 +++--- crypto/randentropy/rand_entropy.go | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'crypto') diff --git a/crypto/key_store_test.go b/crypto/key_store_test.go index a136ba992..485d8f536 100644 --- a/crypto/key_store_test.go +++ b/crypto/key_store_test.go @@ -9,7 +9,7 @@ import ( func TestKeyStorePlain(t *testing.T) { ks := NewKeyStorePlain(DefaultDataDir()) pass := "" // not used but required by API - k1, err := ks.GenerateNewKey(new(randentropy.RandEntropy), pass) + k1, err := ks.GenerateNewKey(randentropy.Reader, pass) if err != nil { t.Fatal(err) } @@ -37,7 +37,7 @@ func TestKeyStorePlain(t *testing.T) { func TestKeyStorePassphrase(t *testing.T) { ks := NewKeyStorePassphrase(DefaultDataDir()) pass := "foo" - k1, err := ks.GenerateNewKey(new(randentropy.RandEntropy), pass) + k1, err := ks.GenerateNewKey(randentropy.Reader, pass) if err != nil { t.Fatal(err) } @@ -63,7 +63,7 @@ func TestKeyStorePassphrase(t *testing.T) { func TestKeyStorePassphraseDecryptionFail(t *testing.T) { ks := NewKeyStorePassphrase(DefaultDataDir()) pass := "foo" - k1, err := ks.GenerateNewKey(new(randentropy.RandEntropy), pass) + k1, err := ks.GenerateNewKey(randentropy.Reader, pass) if err != nil { t.Fatal(err) } diff --git a/crypto/randentropy/rand_entropy.go b/crypto/randentropy/rand_entropy.go index 28181030c..b87fa564e 100644 --- a/crypto/randentropy/rand_entropy.go +++ b/crypto/randentropy/rand_entropy.go @@ -10,10 +10,12 @@ import ( "time" ) -type RandEntropy struct { +var Reader io.Reader = &randEntropy{} + +type randEntropy struct { } -func (*RandEntropy) Read(bytes []byte) (n int, err error) { +func (*randEntropy) Read(bytes []byte) (n int, err error) { readBytes := GetEntropyMixed(len(bytes)) copy(bytes, readBytes) return len(bytes), nil -- cgit v1.2.3 From f35d62b75977231bb45d2e298c3f39744c875e67 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Fri, 13 Feb 2015 18:22:36 +0100 Subject: Remove secp256_rand.go and update tests --- crypto/secp256k1/secp256_rand.go | 97 ---------------------------------------- crypto/secp256k1/secp256_test.go | 21 ++++----- 2 files changed, 11 insertions(+), 107 deletions(-) delete mode 100644 crypto/secp256k1/secp256_rand.go (limited to 'crypto') diff --git a/crypto/secp256k1/secp256_rand.go b/crypto/secp256k1/secp256_rand.go deleted file mode 100644 index bb10025fc..000000000 --- a/crypto/secp256k1/secp256_rand.go +++ /dev/null @@ -1,97 +0,0 @@ -package secp256k1 - -import ( - crand "crypto/rand" - "io" - mrand "math/rand" - "os" - "strings" - "time" -) - -/* -Note: - -- On windows cryto/rand uses CrytoGenRandom which uses RC4 which is insecure -- Android random number generator is known to be insecure. -- Linux uses /dev/urandom , which is thought to be secure and uses entropy pool - -Therefore the output is salted. -*/ - -//finalizer from MurmerHash3 -func mmh3f(key uint64) uint64 { - key ^= key >> 33 - key *= 0xff51afd7ed558ccd - key ^= key >> 33 - key *= 0xc4ceb9fe1a85ec53 - key ^= key >> 33 - return key -} - -//knuth hash -func knuth_hash(in []byte) uint64 { - var acc uint64 = 3074457345618258791 - for i := 0; i < len(in); i++ { - acc += uint64(in[i]) - acc *= 3074457345618258799 - } - return acc -} - -var _rand *mrand.Rand - -func init() { - var seed1 uint64 = mmh3f(uint64(time.Now().UnixNano())) - var seed2 uint64 = knuth_hash([]byte(strings.Join(os.Environ(), ""))) - var seed3 uint64 = mmh3f(uint64(os.Getpid())) - - _rand = mrand.New(mrand.NewSource(int64(seed1 ^ seed2 ^ seed3))) -} - -func saltByte(n int) []byte { - buff := make([]byte, n) - for i := 0; i < len(buff); i++ { - var v uint64 = uint64(_rand.Int63()) - var b byte - for j := 0; j < 8; j++ { - b ^= byte(v & 0xff) - v = v >> 8 - } - buff[i] = b - } - return buff -} - -//On Unix-like systems, Reader reads from /dev/urandom. -//On Windows systems, Reader uses the CryptGenRandom API. - -//use entropy pool etc and cryptographic random number generator -//mix in time -//mix in mix in cpu cycle count -func RandByte(n int) []byte { - buff := make([]byte, n) - ret, err := io.ReadFull(crand.Reader, buff) - if len(buff) != ret || err != nil { - return nil - } - - buff2 := saltByte(n) - for i := 0; i < n; i++ { - buff[i] ^= buff2[2] - } - return buff -} - -/* - On Unix-like systems, Reader reads from /dev/urandom. - On Windows systems, Reader uses the CryptGenRandom API. -*/ -func RandByteWeakCrypto(n int) []byte { - buff := make([]byte, n) - ret, err := io.ReadFull(crand.Reader, buff) - if len(buff) != ret || err != nil { - return nil - } - return buff -} diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index 468c50db9..5e657cd72 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -3,6 +3,7 @@ package secp256k1 import ( "bytes" "fmt" + "github.com/ethereum/go-ethereum/crypto/randentropy" "log" "testing" ) @@ -12,7 +13,7 @@ const SigSize = 65 //64+1 func Test_Secp256_00(t *testing.T) { - var nonce []byte = RandByte(32) //going to get bitcoins stolen! + var nonce []byte = randentropy.GetEntropyMixed(32) //going to get bitcoins stolen! if len(nonce) != 32 { t.Fatal() @@ -50,7 +51,7 @@ func Test_Secp256_01(t *testing.T) { //test size of messages func Test_Secp256_02s(t *testing.T) { pubkey, seckey := GenerateKeyPair() - msg := RandByte(32) + msg := randentropy.GetEntropyMixed(32) sig, _ := Sign(msg, seckey) CompactSigTest(sig) if sig == nil { @@ -73,7 +74,7 @@ func Test_Secp256_02s(t *testing.T) { //test signing message func Test_Secp256_02(t *testing.T) { pubkey1, seckey := GenerateKeyPair() - msg := RandByte(32) + msg := randentropy.GetEntropyMixed(32) sig, _ := Sign(msg, seckey) if sig == nil { t.Fatal("Signature nil") @@ -96,7 +97,7 @@ func Test_Secp256_02(t *testing.T) { //test pubkey recovery func Test_Secp256_02a(t *testing.T) { pubkey1, seckey1 := GenerateKeyPair() - msg := RandByte(32) + msg := randentropy.GetEntropyMixed(32) sig, _ := Sign(msg, seckey1) if sig == nil { @@ -125,7 +126,7 @@ func Test_Secp256_02a(t *testing.T) { func Test_Secp256_03(t *testing.T) { _, seckey := GenerateKeyPair() for i := 0; i < TESTS; i++ { - msg := RandByte(32) + msg := randentropy.GetEntropyMixed(32) sig, _ := Sign(msg, seckey) CompactSigTest(sig) @@ -141,7 +142,7 @@ func Test_Secp256_03(t *testing.T) { func Test_Secp256_04(t *testing.T) { for i := 0; i < TESTS; i++ { pubkey1, seckey := GenerateKeyPair() - msg := RandByte(32) + msg := randentropy.GetEntropyMixed(32) sig, _ := Sign(msg, seckey) CompactSigTest(sig) @@ -164,7 +165,7 @@ func Test_Secp256_04(t *testing.T) { // -SIPA look at this func randSig() []byte { - sig := RandByte(65) + sig := randentropy.GetEntropyMixed(65) sig[32] &= 0x70 sig[64] %= 4 return sig @@ -172,7 +173,7 @@ func randSig() []byte { func Test_Secp256_06a_alt0(t *testing.T) { pubkey1, seckey := GenerateKeyPair() - msg := RandByte(32) + msg := randentropy.GetEntropyMixed(32) sig, _ := Sign(msg, seckey) if sig == nil { @@ -203,12 +204,12 @@ func Test_Secp256_06a_alt0(t *testing.T) { func Test_Secp256_06b(t *testing.T) { pubkey1, seckey := GenerateKeyPair() - msg := RandByte(32) + msg := randentropy.GetEntropyMixed(32) sig, _ := Sign(msg, seckey) fail_count := 0 for i := 0; i < TESTS; i++ { - msg = RandByte(32) + msg = randentropy.GetEntropyMixed(32) pubkey2, _ := RecoverPubkey(msg, sig) if bytes.Equal(pubkey1, pubkey2) == true { t.Fail() -- cgit v1.2.3 From 84f7c966f725ef0f5c62b4427857d112c0d1e828 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 14 Feb 2015 00:25:47 +0100 Subject: Moved ECIES to repo & added secondary title for webview * ECIES moved from obscuren to ethereum * Added html META[name=badge] to reflect menuItem.secondaryTitle --- crypto/crypto.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crypto') diff --git a/crypto/crypto.go b/crypto/crypto.go index 2c8f82977..e59250eb2 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -18,10 +18,10 @@ import ( "code.google.com/p/go-uuid/uuid" "code.google.com/p/go.crypto/pbkdf2" "code.google.com/p/go.crypto/ripemd160" + "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/ethutil" - "github.com/obscuren/ecies" ) func init() { -- cgit v1.2.3 From 238f39a42ecaf489f0dbbf880e6d8f8e86791f1d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 15 Feb 2015 02:20:31 +0100 Subject: Validate seckey when generating pub key --- crypto/secp256k1/secp256.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'crypto') diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index c1e37629e..4864e8d09 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -15,8 +15,9 @@ import "C" import ( "bytes" "errors" - "github.com/ethereum/go-ethereum/crypto/randentropy" "unsafe" + + "github.com/ethereum/go-ethereum/crypto/randentropy" ) //#define USE_FIELD_5X64 @@ -85,6 +86,10 @@ func GenerateKeyPair() ([]byte, []byte) { } func GeneratePubKey(seckey []byte) ([]byte, error) { + if err := VerifySeckeyValidity(seckey); err != nil { + return nil, err + } + pubkey_len := C.int(65) const seckey_len = 32 -- cgit v1.2.3 From 8f69b5c7a221e51e41985e82ef0a22ddc6444b5f Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 16 Feb 2015 13:19:57 +0100 Subject: Added invalid sec key test --- crypto/secp256k1/secp256_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'crypto') diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index 5e657cd72..3599fde38 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -3,9 +3,10 @@ package secp256k1 import ( "bytes" "fmt" - "github.com/ethereum/go-ethereum/crypto/randentropy" "log" "testing" + + "github.com/ethereum/go-ethereum/crypto/randentropy" ) const TESTS = 10000 // how many tests @@ -227,3 +228,11 @@ func Test_Secp256_06b(t *testing.T) { fmt.Printf("ERROR: Accepted signature for %v of %v random messages\n", fail_count, TESTS) } } + +func TestInvalidKey(t *testing.T) { + p1 := make([]byte, 32) + err := VerifySeckeyValidity(p1) + if err == nil { + t.Errorf("pvk %x varify sec key should have returned error", p1) + } +} -- cgit v1.2.3 From 119bea22aaf061d5b3e6a9abee9b1b95655b9fbe Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 17 Feb 2015 13:05:58 +0100 Subject: crypto: switch to golang.org/x/crypto code.google.com/p/go.crypto is deprecated and will cause problems in future versions of Go. --- crypto/crypto.go | 4 ++-- crypto/key_store_passphrase.go | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'crypto') diff --git a/crypto/crypto.go b/crypto/crypto.go index e59250eb2..90e2c8939 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -16,12 +16,12 @@ import ( "errors" "code.google.com/p/go-uuid/uuid" - "code.google.com/p/go.crypto/pbkdf2" - "code.google.com/p/go.crypto/ripemd160" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/ethutil" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/ripemd160" ) func init() { diff --git a/crypto/key_store_passphrase.go b/crypto/key_store_passphrase.go index 74408f874..7d21b6604 100644 --- a/crypto/key_store_passphrase.go +++ b/crypto/key_store_passphrase.go @@ -20,6 +20,7 @@ * @date 2015 * */ + /* This key store behaves as KeyStorePlain with the difference that @@ -64,17 +65,18 @@ package crypto import ( "bytes" - "code.google.com/p/go-uuid/uuid" - "code.google.com/p/go.crypto/scrypt" "crypto/aes" "crypto/cipher" "encoding/hex" "encoding/json" "errors" - "github.com/ethereum/go-ethereum/crypto/randentropy" "io" "os" "path" + + "code.google.com/p/go-uuid/uuid" + "github.com/ethereum/go-ethereum/crypto/randentropy" + "golang.org/x/crypto/scrypt" ) const ( -- cgit v1.2.3