Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wallet keymanager #4687

Merged
merged 9 commits into from Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 78 additions & 0 deletions WORKSPACE
Expand Up @@ -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",
)
3 changes: 3 additions & 0 deletions validator/keymanager/BUILD.bazel
Expand Up @@ -10,6 +10,7 @@ go_library(
"keymanager.go",
"log.go",
"opts.go",
"wallet.go",
],
importpath = "github.com/prysmaticlabs/prysm/validator/keymanager",
visibility = ["//validator:__subpackages__"],
Expand All @@ -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",
],
)
Expand Down
3 changes: 3 additions & 0 deletions validator/keymanager/keymanager.go
Expand Up @@ -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.
Expand Down
110 changes: 110 additions & 0 deletions validator/keymanager/wallet.go
@@ -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)
nisdas marked this conversation as resolved.
Show resolved Hide resolved
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())
}
18 changes: 16 additions & 2 deletions validator/node/node.go
Expand Up @@ -5,6 +5,7 @@ package node
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/signal"
"strings"
Expand Down Expand Up @@ -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")
}
}
}

Expand Down Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -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)
}
Expand Down