diff --git a/.codecov.yml b/.codecov.yml
index 1c90602afd..602a444602 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -11,7 +11,7 @@ coverage:
project:
default:
target: auto
- threshold: 1%
+ threshold: 1.5%
patch: no
changes: no
diff --git a/WORKSPACE b/WORKSPACE
index 8cce23dcc2..98cc728ef2 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -650,6 +650,12 @@ go_repository(
importpath = "github.com/boltdb/bolt",
)
+go_repository(
+ name = "com_github_pborman_uuid",
+ commit = "8b1b92947f46224e3b97bb1a3a5b0382be00d31e",
+ importpath = "github.com/pborman/uuid",
+)
+
go_repository(
name = "com_github_libp2p_go_buffer_pool",
commit = "058210c5a0d042677367d923eb8a6dc072a15f7f",
diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go
index 2b2fc9f104..8ddb3c9a45 100644
--- a/beacon-chain/blockchain/service_test.go
+++ b/beacon-chain/blockchain/service_test.go
@@ -7,11 +7,10 @@ import (
"math/big"
"testing"
- "github.com/prysmaticlabs/prysm/beacon-chain/casper"
-
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
+ "github.com/prysmaticlabs/prysm/beacon-chain/casper"
"github.com/prysmaticlabs/prysm/beacon-chain/db"
"github.com/prysmaticlabs/prysm/beacon-chain/internal"
"github.com/prysmaticlabs/prysm/beacon-chain/powchain"
diff --git a/beacon-chain/casper/incentives_test.go b/beacon-chain/casper/incentives_test.go
index 1bf9bacae0..768f9cf574 100644
--- a/beacon-chain/casper/incentives_test.go
+++ b/beacon-chain/casper/incentives_test.go
@@ -4,10 +4,9 @@ import (
"math"
"testing"
- "github.com/prysmaticlabs/prysm/shared/mathutil"
-
"github.com/prysmaticlabs/prysm/beacon-chain/params"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
+ "github.com/prysmaticlabs/prysm/shared/mathutil"
)
func NewValidators() []*pb.ValidatorRecord {
diff --git a/beacon-chain/main.go b/beacon-chain/main.go
index c2dac6fd5f..bf2d496203 100644
--- a/beacon-chain/main.go
+++ b/beacon-chain/main.go
@@ -75,6 +75,8 @@ VERSION:
cmd.EnableTracingFlag,
cmd.TracingEndpointFlag,
cmd.TraceSampleFractionFlag,
+ cmd.KeystorePasswordFlag,
+ cmd.KeystoreDirectoryFlag,
debug.PProfFlag,
debug.PProfAddrFlag,
debug.PProfPortFlag,
diff --git a/beacon-chain/node/p2p_config.go b/beacon-chain/node/p2p_config.go
index 9eab1268ab..344ac010d6 100644
--- a/beacon-chain/node/p2p_config.go
+++ b/beacon-chain/node/p2p_config.go
@@ -2,12 +2,11 @@ package node
import (
"github.com/golang/protobuf/proto"
+ pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/cmd"
"github.com/prysmaticlabs/prysm/shared/p2p"
"github.com/prysmaticlabs/prysm/shared/p2p/adapter/tracer"
"github.com/urfave/cli"
-
- pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
)
var topicMappings = map[pb.Topic]proto.Message{
diff --git a/beacon-chain/simulator/service.go b/beacon-chain/simulator/service.go
index c36d1ca45c..dd4ae84346 100644
--- a/beacon-chain/simulator/service.go
+++ b/beacon-chain/simulator/service.go
@@ -6,10 +6,9 @@ import (
"fmt"
"time"
+ "github.com/ethereum/go-ethereum/common"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
-
- "github.com/ethereum/go-ethereum/common"
"github.com/prysmaticlabs/prysm/beacon-chain/params"
"github.com/prysmaticlabs/prysm/beacon-chain/types"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
diff --git a/shared/bls/bls.go b/shared/bls/bls.go
index 4cab3c02a5..3dc16388f4 100644
--- a/shared/bls/bls.go
+++ b/shared/bls/bls.go
@@ -3,17 +3,58 @@
// aggregating BLS signatures used by Ethereum Serenity.
package bls
-import "fmt"
+import (
+ "fmt"
+ "math/big"
+)
// Signature used in the BLS signature scheme.
type Signature struct{}
// SecretKey used in the BLS scheme.
-type SecretKey struct{}
+type SecretKey struct {
+ K *big.Int
+}
// PublicKey corresponding to secret key used in the BLS scheme.
type PublicKey struct{}
+// PublicKey returns the corresponding public key for the
+// Secret Key
+func (s *SecretKey) PublicKey() (*PublicKey, error) {
+ return &PublicKey{}, nil
+}
+
+// BufferedSecretKey returns the secret key in a byte format.
+func (s *SecretKey) BufferedSecretKey() []byte {
+ return s.K.Bytes()
+}
+
+// BufferedPublicKey returns the public key in a byte format.
+func (p *PublicKey) BufferedPublicKey() []byte {
+ return []byte{}
+}
+
+// UnBufferSecretKey takes the byte representation of a secret key
+// and sets it to a big int of the underlying secret key object.
+func (s *SecretKey) UnBufferSecretKey(bufferedKey []byte) {
+ s.K = big.NewInt(0).SetBytes(bufferedKey)
+
+}
+
+// UnBufferPublicKey takes the byte representation of a public key
+// and sets it to a big int of the underlying public key object.
+func (p *PublicKey) UnBufferPublicKey(bufferedKey []byte) {
+
+}
+
+// GenerateKey generates a new secret key using a seed.
+func GenerateKey(seed []byte) *SecretKey {
+ return &SecretKey{
+ K: big.NewInt(0).SetBytes(seed),
+ }
+}
+
// Sign a message using a secret key - in a beacon/validator client,
// this key will come from and be unlocked from the account keystore.
func Sign(sec *SecretKey, msg []byte) (*Signature, error) {
diff --git a/shared/bls/bls_test.go b/shared/bls/bls_test.go
index 6f35c925d8..d6f7413749 100644
--- a/shared/bls/bls_test.go
+++ b/shared/bls/bls_test.go
@@ -12,6 +12,13 @@ func TestSign(t *testing.T) {
}
}
+func TestPublicKey(t *testing.T) {
+ sk := &SecretKey{}
+ if _, err := sk.PublicKey(); err != nil {
+ t.Errorf("Expected nil error, received %v", err)
+ }
+}
+
func TestVerifySig(t *testing.T) {
pk := &PublicKey{}
msg := []byte{}
diff --git a/shared/cmd/flags.go b/shared/cmd/flags.go
index 764c1fcf5a..0745efcc88 100644
--- a/shared/cmd/flags.go
+++ b/shared/cmd/flags.go
@@ -47,7 +47,7 @@ var (
Name: "enable-tracing",
Usage: "Enable request tracing.",
}
- // TracingEndpointFlag flag defines the http enpoint for serving traces to Jaeger.
+ // TracingEndpointFlag flag defines the http endpoint for serving traces to Jaeger.
TracingEndpointFlag = cli.StringFlag{
Name: "tracing-endpoint",
Usage: "Tracing endpoint defines where beacon chain traces are exposed to Jaeger.",
@@ -60,4 +60,15 @@ var (
Usage: "Indicate what fraction of p2p messages are sampled for tracing.",
Value: 0.20,
}
+ // KeystoreDirectoryFlag defines a flag to indicate where the keystore of the user
+ // is located.
+ KeystoreDirectoryFlag = DirectoryFlag{
+ Name: "keystore-dir",
+ Usage: "Keystore directory indicates which directory the keystore is located.",
+ }
+ // KeystorePasswordFlag defines the password that will unlock the keystore file.
+ KeystorePasswordFlag = cli.StringFlag{
+ Name: "keystore-password",
+ Usage: "Keystore password is used to unlock the keystore so that the users decrypted keys can be used.",
+ }
)
diff --git a/shared/keystore/BUILD.bazel b/shared/keystore/BUILD.bazel
new file mode 100644
index 0000000000..e933947f82
--- /dev/null
+++ b/shared/keystore/BUILD.bazel
@@ -0,0 +1,33 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "key.go",
+ "keystore.go",
+ "utils.go",
+ ],
+ importpath = "github.com/prysmaticlabs/prysm/shared/keystore",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//shared/bls:go_default_library",
+ "@com_github_ethereum_go_ethereum//common/math:go_default_library",
+ "@com_github_ethereum_go_ethereum//crypto:go_default_library",
+ "@com_github_pborman_uuid//:go_default_library",
+ "@org_golang_x_crypto//pbkdf2:go_default_library",
+ "@org_golang_x_crypto//scrypt:go_default_library",
+ ],
+)
+
+go_test(
+ name = "go_default_test",
+ srcs = [
+ "key_test.go",
+ "keystore_test.go",
+ ],
+ embed = [":go_default_library"],
+ deps = [
+ "//shared/bls:go_default_library",
+ "@com_github_pborman_uuid//:go_default_library",
+ ],
+)
diff --git a/shared/keystore/key.go b/shared/keystore/key.go
new file mode 100644
index 0000000000..800b989998
--- /dev/null
+++ b/shared/keystore/key.go
@@ -0,0 +1,204 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// Modified by Prysmatic Labs 2018
+//
+// 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 (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/pborman/uuid"
+ "github.com/prysmaticlabs/prysm/shared/bls"
+)
+
+const (
+ keyHeaderKDF = "scrypt"
+
+ // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB
+ // memory and taking approximately 1s CPU time on a modern processor.
+ StandardScryptN = 1 << 18
+
+ // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB
+ // memory and taking approximately 1s CPU time on a modern processor.
+ StandardScryptP = 1
+
+ // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB
+ // memory and taking approximately 100ms CPU time on a modern processor.
+ LightScryptN = 1 << 12
+
+ // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB
+ // memory and taking approximately 100ms CPU time on a modern processor.
+ LightScryptP = 6
+
+ scryptR = 8
+ scryptDKLen = 32
+)
+
+// Key is the object that stores all the user data related to their public/secret keys.
+type Key struct {
+ ID uuid.UUID // Version 4 "random" for unique id not derived from key data
+
+ PublicKey *bls.PublicKey // Represents the public key of the user.
+
+ SecretKey *bls.SecretKey // Represents the private key of the user.
+}
+
+type keyStore interface {
+ // Loads and decrypts the key from disk.
+ GetKey(filename string, password 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 {
+ PublicKey string `json:"address"`
+ SecretKey string `json:"privatekey"`
+ ID string `json:"id"`
+}
+
+type encryptedKeyJSON struct {
+ PublicKey string `json:"publickey"`
+ Crypto cryptoJSON `json:"crypto"`
+ ID string `json:"id"`
+}
+
+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"`
+}
+
+// MarshalJSON marshalls a key struct into a JSON blob.
+func (k *Key) MarshalJSON() (j []byte, err error) {
+ jStruct := plainKeyJSON{
+ hex.EncodeToString(k.PublicKey.BufferedPublicKey()),
+ hex.EncodeToString(k.SecretKey.BufferedSecretKey()),
+ k.ID.String(),
+ }
+ j, err = json.Marshal(jStruct)
+ return j, err
+}
+
+// UnmarshalJSON unmarshals a blob into a key struct.
+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 = uuid.Parse(keyJSON.ID)
+ k.ID = *u
+ pubkey, err := hex.DecodeString(keyJSON.PublicKey)
+ if err != nil {
+ return err
+ }
+ seckey, err := hex.DecodeString(keyJSON.SecretKey)
+ if err != nil {
+ return err
+ }
+
+ k.PublicKey.UnBufferPublicKey(pubkey)
+ k.SecretKey.UnBufferSecretKey(seckey)
+
+ return nil
+}
+
+func newKeyFromBLS(blsKey *bls.SecretKey) (*Key, error) {
+ id := uuid.NewRandom()
+ pubkey, err := blsKey.PublicKey()
+ if err != nil {
+ return nil, err
+ }
+ key := &Key{
+ ID: id,
+ PublicKey: pubkey,
+ SecretKey: blsKey,
+ }
+ return key, nil
+}
+
+// NewKey generates a new random key.
+func NewKey(rand io.Reader) (*Key, error) {
+ randBytes := make([]byte, 64)
+ _, err := rand.Read(randBytes)
+ if err != nil {
+ return nil, fmt.Errorf("key generation: could not read from random source: %v", err)
+ }
+ secretKey := bls.GenerateKey(randBytes)
+
+ return newKeyFromBLS(secretKey)
+}
+
+func storeNewRandomKey(ks keyStore, rand io.Reader, password string) error {
+ key, err := NewKey(rand)
+ if err != nil {
+ return err
+ }
+
+ if err := ks.StoreKey(ks.JoinPath(keyFileName(key.PublicKey)), key, password); err != nil {
+ zeroKey(key.SecretKey)
+ return err
+ }
+ return nil
+}
+
+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 {
+ newErr := f.Close()
+ if newErr != nil {
+ err = newErr
+ }
+ newErr = os.Remove(f.Name())
+ if newErr != nil {
+ err = newErr
+ }
+ return err
+ }
+ if err := f.Close(); err != nil {
+ return err
+ }
+ return os.Rename(f.Name(), file)
+}
diff --git a/shared/keystore/key_test.go b/shared/keystore/key_test.go
new file mode 100644
index 0000000000..a55c303cf1
--- /dev/null
+++ b/shared/keystore/key_test.go
@@ -0,0 +1,113 @@
+package keystore
+
+import (
+ "bytes"
+ "crypto/rand"
+ "io/ioutil"
+ "math/big"
+ "os"
+ "testing"
+
+ "github.com/pborman/uuid"
+ "github.com/prysmaticlabs/prysm/shared/bls"
+)
+
+func TestMarshalAndUnmarshal(t *testing.T) {
+ testID := uuid.NewRandom()
+ blsKey := &bls.SecretKey{
+ K: big.NewInt(10),
+ }
+ key := &Key{
+ ID: testID,
+ SecretKey: blsKey,
+ }
+ marshalledObject, err := key.MarshalJSON()
+ if err != nil {
+ t.Fatalf("unable to marshall key %v", err)
+ }
+ newKey := &Key{
+ ID: []byte{},
+ SecretKey: &bls.SecretKey{
+ K: big.NewInt(0),
+ },
+ }
+
+ err = newKey.UnmarshalJSON(marshalledObject)
+ if err != nil {
+ t.Fatalf("unable to unmarshall object %v", err)
+ }
+
+ if !bytes.Equal([]byte(newKey.ID), []byte(testID)) {
+ t.Fatalf("retrieved id not the same as pre serialized id: %v ", newKey.ID)
+ }
+}
+
+func TestStoreRandomKey(t *testing.T) {
+ tmpdir := os.TempDir()
+ filedir := tmpdir + "/keystore"
+ ks := &keyStorePassphrase{
+ keysDirPath: filedir,
+ scryptN: LightScryptN,
+ scryptP: LightScryptP,
+ }
+
+ reader := rand.Reader
+
+ if err := storeNewRandomKey(ks, reader, "password"); err != nil {
+ t.Fatalf("storage of random key unsuccessful %v", err)
+ }
+
+ if err := os.RemoveAll(filedir); err != nil {
+ t.Errorf("unable to remove temporary files %v", err)
+ }
+
+}
+func TestNewKeyFromBLS(t *testing.T) {
+ blskey := &bls.SecretKey{
+ K: big.NewInt(20),
+ }
+
+ key, err := newKeyFromBLS(blskey)
+ if err != nil {
+ t.Fatalf("could not get new key from bls %v", err)
+ }
+
+ expectedNum := big.NewInt(20)
+
+ if expectedNum.Cmp(key.SecretKey.K) != 0 {
+ t.Fatalf("secret key is not of the expected value %d", key.SecretKey.K)
+ }
+
+ reader := rand.Reader
+
+ _, err = NewKey(reader)
+ if err != nil {
+ t.Fatalf("random key unable to be generated: %v", err)
+ }
+
+}
+
+func TestWriteFile(t *testing.T) {
+ tmpdir := os.TempDir()
+ filedir := tmpdir + "/keystore"
+
+ testKeystore := []byte{'t', 'e', 's', 't'}
+
+ err := writeKeyFile(filedir, testKeystore)
+ if err != nil {
+ t.Fatalf("unable to write file %v", err)
+ }
+
+ keystore, err := ioutil.ReadFile(filedir)
+ if err != nil {
+ t.Fatalf("unable to retrieve file %v", err)
+ }
+
+ if !bytes.Equal(keystore, testKeystore) {
+ t.Fatalf("retrieved keystore is not the same %v", keystore)
+ }
+
+ if err := os.RemoveAll(filedir); err != nil {
+ t.Errorf("unable to remove temporary files %v", err)
+ }
+}
diff --git a/shared/keystore/keystore.go b/shared/keystore/keystore.go
new file mode 100644
index 0000000000..c882305c7b
--- /dev/null
+++ b/shared/keystore/keystore.go
@@ -0,0 +1,245 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// Modified by Prysmatic Labs 2018
+//
+// 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/aes"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "path/filepath"
+
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/pborman/uuid"
+ "github.com/prysmaticlabs/prysm/shared/bls"
+ "golang.org/x/crypto/pbkdf2"
+ "golang.org/x/crypto/scrypt"
+)
+
+var (
+ // ErrDecrypt is the standard error message when decryption is a failure.
+ ErrDecrypt = errors.New("could not decrypt key with given passphrase")
+)
+
+type keyStorePassphrase struct {
+ keysDirPath string
+ scryptN int
+ scryptP int
+}
+
+// RetrievePubKey retrieves the public key from the keystore.
+func RetrievePubKey(directory string, password string) (*bls.PublicKey, error) {
+
+ ks := keyStorePassphrase{
+ keysDirPath: directory,
+ scryptN: StandardScryptN,
+ scryptP: StandardScryptP,
+ }
+ key, err := ks.GetKey(ks.keysDirPath, password)
+ return key.PublicKey, err
+}
+
+func (ks keyStorePassphrase) GetKey(filename, password string) (*Key, error) {
+ // Load the key from the keystore and decrypt its contents
+ // #nosec G304
+ keyjson, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ return DecryptKey(keyjson, password)
+}
+
+func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
+ keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
+ if err != nil {
+ return err
+ }
+ return writeKeyFile(filename, keyjson)
+}
+
+func (ks keyStorePassphrase) JoinPath(filename string) string {
+ if filepath.IsAbs(filename) {
+ return filename
+ }
+ return filepath.Join(ks.keysDirPath, filename)
+}
+
+// StoreRandomKey generates a key, encrypts with 'auth' and stores in the given directory
+func StoreRandomKey(dir, password string, scryptN, scryptP int) error {
+ err := storeNewRandomKey(keyStorePassphrase{dir, scryptN, scryptP}, rand.Reader, password)
+ return err
+}
+
+// EncryptKey encrypts a key using the specified scrypt parameters into a json
+// blob that can be decrypted later on.
+func EncryptKey(key *Key, password string, scryptN, scryptP int) ([]byte, error) {
+ authArray := []byte(password)
+ salt := make([]byte, 32)
+ if _, err := io.ReadFull(rand.Reader, salt); err != nil {
+ panic("reading from crypto/rand failed: " + err.Error())
+ }
+
+ derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
+ if err != nil {
+ return nil, err
+ }
+
+ encryptKey := derivedKey[:16]
+ keyBytes := math.PaddedBigBytes(key.SecretKey.K, 32)
+
+ iv := make([]byte, aes.BlockSize) // 16
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ return nil, errors.New("reading from crypto/rand failed: " + err.Error())
+ }
+
+ cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
+ if err != nil {
+ return nil, err
+ }
+ mac := crypto.Keccak256(derivedKey[16:32], cipherText)
+
+ scryptParamsJSON := make(map[string]interface{}, 5)
+ scryptParamsJSON["n"] = scryptN
+ scryptParamsJSON["r"] = scryptR
+ scryptParamsJSON["p"] = scryptP
+ scryptParamsJSON["dklen"] = scryptDKLen
+ scryptParamsJSON["salt"] = hex.EncodeToString(salt)
+
+ cipherParamsJSON := cipherparamsJSON{
+ IV: hex.EncodeToString(iv),
+ }
+
+ cryptoStruct := cryptoJSON{
+ Cipher: "aes-128-ctr",
+ CipherText: hex.EncodeToString(cipherText),
+ CipherParams: cipherParamsJSON,
+ KDF: keyHeaderKDF,
+ KDFParams: scryptParamsJSON,
+ MAC: hex.EncodeToString(mac),
+ }
+ encryptedJSON := encryptedKeyJSON{
+ hex.EncodeToString(key.PublicKey.BufferedPublicKey()),
+ cryptoStruct,
+ key.ID.String(),
+ }
+ return json.Marshal(encryptedJSON)
+}
+
+// DecryptKey decrypts a key from a json blob, returning the private key itself.
+func DecryptKey(keyjson []byte, password string) (*Key, error) {
+
+ var keyBytes, keyID []byte
+ var err error
+
+ k := new(encryptedKeyJSON)
+ if err := json.Unmarshal(keyjson, k); err != nil {
+ return nil, err
+ }
+
+ keyBytes, keyID, err = decryptKeyJSON(k, password)
+ // Handle any decryption errors and return the key
+ if err != nil {
+ return nil, err
+ }
+
+ key := &bls.SecretKey{}
+ key.UnBufferSecretKey(keyBytes)
+ pubkey, err := key.PublicKey()
+ if err != nil {
+ return nil, err
+ }
+
+ return &Key{
+ ID: uuid.UUID(keyID),
+ PublicKey: pubkey,
+ SecretKey: key,
+ }, nil
+}
+
+func decryptKeyJSON(keyProtected *encryptedKeyJSON, auth string) (keyBytes []byte, keyID []byte, err error) {
+ keyID = uuid.Parse(keyProtected.ID)
+ if keyProtected.Crypto.Cipher != "aes-128-ctr" {
+ return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher)
+ }
+
+ mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
+ if !bytes.Equal(calculatedMAC, mac) {
+ return nil, nil, ErrDecrypt
+ }
+
+ plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
+ if err != nil {
+ return nil, nil, err
+ }
+ return plainText, keyID, nil
+}
+
+func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
+ authArray := []byte(auth)
+ salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
+ if err != nil {
+ return nil, err
+ }
+ dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
+
+ if cryptoJSON.KDF == keyHeaderKDF {
+ n := ensureInt(cryptoJSON.KDFParams["n"])
+ r := ensureInt(cryptoJSON.KDFParams["r"])
+ p := ensureInt(cryptoJSON.KDFParams["p"])
+ return scrypt.Key(authArray, salt, n, r, p, dkLen)
+
+ } else if cryptoJSON.KDF == "pbkdf2" {
+ c := ensureInt(cryptoJSON.KDFParams["c"])
+ prf := cryptoJSON.KDFParams["prf"].(string)
+ if prf != "hmac-sha256" {
+ return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
+ }
+ key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
+ return key, nil
+ }
+
+ return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
+}
diff --git a/shared/keystore/keystore_test.go b/shared/keystore/keystore_test.go
new file mode 100644
index 0000000000..85a8be35bc
--- /dev/null
+++ b/shared/keystore/keystore_test.go
@@ -0,0 +1,75 @@
+package keystore
+
+import (
+ "bytes"
+ "crypto/rand"
+ "math/big"
+ "os"
+ "testing"
+
+ "github.com/pborman/uuid"
+ "github.com/prysmaticlabs/prysm/shared/bls"
+)
+
+func TestStoreandGetKey(t *testing.T) {
+ tmpdir := os.TempDir()
+ filedir := tmpdir + "/keystore"
+ ks := &keyStorePassphrase{
+ keysDirPath: filedir,
+ scryptN: LightScryptN,
+ scryptP: LightScryptP,
+ }
+
+ key, err := NewKey(rand.Reader)
+ if err != nil {
+ t.Fatalf("key generation failed %v", err)
+ }
+
+ if err := ks.StoreKey(filedir, key, "password"); err != nil {
+ t.Fatalf("unable to store key %v", err)
+ }
+
+ newkey, err := ks.GetKey(filedir, "password")
+ if err != nil {
+ t.Fatalf("unable to get key %v", err)
+ }
+
+ if newkey.SecretKey.K.Cmp(key.SecretKey.K) != 0 {
+ t.Fatalf("retrieved secret keys are not equal %v , %v", newkey.SecretKey.K, key.SecretKey.K)
+ }
+
+ if err := os.RemoveAll(filedir); err != nil {
+ t.Errorf("unable to remove temporary files %v", err)
+ }
+}
+func TestEncryptDecryptKey(t *testing.T) {
+ newID := uuid.NewRandom()
+ keyValue := big.NewInt(1e16)
+ password := "test"
+
+ key := &Key{
+ ID: newID,
+ SecretKey: &bls.SecretKey{
+ K: keyValue,
+ },
+ }
+
+ keyjson, err := EncryptKey(key, password, LightScryptN, LightScryptP)
+ if err != nil {
+ t.Fatalf("unable to encrypt key %v", err)
+ }
+
+ newkey, err := DecryptKey(keyjson, password)
+ if err != nil {
+ t.Fatalf("unable to decrypt keystore %v", err)
+ }
+
+ if !bytes.Equal(newkey.ID, newID) {
+ t.Fatalf("decrypted key's uuid doesn't match %v", newkey.ID)
+ }
+
+ if newkey.SecretKey.K.Cmp(keyValue) != 0 {
+ t.Fatalf("decrypted key's value is not equal %v", newkey.SecretKey.K)
+ }
+
+}
diff --git a/shared/keystore/utils.go b/shared/keystore/utils.go
new file mode 100644
index 0000000000..d978ddfb4c
--- /dev/null
+++ b/shared/keystore/utils.go
@@ -0,0 +1,75 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// Modified by Prysmatic Labs 2018
+//
+// 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 (
+ "crypto/aes"
+ "crypto/cipher"
+ "encoding/hex"
+ "fmt"
+ "time"
+
+ "github.com/prysmaticlabs/prysm/shared/bls"
+)
+
+func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
+ // AES-128 is selected due to size of encryptKey.
+ aesBlock, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ stream := cipher.NewCTR(aesBlock, iv)
+ outText := make([]byte, len(inText))
+ stream.XORKeyStream(outText, inText)
+ return outText, err
+}
+
+func ensureInt(x interface{}) int {
+ res, ok := x.(int)
+ if !ok {
+ res = int(x.(float64))
+ }
+ return res
+}
+
+// keyFileName implements the naming convention for keyfiles:
+// UTC---
+func keyFileName(pubkey *bls.PublicKey) string {
+ ts := time.Now().UTC()
+ return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(pubkey.BufferedPublicKey()))
+}
+
+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)
+}
+
+// zeroKey zeroes a private key in memory.
+func zeroKey(k *bls.SecretKey) {
+ b := k.K.Bits()
+ for i := range b {
+ b[i] = 0
+ }
+}
diff --git a/shared/p2p/service.go b/shared/p2p/service.go
index c53759c428..b2148a51cc 100644
--- a/shared/p2p/service.go
+++ b/shared/p2p/service.go
@@ -7,9 +7,6 @@ import (
"sync"
"github.com/golang/protobuf/proto"
- "github.com/prysmaticlabs/prysm/shared/event"
- "github.com/sirupsen/logrus"
-
ds "github.com/ipfs/go-datastore"
dsync "github.com/ipfs/go-datastore/sync"
libp2p "github.com/libp2p/go-libp2p"
@@ -17,6 +14,8 @@ import (
kaddht "github.com/libp2p/go-libp2p-kad-dht"
pubsub "github.com/libp2p/go-libp2p-pubsub"
rhost "github.com/libp2p/go-libp2p/p2p/host/routed"
+ "github.com/prysmaticlabs/prysm/shared/event"
+ "github.com/sirupsen/logrus"
)
// Sender represents a struct that is able to relay information via p2p.
diff --git a/validator/main.go b/validator/main.go
index 48227e192f..b44c10ffa1 100644
--- a/validator/main.go
+++ b/validator/main.go
@@ -69,6 +69,8 @@ VERSION:
cmd.EnableTracingFlag,
cmd.TracingEndpointFlag,
cmd.TraceSampleFractionFlag,
+ cmd.KeystorePasswordFlag,
+ cmd.KeystoreDirectoryFlag,
debug.PProfFlag,
debug.PProfAddrFlag,
debug.PProfPortFlag,
diff --git a/validator/node/BUILD.bazel b/validator/node/BUILD.bazel
index 0581e7b915..3e95acb288 100644
--- a/validator/node/BUILD.bazel
+++ b/validator/node/BUILD.bazel
@@ -4,11 +4,7 @@ go_test(
name = "go_default_test",
srcs = ["node_test.go"],
embed = [":go_default_library"],
- deps = [
- "//shared/testutil:go_default_library",
- "@com_github_sirupsen_logrus//hooks/test:go_default_library",
- "@com_github_urfave_cli//:go_default_library",
- ],
+ deps = ["@com_github_urfave_cli//:go_default_library"],
)
go_library(
@@ -25,6 +21,7 @@ go_library(
"//shared/cmd:go_default_library",
"//shared/database:go_default_library",
"//shared/debug:go_default_library",
+ "//shared/keystore:go_default_library",
"//shared/p2p:go_default_library",
"//shared/p2p/adapter/tracer:go_default_library",
"//validator/attester:go_default_library",
diff --git a/validator/node/node.go b/validator/node/node.go
index d3162a1142..4d3236d54a 100644
--- a/validator/node/node.go
+++ b/validator/node/node.go
@@ -15,6 +15,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/cmd"
"github.com/prysmaticlabs/prysm/shared/database"
"github.com/prysmaticlabs/prysm/shared/debug"
+ "github.com/prysmaticlabs/prysm/shared/keystore"
"github.com/prysmaticlabs/prysm/shared/p2p"
"github.com/prysmaticlabs/prysm/validator/attester"
"github.com/prysmaticlabs/prysm/validator/beacon"
@@ -60,17 +61,15 @@ func NewValidatorClient(ctx *cli.Context) (*ValidatorClient, error) {
var pubKey []byte
- inputKey := ctx.GlobalString(types.PubKeyFlag.Name)
- if inputKey == "" {
- var err error
- pubKey, err = GeneratePubKey()
+ keystorePath := ctx.GlobalString(cmd.KeystoreDirectoryFlag.Name)
+ password := ctx.GlobalString(cmd.KeystorePasswordFlag.Name)
+ if keystorePath != "" && password != "" {
+ blspubkey, err := keystore.RetrievePubKey(keystorePath, password)
if err != nil {
return nil, err
}
- log.Warnf("PublicKey not detected, generating a new one: %v", pubKey)
- } else {
- pubKey = []byte(inputKey)
+ pubKey = blspubkey.BufferedPublicKey()
}
if err := ValidatorClient.startDB(ctx); err != nil {
diff --git a/validator/node/node_test.go b/validator/node/node_test.go
index 96b902b05f..fb6ac66a20 100644
--- a/validator/node/node_test.go
+++ b/validator/node/node_test.go
@@ -4,24 +4,18 @@ import (
"flag"
"testing"
- "github.com/prysmaticlabs/prysm/shared/testutil"
- logTest "github.com/sirupsen/logrus/hooks/test"
"github.com/urfave/cli"
)
// Test that the sharding node can build with default flag values.
func TestNode_Builds(t *testing.T) {
- hook := logTest.NewGlobal()
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("datadir", "/tmp/datadir", "the node data directory")
- set.String("pukey", "f24f252", "set public key of validator")
context := cli.NewContext(app, set, nil)
_, err := NewValidatorClient(context)
if err != nil {
t.Fatalf("Failed to create ValidatorClient: %v", err)
}
-
- testutil.AssertLogsContain(t, hook, "PublicKey not detected, generating a new one")
}
diff --git a/validator/node/p2p_config.go b/validator/node/p2p_config.go
index 7fdb05f76c..ce20720645 100644
--- a/validator/node/p2p_config.go
+++ b/validator/node/p2p_config.go
@@ -2,12 +2,11 @@ package node
import (
"github.com/golang/protobuf/proto"
+ pb "github.com/prysmaticlabs/prysm/proto/sharding/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/cmd"
"github.com/prysmaticlabs/prysm/shared/p2p"
"github.com/prysmaticlabs/prysm/shared/p2p/adapter/tracer"
"github.com/urfave/cli"
-
- pb "github.com/prysmaticlabs/prysm/proto/sharding/p2p/v1"
)
var topicMappings = map[pb.Topic]proto.Message{
diff --git a/validator/types/collation.go b/validator/types/collation.go
index 74f825262a..1514726298 100644
--- a/validator/types/collation.go
+++ b/validator/types/collation.go
@@ -2,7 +2,6 @@ package types
import (
"fmt"
-
"math/big"
"github.com/ethereum/go-ethereum/common"