Add wallet keymanager (#4687)

* Add wallet keymanager

* Read keymanageropts from file if not JSON

Co-authored-by: Nishant Das <nish1993@hotmail.com>
Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
This commit is contained in:
Jim McDonald
2020-02-03 17:13:58 +00:00
committed by GitHub
parent fb7a75d2c3
commit 648584b356
5 changed files with 210 additions and 2 deletions

View File

@@ -1486,3 +1486,81 @@ go_repository(
sum = "h1:oq6BiN7v0MfWCRcJAxSV+hesVMAAV8COrQbTjYNnso4=",
version = "v0.0.0-20190611015032-8a3d0352aa79",
)
go_repository(
name = "com_github_wealdtech_go_eth2_wallet",
commit = "6970d62e60d86fdae3c3e510e800e8a60d755a7d",
importpath = "github.com/wealdtech/go-eth2-wallet",
)
go_repository(
name = "com_github_wealdtech_go_eth2_wallet_hd",
commit = "ce0a252a01c621687e9786a64899cfbfe802ba73",
importpath = "github.com/wealdtech/go-eth2-wallet-hd",
)
go_repository(
name = "com_github_wealdtech_go_eth2_wallet_nd",
commit = "12c8c41cdbd16797ff292e27f58e126bb89e9706",
importpath = "github.com/wealdtech/go-eth2-wallet-nd",
)
go_repository(
name = "com_github_wealdtech_go_eth2_wallet_store_filesystem",
commit = "1eea6a48d75380047d2ebe7c8c4bd8985bcfdeca",
importpath = "github.com/wealdtech/go-eth2-wallet-store-filesystem",
)
go_repository(
name = "com_github_wealdtech_go_eth2_wallet_store_s3",
commit = "1c821b5161f7bb0b3efa2030eff687eea5e70e53",
importpath = "github.com/wealdtech/go-eth2-wallet-store-s3",
)
go_repository(
name = "com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4",
commit = "0c11c07b9544eb662210fadded94f40f309d8c8f",
importpath = "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4",
)
go_repository(
name = "com_github_wealdtech_go_eth2_wallet_types",
commit = "af67d8101be61e7c4dd8126d2b3eba20cff5dab2",
importpath = "github.com/wealdtech/go-eth2-wallet-types",
)
go_repository(
name = "com_github_wealdtech_go_eth2_types",
commit = "f9c31ddf180537dd5712d5998a3d56c45864d71f",
importpath = "github.com/wealdtech/go-eth2-types",
)
go_repository(
name = "com_github_wealdtech_go_eth2_util",
commit = "326ebb1755651131bb8f4506ea9a23be6d9ad1dd",
importpath = "github.com/wealdtech/go-eth2-util",
)
go_repository(
name = "com_github_wealdtech_go_ecodec",
commit = "7473d835445a3490e61a5fcf48fe4e9755a37957",
importpath = "github.com/wealdtech/go-ecodec",
)
go_repository(
name = "com_github_wealdtech_go_bytesutil",
commit = "e564d0ade555b9f97494f0f669196ddcc6bc531d",
importpath = "github.com/wealdtech/go-bytesutil",
)
go_repository(
name = "com_github_wealdtech_go_indexer",
commit = "334862c32b1e3a5c6738a2618f5c0a8ebeb8cd51",
importpath = "github.com/wealdtech/go-indexer",
)
go_repository(
name = "com_github_shibukawa_configdir",
commit = "e180dbdc8da04c4fa04272e875ce64949f38bd3e",
importpath = "github.com/shibukawa/configdir",
)

View File

@@ -10,6 +10,7 @@ go_library(
"keymanager.go",
"log.go",
"opts.go",
"wallet.go",
],
importpath = "github.com/prysmaticlabs/prysm/validator/keymanager",
visibility = ["//validator:__subpackages__"],
@@ -19,6 +20,8 @@ go_library(
"//shared/interop:go_default_library",
"//validator/accounts:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_wealdtech_go_eth2_wallet//:go_default_library",
"@com_github_wealdtech_go_eth2_wallet_types//:go_default_library",
"@org_golang_x_crypto//ssh/terminal:go_default_library",
],
)

View File

