mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
Adding Basic Private Key Management (#671)
* adding flags * adding modified key utils * adding more funcs * more changes * more changes * documentation * changes to node * gazelle * fixing bazel build * gazelle * adding tests * more tests * addressing terence's feedback * adding geth header * test * changes * fixedd it * fixed marshalling * adding more to tests * gazelle * adding more tests * lint * add cov * cov * fix imports
This commit is contained in:
@@ -11,7 +11,7 @@ coverage:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 1%
|
||||
threshold: 1.5%
|
||||
patch: no
|
||||
changes: no
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -75,6 +75,8 @@ VERSION:
|
||||
cmd.EnableTracingFlag,
|
||||
cmd.TracingEndpointFlag,
|
||||
cmd.TraceSampleFractionFlag,
|
||||
cmd.KeystorePasswordFlag,
|
||||
cmd.KeystoreDirectoryFlag,
|
||||
debug.PProfFlag,
|
||||
debug.PProfAddrFlag,
|
||||
debug.PProfPortFlag,
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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.",
|
||||
}
|
||||
)
|
||||
|
||||
33
shared/keystore/BUILD.bazel
Normal file
33
shared/keystore/BUILD.bazel
Normal file
@@ -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",
|
||||
],
|
||||
)
|
||||
204
shared/keystore/key.go
Normal file
204
shared/keystore/key.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
113
shared/keystore/key_test.go
Normal file
113
shared/keystore/key_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
245
shared/keystore/keystore.go
Normal file
245
shared/keystore/keystore.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
75
shared/keystore/keystore_test.go
Normal file
75
shared/keystore/keystore_test.go
Normal file
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
75
shared/keystore/utils.go
Normal file
75
shared/keystore/utils.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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--<created_at UTC ISO8601>-<address hex>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -69,6 +69,8 @@ VERSION:
|
||||
cmd.EnableTracingFlag,
|
||||
cmd.TracingEndpointFlag,
|
||||
cmd.TraceSampleFractionFlag,
|
||||
cmd.KeystorePasswordFlag,
|
||||
cmd.KeystoreDirectoryFlag,
|
||||
debug.PProfFlag,
|
||||
debug.PProfAddrFlag,
|
||||
debug.PProfPortFlag,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -2,7 +2,6 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
Reference in New Issue
Block a user