Skip to content

Commit

Permalink
Add wallet keymanager (#4687)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people committed Feb 3, 2020
1 parent fb7a75d commit 648584b
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 2 deletions.
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)
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

0 comments on commit 648584b

Please sign in to comment.