@@ -9,6 +9,9 @@ import (
// ErrNoSuchKey is returned whenever a request is made for a key of which a key manager is unaware.
var ErrNoSuchKey = errors.New("no such key")
// ErrCannotSign is returned whenever a signing attempt fails.
var ErrCannotSign = errors.New("cannot sign")
// KeyManager controls access to private keys by the validator.
type KeyManager interface {
// FetchValidatingKeys fetches the list of public keys that should be used to validate with.

View File

@@ -0,0 +1,110 @@
package keymanager
import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
e2wallet "github.com/wealdtech/go-eth2-wallet"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types"
)
type walletOpts struct {
Accounts []string `json:"accounts"`
Passphrases []string `json:"passphrases"`
}
var walletOptsHelp = `The wallet key manager stores keys in a local encrypted store. The options are:
- accounts This is a list of account specifiers. An account specifier is of
the form <wallet name>/[account name], where the account name can be a
regular expression. If the account specifier is just <wallet name> all
accounts in that wallet will be used. Multiple account specifiers can be
supplied if required.
- passphrase This is the passphrase used to encrypt the accounts when they
were created. Multiple passphrases can be supplied if required.
{
"accounts": ["Validators/Account.*"], // Use all accounts in the 'Validators' wallet starting with 'Account'
"passphrases": ["secret1","secret2"] // Use the passphrases 'secret1' and 'secret2' to decrypt accounts
}`
// NewWallet creates a key manager populated with the keys from a wallet at the given path.
func NewWallet(input string) (KeyManager, string, error) {
opts := &walletOpts{}
err := json.Unmarshal([]byte(input), opts)
if err != nil {
return nil, walletOptsHelp, err
}
if len(opts.Accounts) == 0 {
return nil, walletOptsHelp, errors.New("at least one account specifier is required")
}
if len(opts.Passphrases) == 0 {
return nil, walletOptsHelp, errors.New("at least one passphrase is required to decrypt accounts")
}
km := &Wallet{
accounts: make(map[[48]byte]e2wtypes.Account),
}
for _, path := range opts.Accounts {
parts := strings.Split(path, "/")
if len(parts[0]) == 0 {
return nil, walletOptsHelp, fmt.Errorf("did not understand account specifier %q", path)
}
wallet, err := e2wallet.OpenWallet(parts[0])
if err != nil {
return nil, walletOptsHelp, err
}
accountSpecifier := "^.*$"
if len(parts) > 1 && len(parts[1]) > 0 {
accountSpecifier = fmt.Sprintf("^%s$", parts[1])
}
re := regexp.MustCompile(accountSpecifier)
for account := range wallet.Accounts() {
if re.Match([]byte(account.Name())) {
pubKey := bytesutil.ToBytes48(account.PublicKey().Marshal())
for _, passphrase := range opts.Passphrases {
if err := account.Unlock([]byte(passphrase)); err != nil {
log.WithError(err).WithField("pubKey", fmt.Sprintf("%#x", pubKey)).Warn("Failed to unlock account with supplied passphrases; cannot validate")
} else {
km.accounts[pubKey] = account
}
}
}
}
}
return km, walletOptsHelp, nil
}
// Wallet is a key manager that loads keys from a local Ethereum 2 wallet.
type Wallet struct {
accounts map[[48]byte]e2wtypes.Account
}
// FetchValidatingKeys fetches the list of public keys that should be used to validate with.
func (km *Wallet) FetchValidatingKeys() ([][48]byte, error) {
res := make([][48]byte, 0, len(km.accounts))
for pubKey := range km.accounts {
res = append(res, pubKey)
}
return res, nil
}
// Sign signs a message for the validator to broadcast.
func (km *Wallet) Sign(pubKey [48]byte, root [32]byte, domain uint64) (*bls.Signature, error) {
account, exists := km.accounts[pubKey]
if !exists {
return nil, ErrNoSuchKey
}
sig, err := account.Sign(root[:], domain)
if err != nil {
return nil, err
}
return bls.SignatureFromBytes(sig.Marshal())
}

View File

@@ -5,6 +5,7 @@ package node
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/signal"
"strings"
@@ -88,8 +89,13 @@ func NewValidatorClient(ctx *cli.Context) (*ValidatorClient, error) {
if err != nil {
log.WithError(err).Error("Failed to obtain public keys for validation")
} else {
for _, key := range pubKeys {
log.WithField("pubKey", fmt.Sprintf("%#x", key)).Info("Validating for public key")
if len(pubKeys) == 0 {
log.Warn("No keys found; nothing to validate")
} else {
log.WithField("validators", len(pubKeys)).Info("Found validator keys")
for _, key := range pubKeys {
log.WithField("pubKey", fmt.Sprintf("%#x", key)).Info("Validating for public key")
}
}
}
@@ -199,6 +205,12 @@ func selectKeyManager(ctx *cli.Context) (keymanager.KeyManager, error) {
opts := ctx.String(flags.KeyManagerOpts.Name)
if opts == "" {
opts = "{}"
} else if !strings.HasPrefix(opts, "{") {
fileopts, err := ioutil.ReadFile(opts)
if err != nil {
return nil, errors.Wrap(err, "Failed to read keymanager options file")
}
opts = string(fileopts)
}
if manager == "" {
@@ -238,6 +250,8 @@ func selectKeyManager(ctx *cli.Context) (keymanager.KeyManager, error) {
km, help, err = keymanager.NewUnencrypted(opts)
case "keystore":
km, help, err = keymanager.NewKeystore(opts)
case "wallet":
km, help, err = keymanager.NewWallet(opts)
default:
return nil, fmt.Errorf("unknown keymanager %q", manager)
}