mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 13:58:09 -05:00
Compare commits
4 Commits
v7.1.0
...
keymanager
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d416de6d9 | ||
|
|
dfcad26ad2 | ||
|
|
1b9fb0cbac | ||
|
|
e4b392ddb2 |
1
cmd/validator/wallet/keymanager.go
Normal file
1
cmd/validator/wallet/keymanager.go
Normal file
@@ -0,0 +1 @@
|
||||
package wallet
|
||||
@@ -1,51 +0,0 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"keymanager.go",
|
||||
"log.go",
|
||||
"mnemonic.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/validator/keymanager/derived",
|
||||
visibility = [
|
||||
"//tools:__subpackages__",
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//async/event:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//io/prompt:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//validator/accounts/iface:go_default_library",
|
||||
"//validator/keymanager:go_default_library",
|
||||
"//validator/keymanager/imported:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_util//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"eip_test.go",
|
||||
"keymanager_test.go",
|
||||
"mnemonic_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//validator/accounts/testing:go_default_library",
|
||||
"//validator/testing:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_util//:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -1,119 +0,0 @@
|
||||
package derived
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/validator/accounts/iface"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
)
|
||||
|
||||
const (
|
||||
// DerivationPathFormat describes the structure of how keys are derived from a master key.
|
||||
DerivationPathFormat = "m / purpose / coin_type / account_index / withdrawal_key / validating_key"
|
||||
// ValidatingKeyDerivationPathTemplate defining the hierarchical path for validating
|
||||
// keys for Prysm Ethereum validators. According to EIP-2334, the format is as follows:
|
||||
// m / purpose / coin_type / account_index / withdrawal_key / validating_key
|
||||
ValidatingKeyDerivationPathTemplate = "m/12381/3600/%d/0/0"
|
||||
)
|
||||
|
||||
// SetupConfig includes configuration values for initializing
|
||||
// a keymanager, such as passwords, the wallet, and more.
|
||||
type SetupConfig struct {
|
||||
Wallet iface.Wallet
|
||||
ListenForChanges bool
|
||||
}
|
||||
|
||||
// Keymanager implementation for derived, HD keymanager using EIP-2333 and EIP-2334.
|
||||
type Keymanager struct {
|
||||
importedKM *imported.Keymanager
|
||||
}
|
||||
|
||||
// NewKeymanager instantiates a new derived keymanager from configuration options.
|
||||
func NewKeymanager(
|
||||
ctx context.Context,
|
||||
cfg *SetupConfig,
|
||||
) (*Keymanager, error) {
|
||||
importedKM, err := imported.NewKeymanager(ctx, &imported.SetupConfig{
|
||||
Wallet: cfg.Wallet,
|
||||
ListenForChanges: cfg.ListenForChanges,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Keymanager{
|
||||
importedKM: importedKM,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RecoverAccountsFromMnemonic given a mnemonic phrase, is able to regenerate N accounts
|
||||
// from a derived seed, encrypt them according to the EIP-2334 JSON standard, and write them
|
||||
// to disk. Then, the mnemonic is never stored nor used by the validator.
|
||||
func (km *Keymanager) RecoverAccountsFromMnemonic(
|
||||
ctx context.Context, mnemonic, mnemonicPassphrase string, numAccounts int,
|
||||
) error {
|
||||
seed, err := seedFromMnemonic(mnemonic, mnemonicPassphrase)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize new wallet seed file")
|
||||
}
|
||||
privKeys := make([][]byte, numAccounts)
|
||||
pubKeys := make([][]byte, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
privKey, err := util.PrivateKeyFromSeedAndPath(
|
||||
seed, fmt.Sprintf(ValidatingKeyDerivationPathTemplate, i),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privKeys[i] = privKey.Marshal()
|
||||
pubKeys[i] = privKey.PublicKey().Marshal()
|
||||
}
|
||||
return km.importedKM.ImportKeypairs(ctx, privKeys, pubKeys)
|
||||
}
|
||||
|
||||
// ExtractKeystores retrieves the secret keys for specified public keys
|
||||
// in the function input, encrypts them using the specified password,
|
||||
// and returns their respective EIP-2335 keystores.
|
||||
func (km *Keymanager) ExtractKeystores(
|
||||
ctx context.Context, publicKeys []bls.PublicKey, password string,
|
||||
) ([]*keymanager.Keystore, error) {
|
||||
return km.importedKM.ExtractKeystores(ctx, publicKeys, password)
|
||||
}
|
||||
|
||||
// ValidatingAccountNames for the derived keymanager.
|
||||
func (km *Keymanager) ValidatingAccountNames(_ context.Context) ([]string, error) {
|
||||
return km.importedKM.ValidatingAccountNames()
|
||||
}
|
||||
|
||||
// Sign signs a message using a validator key.
|
||||
func (km *Keymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (bls.Signature, error) {
|
||||
return km.importedKM.Sign(ctx, req)
|
||||
}
|
||||
|
||||
// FetchValidatingPublicKeys fetches the list of validating public keys from the keymanager.
|
||||
func (km *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error) {
|
||||
return km.importedKM.FetchValidatingPublicKeys(ctx)
|
||||
}
|
||||
|
||||
// FetchValidatingPrivateKeys fetches the list of validating private keys from the keymanager.
|
||||
func (km *Keymanager) FetchValidatingPrivateKeys(ctx context.Context) ([][32]byte, error) {
|
||||
return km.importedKM.FetchValidatingPrivateKeys(ctx)
|
||||
}
|
||||
|
||||
// DeleteAccounts for a derived keymanager.
|
||||
func (km *Keymanager) DeleteAccounts(ctx context.Context, publicKeys [][]byte) error {
|
||||
return km.importedKM.DeleteAccounts(ctx, publicKeys)
|
||||
}
|
||||
|
||||
// SubscribeAccountChanges creates an event subscription for a channel
|
||||
// to listen for public key changes at runtime, such as when new validator accounts
|
||||
// are imported into the keymanager while the validator process is running.
|
||||
func (km *Keymanager) SubscribeAccountChanges(pubKeysChan chan [][48]byte) event.Subscription {
|
||||
return km.importedKM.SubscribeAccountChanges(pubKeysChan)
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package derived
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/crypto/rand"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
mock "github.com/prysmaticlabs/prysm/validator/accounts/testing"
|
||||
constant "github.com/prysmaticlabs/prysm/validator/testing"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
)
|
||||
|
||||
const (
|
||||
password = "secretPassw0rd$1999"
|
||||
)
|
||||
|
||||
// We test that using a '25th word' mnemonic passphrase leads to different
|
||||
// public keys derived than not specifying the passphrase.
|
||||
func TestDerivedKeymanager_MnemnonicPassphrase_DifferentResults(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
km, err := NewKeymanager(ctx, &SetupConfig{
|
||||
Wallet: wallet,
|
||||
ListenForChanges: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
numAccounts := 5
|
||||
err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, "mnemonicpass", numAccounts)
|
||||
require.NoError(t, err)
|
||||
without25thWord, err := km.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
wallet = &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
km, err = NewKeymanager(ctx, &SetupConfig{
|
||||
Wallet: wallet,
|
||||
ListenForChanges: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// No mnemonic passphrase this time.
|
||||
err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
with25thWord, err := km.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
for i, k := range with25thWord {
|
||||
without := without25thWord[i]
|
||||
assert.DeepNotEqual(t, k, without)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDerivedKeymanager_RecoverSeedRoundTrip(t *testing.T) {
|
||||
mnemonicEntropy := make([]byte, 32)
|
||||
n, err := rand.NewGenerator().Read(mnemonicEntropy)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n, len(mnemonicEntropy))
|
||||
mnemonic, err := bip39.NewMnemonic(mnemonicEntropy)
|
||||
require.NoError(t, err)
|
||||
wanted := bip39.NewSeed(mnemonic, "")
|
||||
|
||||
got, err := seedFromMnemonic(mnemonic, "" /* no passphrase */)
|
||||
require.NoError(t, err)
|
||||
// Ensure the derived seed matches.
|
||||
assert.DeepEqual(t, wanted, got)
|
||||
}
|
||||
|
||||
func TestDerivedKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
derivedSeed, err := seedFromMnemonic(constant.TestMnemonic, "")
|
||||
require.NoError(t, err)
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
ctx := context.Background()
|
||||
dr, err := NewKeymanager(ctx, &SetupConfig{
|
||||
Wallet: wallet,
|
||||
ListenForChanges: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
numAccounts := 5
|
||||
err = dr.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fetch the public keys.
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numAccounts, len(publicKeys))
|
||||
|
||||
wantedPubKeys := make([][48]byte, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
privKey, err := util.PrivateKeyFromSeedAndPath(derivedSeed, fmt.Sprintf(ValidatingKeyDerivationPathTemplate, i))
|
||||
require.NoError(t, err)
|
||||
pubKey := [48]byte{}
|
||||
copy(pubKey[:], privKey.PublicKey().Marshal())
|
||||
wantedPubKeys[i] = pubKey
|
||||
}
|
||||
|
||||
// FetchValidatingPublicKeys is also used in generating the output of account list
|
||||
// therefore the results must be in the same order as the order in which the accounts were derived
|
||||
for i, key := range wantedPubKeys {
|
||||
assert.Equal(t, key, publicKeys[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDerivedKeymanager_FetchValidatingPrivateKeys(t *testing.T) {
|
||||
derivedSeed, err := seedFromMnemonic(constant.TestMnemonic, "")
|
||||
require.NoError(t, err)
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
ctx := context.Background()
|
||||
dr, err := NewKeymanager(ctx, &SetupConfig{
|
||||
Wallet: wallet,
|
||||
ListenForChanges: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
numAccounts := 5
|
||||
err = dr.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fetch the private keys.
|
||||
privateKeys, err := dr.FetchValidatingPrivateKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numAccounts, len(privateKeys))
|
||||
|
||||
wantedPrivKeys := make([][32]byte, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
privKey, err := util.PrivateKeyFromSeedAndPath(derivedSeed, fmt.Sprintf(ValidatingKeyDerivationPathTemplate, i))
|
||||
require.NoError(t, err)
|
||||
privKeyBytes := [32]byte{}
|
||||
copy(privKeyBytes[:], privKey.Marshal())
|
||||
wantedPrivKeys[i] = privKeyBytes
|
||||
}
|
||||
|
||||
// FetchValidatingPrivateKeys is also used in generating the output of account list
|
||||
// therefore the results must be in the same order as the order in which the accounts were derived
|
||||
for i, key := range wantedPrivKeys {
|
||||
assert.Equal(t, key, privateKeys[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDerivedKeymanager_Sign(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
ctx := context.Background()
|
||||
dr, err := NewKeymanager(ctx, &SetupConfig{
|
||||
Wallet: wallet,
|
||||
ListenForChanges: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
numAccounts := 5
|
||||
err = dr.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
// We prepare naive data to sign.
|
||||
data := []byte("eth2data")
|
||||
signRequest := &validatorpb.SignRequest{
|
||||
PublicKey: pubKeys[0][:],
|
||||
SigningRoot: data,
|
||||
}
|
||||
sig, err := dr.Sign(ctx, signRequest)
|
||||
require.NoError(t, err)
|
||||
pubKey, err := bls.PublicKeyFromBytes(pubKeys[0][:])
|
||||
require.NoError(t, err)
|
||||
wrongPubKey, err := bls.PublicKeyFromBytes(pubKeys[1][:])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check if the signature verifies.
|
||||
assert.Equal(t, true, sig.Verify(pubKey, data))
|
||||
// Check if the bad signature fails.
|
||||
assert.Equal(t, false, sig.Verify(wrongPubKey, data))
|
||||
}
|
||||
|
||||
func TestDerivedKeymanager_Sign_NoPublicKeySpecified(t *testing.T) {
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: nil,
|
||||
}
|
||||
dr := &Keymanager{}
|
||||
_, err := dr.Sign(context.Background(), req)
|
||||
assert.ErrorContains(t, "nil public key", err)
|
||||
}
|
||||
|
||||
func TestDerivedKeymanager_Sign_NoPublicKeyInCache(t *testing.T) {
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: []byte("hello world"),
|
||||
}
|
||||
dr := &Keymanager{}
|
||||
_, err := dr.Sign(context.Background(), req)
|
||||
assert.ErrorContains(t, "no signing key found", err)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package derived
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
var log = logrus.WithField("prefix", "derived-keymanager")
|
||||
59
validator/keymanager/imported/delete.go
Normal file
59
validator/keymanager/imported/delete.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/validator/accounts/petnames"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// DeleteAccounts takes in public keys and removes the accounts entirely. This includes their disk keystore and cached keystore.
|
||||
func (km *Keymanager) DeleteAccounts(ctx context.Context, publicKeys [][]byte) error {
|
||||
for _, publicKey := range publicKeys {
|
||||
var index int
|
||||
var found bool
|
||||
for i, pubKey := range km.accountsStore.PublicKeys {
|
||||
if bytes.Equal(pubKey, publicKey) {
|
||||
index = i
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("could not find public key %#x", publicKey)
|
||||
}
|
||||
deletedPublicKey := km.accountsStore.PublicKeys[index]
|
||||
accountName := petnames.DeterministicName(deletedPublicKey, "-")
|
||||
km.accountsStore.PrivateKeys = append(km.accountsStore.PrivateKeys[:index], km.accountsStore.PrivateKeys[index+1:]...)
|
||||
km.accountsStore.PublicKeys = append(km.accountsStore.PublicKeys[:index], km.accountsStore.PublicKeys[index+1:]...)
|
||||
|
||||
newStore, err := km.CreateAccountsKeystore(ctx, km.accountsStore.PrivateKeys, km.accountsStore.PublicKeys)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not rewrite accounts keystore")
|
||||
}
|
||||
|
||||
// Write the encoded keystore.
|
||||
encoded, err := json.MarshalIndent(newStore, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := km.wallet.WriteFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName, encoded); err != nil {
|
||||
return errors.Wrap(err, "could not write keystore file for accounts")
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"name": accountName,
|
||||
"publicKey": fmt.Sprintf("%#x", bytesutil.Trunc(deletedPublicKey)),
|
||||
}).Info("Successfully deleted validator account")
|
||||
err = km.initializeKeysCachesFromKeystore()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize keys caches")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
66
validator/keymanager/imported/delete_test.go
Normal file
66
validator/keymanager/imported/delete_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
mock "github.com/prysmaticlabs/prysm/validator/accounts/testing"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
func TestImportedKeymanager_DeleteKeystores(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &accountStore{},
|
||||
}
|
||||
numAccounts := 5
|
||||
ctx := context.Background()
|
||||
keystores := make([]*keymanager.Keystore, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
keystores[i] = createRandomKeystore(t, password)
|
||||
}
|
||||
require.NoError(t, dr.ImportKeystores(ctx, keystores, password))
|
||||
accounts, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numAccounts, len(accounts))
|
||||
|
||||
accountToRemove := uint64(2)
|
||||
accountPubKey := accounts[accountToRemove]
|
||||
// Remove an account from the keystore.
|
||||
require.NoError(t, dr.DeleteAccounts(ctx, [][]byte{accountPubKey[:]}))
|
||||
// Ensure the keystore file was written to the wallet
|
||||
// and ensure we can decrypt it using the EIP-2335 standard.
|
||||
var encodedKeystore []byte
|
||||
for k, v := range wallet.Files[AccountsPath] {
|
||||
if strings.Contains(k, "keystore") {
|
||||
encodedKeystore = v
|
||||
}
|
||||
}
|
||||
require.NotNil(t, encodedKeystore, "could not find keystore file")
|
||||
keystoreFile := &keymanager.Keystore{}
|
||||
require.NoError(t, json.Unmarshal(encodedKeystore, keystoreFile))
|
||||
|
||||
// We extract the accounts from the keystore.
|
||||
decryptor := keystorev4.New()
|
||||
encodedAccounts, err := decryptor.Decrypt(keystoreFile.Crypto, password)
|
||||
require.NoError(t, err, "Could not decrypt validator accounts")
|
||||
store := &accountStore{}
|
||||
require.NoError(t, json.Unmarshal(encodedAccounts, store))
|
||||
|
||||
require.Equal(t, numAccounts-1, len(store.PublicKeys))
|
||||
require.Equal(t, numAccounts-1, len(store.PrivateKeys))
|
||||
require.LogsContain(t, hook, fmt.Sprintf("%#x", bytesutil.Trunc(accountPubKey[:])))
|
||||
require.LogsContain(t, hook, "Successfully deleted validator account")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package derived
|
||||
package imported
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
41
validator/keymanager/imported/fetch_keys.go
Normal file
41
validator/keymanager/imported/fetch_keys.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// FetchValidatingPublicKeys fetches the list of active public keys from the imported account keystores.
|
||||
func (km *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "keymanager.FetchValidatingPublicKeys")
|
||||
defer span.End()
|
||||
|
||||
lock.RLock()
|
||||
keys := orderedPublicKeys
|
||||
result := make([][48]byte, len(keys))
|
||||
copy(result, keys)
|
||||
lock.RUnlock()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FetchValidatingPrivateKeys fetches the list of private keys from the secret keys cache
|
||||
func (km *Keymanager) FetchValidatingPrivateKeys(ctx context.Context) ([][32]byte, error) {
|
||||
lock.RLock()
|
||||
defer lock.RUnlock()
|
||||
privKeys := make([][32]byte, len(secretKeysCache))
|
||||
pubKeys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve public keys")
|
||||
}
|
||||
for i, pk := range pubKeys {
|
||||
seckey, ok := secretKeysCache[pk]
|
||||
if !ok {
|
||||
return nil, errors.New("Could not fetch private key")
|
||||
}
|
||||
privKeys[i] = bytesutil.ToBytes32(seckey.Marshal())
|
||||
}
|
||||
return privKeys, nil
|
||||
}
|
||||
77
validator/keymanager/imported/fetch_keys_test.go
Normal file
77
validator/keymanager/imported/fetch_keys_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
mock "github.com/prysmaticlabs/prysm/validator/accounts/testing"
|
||||
)
|
||||
|
||||
func TestImportedKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &accountStore{},
|
||||
}
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
numAccounts := 10
|
||||
wantedPubKeys := make([][48]byte, 0)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
privKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pubKey := bytesutil.ToBytes48(privKey.PublicKey().Marshal())
|
||||
wantedPubKeys = append(wantedPubKeys, pubKey)
|
||||
dr.accountsStore.PublicKeys = append(dr.accountsStore.PublicKeys, pubKey[:])
|
||||
dr.accountsStore.PrivateKeys = append(dr.accountsStore.PrivateKeys, privKey.Marshal())
|
||||
}
|
||||
require.NoError(t, dr.initializeKeysCachesFromKeystore())
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, numAccounts, len(publicKeys))
|
||||
// FetchValidatingPublicKeys is also used in generating the output of account list
|
||||
// therefore the results must be in the same order as the order in which the accounts were derived
|
||||
for i, key := range wantedPubKeys {
|
||||
assert.Equal(t, key, publicKeys[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_FetchValidatingPrivateKeys(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &accountStore{},
|
||||
}
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
numAccounts := 10
|
||||
wantedPrivateKeys := make([][32]byte, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
privKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeyData := privKey.Marshal()
|
||||
pubKey := bytesutil.ToBytes48(privKey.PublicKey().Marshal())
|
||||
wantedPrivateKeys[i] = bytesutil.ToBytes32(privKeyData)
|
||||
dr.accountsStore.PublicKeys = append(dr.accountsStore.PublicKeys, pubKey[:])
|
||||
dr.accountsStore.PrivateKeys = append(dr.accountsStore.PrivateKeys, privKeyData)
|
||||
}
|
||||
require.NoError(t, dr.initializeKeysCachesFromKeystore())
|
||||
privateKeys, err := dr.FetchValidatingPrivateKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, numAccounts, len(privateKeys))
|
||||
// FetchValidatingPrivateKeys is also used in generating the output of account list
|
||||
// therefore the results must be in the same order as the order in which the accounts were created
|
||||
for i, key := range wantedPrivateKeys {
|
||||
assert.Equal(t, key, privateKeys[i])
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -13,14 +12,11 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/runtime/interop"
|
||||
"github.com/prysmaticlabs/prysm/validator/accounts/iface"
|
||||
"github.com/prysmaticlabs/prysm/validator/accounts/petnames"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager"
|
||||
"github.com/sirupsen/logrus"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -29,6 +25,15 @@ var (
|
||||
secretKeysCache = make(map[[48]byte]bls.SecretKey)
|
||||
)
|
||||
|
||||
const (
|
||||
// DerivationPathFormat describes the structure of how keys are derived from a master key.
|
||||
DerivationPathFormat = "m / purpose / coin_type / account_index / withdrawal_key / validating_key"
|
||||
// ValidatingKeyDerivationPathTemplate defining the hierarchical path for validating
|
||||
// keys for Prysm Ethereum validators. According to EIP-2334, the format is as follows:
|
||||
// m / purpose / coin_type / account_index / withdrawal_key / validating_key
|
||||
ValidatingKeyDerivationPathTemplate = "m/12381/3600/%d/0/0"
|
||||
)
|
||||
|
||||
const (
|
||||
// KeystoreFileNameFormat exposes the filename the keystore should be formatted in.
|
||||
KeystoreFileNameFormat = "keystore-%d.json"
|
||||
@@ -83,6 +88,12 @@ func NewKeymanager(ctx context.Context, cfg *SetupConfig) (*Keymanager, error) {
|
||||
accountsStore: &accountStore{},
|
||||
accountsChangedFeed: new(event.Feed),
|
||||
}
|
||||
if strings.Contains(cfg.Wallet.AccountsDir(), "imported") ||
|
||||
strings.Contains(cfg.Wallet.AccountsDir(), "derived") {
|
||||
return nil, errors.New(
|
||||
"keymanager kind found in wallet is not compatible as a local keymanager",
|
||||
)
|
||||
}
|
||||
|
||||
if err := k.initializeAccountKeystore(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize account store")
|
||||
@@ -96,8 +107,8 @@ func NewKeymanager(ctx context.Context, cfg *SetupConfig) (*Keymanager, error) {
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// NewInteropKeymanager instantiates a new imported keymanager with the deterministically generated interop keys.
|
||||
func NewInteropKeymanager(_ context.Context, offset, numValidatorKeys uint64) (*Keymanager, error) {
|
||||
// NewDeterministicKeymanager instantiates a new imported keymanager with the deterministically generated interop keys.
|
||||
func NewDeterministicKeymanager(_ context.Context, offset, numValidatorKeys uint64) (*Keymanager, error) {
|
||||
k := &Keymanager{
|
||||
accountsChangedFeed: new(event.Feed),
|
||||
}
|
||||
@@ -120,13 +131,6 @@ func NewInteropKeymanager(_ context.Context, offset, numValidatorKeys uint64) (*
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// SubscribeAccountChanges creates an event subscription for a channel
|
||||
// to listen for public key changes at runtime, such as when new validator accounts
|
||||
// are imported into the keymanager while the validator process is running.
|
||||
func (km *Keymanager) SubscribeAccountChanges(pubKeysChan chan [][48]byte) event.Subscription {
|
||||
return km.accountsChangedFeed.Subscribe(pubKeysChan)
|
||||
}
|
||||
|
||||
// ValidatingAccountNames for a imported keymanager.
|
||||
func (km *Keymanager) ValidatingAccountNames() ([]string, error) {
|
||||
lock.RLock()
|
||||
@@ -138,164 +142,6 @@ func (km *Keymanager) ValidatingAccountNames() ([]string, error) {
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// Initialize public and secret key caches that are used to speed up the functions
|
||||
// FetchValidatingPublicKeys and Sign
|
||||
func (km *Keymanager) initializeKeysCachesFromKeystore() error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
count := len(km.accountsStore.PrivateKeys)
|
||||
orderedPublicKeys = make([][48]byte, count)
|
||||
secretKeysCache = make(map[[48]byte]bls.SecretKey, count)
|
||||
for i, publicKey := range km.accountsStore.PublicKeys {
|
||||
publicKey48 := bytesutil.ToBytes48(publicKey)
|
||||
orderedPublicKeys[i] = publicKey48
|
||||
secretKey, err := bls.SecretKeyFromBytes(km.accountsStore.PrivateKeys[i])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize keys caches from account keystore")
|
||||
}
|
||||
secretKeysCache[publicKey48] = secretKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAccounts takes in public keys and removes the accounts entirely. This includes their disk keystore and cached keystore.
|
||||
func (km *Keymanager) DeleteAccounts(ctx context.Context, publicKeys [][]byte) error {
|
||||
for _, publicKey := range publicKeys {
|
||||
var index int
|
||||
var found bool
|
||||
for i, pubKey := range km.accountsStore.PublicKeys {
|
||||
if bytes.Equal(pubKey, publicKey) {
|
||||
index = i
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("could not find public key %#x", publicKey)
|
||||
}
|
||||
deletedPublicKey := km.accountsStore.PublicKeys[index]
|
||||
accountName := petnames.DeterministicName(deletedPublicKey, "-")
|
||||
km.accountsStore.PrivateKeys = append(km.accountsStore.PrivateKeys[:index], km.accountsStore.PrivateKeys[index+1:]...)
|
||||
km.accountsStore.PublicKeys = append(km.accountsStore.PublicKeys[:index], km.accountsStore.PublicKeys[index+1:]...)
|
||||
|
||||
newStore, err := km.CreateAccountsKeystore(ctx, km.accountsStore.PrivateKeys, km.accountsStore.PublicKeys)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not rewrite accounts keystore")
|
||||
}
|
||||
|
||||
// Write the encoded keystore.
|
||||
encoded, err := json.MarshalIndent(newStore, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := km.wallet.WriteFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName, encoded); err != nil {
|
||||
return errors.Wrap(err, "could not write keystore file for accounts")
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"name": accountName,
|
||||
"publicKey": fmt.Sprintf("%#x", bytesutil.Trunc(deletedPublicKey)),
|
||||
}).Info("Successfully deleted validator account")
|
||||
err = km.initializeKeysCachesFromKeystore()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize keys caches")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchValidatingPublicKeys fetches the list of active public keys from the imported account keystores.
|
||||
func (km *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "keymanager.FetchValidatingPublicKeys")
|
||||
defer span.End()
|
||||
|
||||
lock.RLock()
|
||||
keys := orderedPublicKeys
|
||||
result := make([][48]byte, len(keys))
|
||||
copy(result, keys)
|
||||
lock.RUnlock()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FetchValidatingPrivateKeys fetches the list of private keys from the secret keys cache
|
||||
func (km *Keymanager) FetchValidatingPrivateKeys(ctx context.Context) ([][32]byte, error) {
|
||||
lock.RLock()
|
||||
defer lock.RUnlock()
|
||||
privKeys := make([][32]byte, len(secretKeysCache))
|
||||
pubKeys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve public keys")
|
||||
}
|
||||
for i, pk := range pubKeys {
|
||||
seckey, ok := secretKeysCache[pk]
|
||||
if !ok {
|
||||
return nil, errors.New("Could not fetch private key")
|
||||
}
|
||||
privKeys[i] = bytesutil.ToBytes32(seckey.Marshal())
|
||||
}
|
||||
return privKeys, nil
|
||||
}
|
||||
|
||||
// Sign signs a message using a validator key.
|
||||
func (km *Keymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (bls.Signature, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "keymanager.Sign")
|
||||
defer span.End()
|
||||
|
||||
publicKey := req.PublicKey
|
||||
if publicKey == nil {
|
||||
return nil, errors.New("nil public key in request")
|
||||
}
|
||||
lock.RLock()
|
||||
secretKey, ok := secretKeysCache[bytesutil.ToBytes48(publicKey)]
|
||||
lock.RUnlock()
|
||||
if !ok {
|
||||
return nil, errors.New("no signing key found in keys cache")
|
||||
}
|
||||
return secretKey.Sign(req.SigningRoot), nil
|
||||
}
|
||||
|
||||
func (km *Keymanager) initializeAccountKeystore(ctx context.Context) error {
|
||||
encoded, err := km.wallet.ReadFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName)
|
||||
if err != nil && strings.Contains(err.Error(), "no files found") {
|
||||
// If there are no keys to initialize at all, just exit.
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return errors.Wrapf(err, "could not read keystore file for accounts %s", AccountsKeystoreFileName)
|
||||
}
|
||||
keystoreFile := &AccountsKeystoreRepresentation{}
|
||||
if err := json.Unmarshal(encoded, keystoreFile); err != nil {
|
||||
return errors.Wrapf(err, "could not decode keystore file for accounts %s", AccountsKeystoreFileName)
|
||||
}
|
||||
// We extract the validator signing private key from the keystore
|
||||
// by utilizing the password and initialize a new BLS secret key from
|
||||
// its raw bytes.
|
||||
password := km.wallet.Password()
|
||||
decryptor := keystorev4.New()
|
||||
enc, err := decryptor.Decrypt(keystoreFile.Crypto, password)
|
||||
if err != nil && strings.Contains(err.Error(), keymanager.IncorrectPasswordErrMsg) {
|
||||
return errors.Wrap(err, "wrong password for wallet entered")
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "could not decrypt keystore")
|
||||
}
|
||||
|
||||
store := &accountStore{}
|
||||
if err := json.Unmarshal(enc, store); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(store.PublicKeys) != len(store.PrivateKeys) {
|
||||
return errors.New("unequal number of public keys and private keys")
|
||||
}
|
||||
if len(store.PublicKeys) == 0 {
|
||||
return nil
|
||||
}
|
||||
km.accountsStore = store
|
||||
err = km.initializeKeysCachesFromKeystore()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize keys caches")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateAccountsKeystore creates a new keystore holding the provided keys.
|
||||
func (km *Keymanager) CreateAccountsKeystore(
|
||||
_ context.Context,
|
||||
@@ -356,3 +202,65 @@ func (km *Keymanager) CreateAccountsKeystore(
|
||||
Name: encryptor.Name(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Initialize public and secret key caches that are used to speed up the functions
|
||||
// FetchValidatingPublicKeys and Sign
|
||||
func (km *Keymanager) initializeKeysCachesFromKeystore() error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
count := len(km.accountsStore.PrivateKeys)
|
||||
orderedPublicKeys = make([][48]byte, count)
|
||||
secretKeysCache = make(map[[48]byte]bls.SecretKey, count)
|
||||
for i, publicKey := range km.accountsStore.PublicKeys {
|
||||
publicKey48 := bytesutil.ToBytes48(publicKey)
|
||||
orderedPublicKeys[i] = publicKey48
|
||||
secretKey, err := bls.SecretKeyFromBytes(km.accountsStore.PrivateKeys[i])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize keys caches from account keystore")
|
||||
}
|
||||
secretKeysCache[publicKey48] = secretKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (km *Keymanager) initializeAccountKeystore(ctx context.Context) error {
|
||||
encoded, err := km.wallet.ReadFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName)
|
||||
if err != nil && strings.Contains(err.Error(), "no files found") {
|
||||
// If there are no keys to initialize at all, just exit.
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return errors.Wrapf(err, "could not read keystore file for accounts %s", AccountsKeystoreFileName)
|
||||
}
|
||||
keystoreFile := &AccountsKeystoreRepresentation{}
|
||||
if err := json.Unmarshal(encoded, keystoreFile); err != nil {
|
||||
return errors.Wrapf(err, "could not decode keystore file for accounts %s", AccountsKeystoreFileName)
|
||||
}
|
||||
// We extract the validator signing private key from the keystore
|
||||
// by utilizing the password and initialize a new BLS secret key from
|
||||
// its raw bytes.
|
||||
password := km.wallet.Password()
|
||||
decryptor := keystorev4.New()
|
||||
enc, err := decryptor.Decrypt(keystoreFile.Crypto, password)
|
||||
if err != nil && strings.Contains(err.Error(), keymanager.IncorrectPasswordErrMsg) {
|
||||
return errors.Wrap(err, "wrong password for wallet entered")
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "could not decrypt keystore")
|
||||
}
|
||||
|
||||
store := &accountStore{}
|
||||
if err := json.Unmarshal(enc, store); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(store.PublicKeys) != len(store.PrivateKeys) {
|
||||
return errors.New("unequal number of public keys and private keys")
|
||||
}
|
||||
if len(store.PublicKeys) == 0 {
|
||||
return nil
|
||||
}
|
||||
km.accountsStore = store
|
||||
err = km.initializeKeysCachesFromKeystore()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize keys caches")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,218 +1 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
mock "github.com/prysmaticlabs/prysm/validator/accounts/testing"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
func TestImportedKeymanager_RemoveAccounts(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &accountStore{},
|
||||
}
|
||||
numAccounts := 5
|
||||
ctx := context.Background()
|
||||
keystores := make([]*keymanager.Keystore, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
keystores[i] = createRandomKeystore(t, password)
|
||||
}
|
||||
require.NoError(t, dr.ImportKeystores(ctx, keystores, password))
|
||||
accounts, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numAccounts, len(accounts))
|
||||
|
||||
accountToRemove := uint64(2)
|
||||
accountPubKey := accounts[accountToRemove]
|
||||
// Remove an account from the keystore.
|
||||
require.NoError(t, dr.DeleteAccounts(ctx, [][]byte{accountPubKey[:]}))
|
||||
// Ensure the keystore file was written to the wallet
|
||||
// and ensure we can decrypt it using the EIP-2335 standard.
|
||||
var encodedKeystore []byte
|
||||
for k, v := range wallet.Files[AccountsPath] {
|
||||
if strings.Contains(k, "keystore") {
|
||||
encodedKeystore = v
|
||||
}
|
||||
}
|
||||
require.NotNil(t, encodedKeystore, "could not find keystore file")
|
||||
keystoreFile := &keymanager.Keystore{}
|
||||
require.NoError(t, json.Unmarshal(encodedKeystore, keystoreFile))
|
||||
|
||||
// We extract the accounts from the keystore.
|
||||
decryptor := keystorev4.New()
|
||||
encodedAccounts, err := decryptor.Decrypt(keystoreFile.Crypto, password)
|
||||
require.NoError(t, err, "Could not decrypt validator accounts")
|
||||
store := &accountStore{}
|
||||
require.NoError(t, json.Unmarshal(encodedAccounts, store))
|
||||
|
||||
require.Equal(t, numAccounts-1, len(store.PublicKeys))
|
||||
require.Equal(t, numAccounts-1, len(store.PrivateKeys))
|
||||
require.LogsContain(t, hook, fmt.Sprintf("%#x", bytesutil.Trunc(accountPubKey[:])))
|
||||
require.LogsContain(t, hook, "Successfully deleted validator account")
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &accountStore{},
|
||||
}
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
numAccounts := 10
|
||||
wantedPubKeys := make([][48]byte, 0)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
privKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pubKey := bytesutil.ToBytes48(privKey.PublicKey().Marshal())
|
||||
wantedPubKeys = append(wantedPubKeys, pubKey)
|
||||
dr.accountsStore.PublicKeys = append(dr.accountsStore.PublicKeys, pubKey[:])
|
||||
dr.accountsStore.PrivateKeys = append(dr.accountsStore.PrivateKeys, privKey.Marshal())
|
||||
}
|
||||
require.NoError(t, dr.initializeKeysCachesFromKeystore())
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, numAccounts, len(publicKeys))
|
||||
// FetchValidatingPublicKeys is also used in generating the output of account list
|
||||
// therefore the results must be in the same order as the order in which the accounts were derived
|
||||
for i, key := range wantedPubKeys {
|
||||
assert.Equal(t, key, publicKeys[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_FetchValidatingPrivateKeys(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &accountStore{},
|
||||
}
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
numAccounts := 10
|
||||
wantedPrivateKeys := make([][32]byte, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
privKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeyData := privKey.Marshal()
|
||||
pubKey := bytesutil.ToBytes48(privKey.PublicKey().Marshal())
|
||||
wantedPrivateKeys[i] = bytesutil.ToBytes32(privKeyData)
|
||||
dr.accountsStore.PublicKeys = append(dr.accountsStore.PublicKeys, pubKey[:])
|
||||
dr.accountsStore.PrivateKeys = append(dr.accountsStore.PrivateKeys, privKeyData)
|
||||
}
|
||||
require.NoError(t, dr.initializeKeysCachesFromKeystore())
|
||||
privateKeys, err := dr.FetchValidatingPrivateKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, numAccounts, len(privateKeys))
|
||||
// FetchValidatingPrivateKeys is also used in generating the output of account list
|
||||
// therefore the results must be in the same order as the order in which the accounts were created
|
||||
for i, key := range wantedPrivateKeys {
|
||||
assert.Equal(t, key, privateKeys[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_Sign(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &accountStore{},
|
||||
}
|
||||
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
numAccounts := 10
|
||||
keystores := make([]*keymanager.Keystore, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
keystores[i] = createRandomKeystore(t, password)
|
||||
}
|
||||
require.NoError(t, dr.ImportKeystores(ctx, keystores, password))
|
||||
|
||||
var encodedKeystore []byte
|
||||
for k, v := range wallet.Files[AccountsPath] {
|
||||
if strings.Contains(k, "keystore") {
|
||||
encodedKeystore = v
|
||||
}
|
||||
}
|
||||
keystoreFile := &keymanager.Keystore{}
|
||||
require.NoError(t, json.Unmarshal(encodedKeystore, keystoreFile))
|
||||
|
||||
// We extract the validator signing private key from the keystore
|
||||
// by utilizing the password and initialize a new BLS secret key from
|
||||
// its raw bytes.
|
||||
decryptor := keystorev4.New()
|
||||
enc, err := decryptor.Decrypt(keystoreFile.Crypto, dr.wallet.Password())
|
||||
require.NoError(t, err)
|
||||
store := &accountStore{}
|
||||
require.NoError(t, json.Unmarshal(enc, store))
|
||||
require.Equal(t, len(store.PublicKeys), len(store.PrivateKeys))
|
||||
require.NotEqual(t, 0, len(store.PublicKeys))
|
||||
dr.accountsStore = store
|
||||
require.NoError(t, dr.initializeKeysCachesFromKeystore())
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(publicKeys), len(store.PublicKeys))
|
||||
|
||||
// We prepare naive data to sign.
|
||||
data := []byte("hello world")
|
||||
signRequest := &validatorpb.SignRequest{
|
||||
PublicKey: publicKeys[0][:],
|
||||
SigningRoot: data,
|
||||
}
|
||||
sig, err := dr.Sign(ctx, signRequest)
|
||||
require.NoError(t, err)
|
||||
pubKey, err := bls.PublicKeyFromBytes(publicKeys[0][:])
|
||||
require.NoError(t, err)
|
||||
wrongPubKey, err := bls.PublicKeyFromBytes(publicKeys[1][:])
|
||||
require.NoError(t, err)
|
||||
if !sig.Verify(pubKey, data) {
|
||||
t.Fatalf("Expected sig to verify for pubkey %#x and data %v", pubKey.Marshal(), data)
|
||||
}
|
||||
if sig.Verify(wrongPubKey, data) {
|
||||
t.Fatalf("Expected sig not to verify for pubkey %#x and data %v", wrongPubKey.Marshal(), data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_Sign_NoPublicKeySpecified(t *testing.T) {
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: nil,
|
||||
}
|
||||
dr := &Keymanager{}
|
||||
_, err := dr.Sign(context.Background(), req)
|
||||
assert.ErrorContains(t, "nil public key", err)
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_Sign_NoPublicKeyInCache(t *testing.T) {
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: []byte("hello world"),
|
||||
}
|
||||
secretKeysCache = make(map[[48]byte]bls.SecretKey)
|
||||
dr := &Keymanager{}
|
||||
_, err := dr.Sign(context.Background(), req)
|
||||
assert.ErrorContains(t, "no signing key found in keys cache", err)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package derived
|
||||
package imported
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package derived
|
||||
package imported
|
||||
|
||||
import (
|
||||
"testing"
|
||||
34
validator/keymanager/imported/recover.go
Normal file
34
validator/keymanager/imported/recover.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
)
|
||||
|
||||
// RecoverKeystoresFromMnemonic given a mnemonic phrase, is able to regenerate N accounts
|
||||
// from a derived seed, encrypt them according to the EIP-2334 JSON standard, and write them
|
||||
// to disk. Then, the mnemonic is never stored nor used by the validator.
|
||||
func (km *Keymanager) RecoverKeystoresFromMnemonic(
|
||||
ctx context.Context, mnemonic, mnemonicPassphrase string, numAccounts int,
|
||||
) error {
|
||||
seed, err := seedFromMnemonic(mnemonic, mnemonicPassphrase)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize new wallet seed file")
|
||||
}
|
||||
privKeys := make([][]byte, numAccounts)
|
||||
pubKeys := make([][]byte, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
privKey, err := util.PrivateKeyFromSeedAndPath(
|
||||
seed, fmt.Sprintf(ValidatingKeyDerivationPathTemplate, i),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privKeys[i] = privKey.Marshal()
|
||||
pubKeys[i] = privKey.PublicKey().Marshal()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
68
validator/keymanager/imported/recover_test.go
Normal file
68
validator/keymanager/imported/recover_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/crypto/rand"
|
||||
"github.com/prysmaticlabs/prysm/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
mock "github.com/prysmaticlabs/prysm/validator/accounts/testing"
|
||||
constant "github.com/prysmaticlabs/prysm/validator/testing"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
)
|
||||
|
||||
// We test that using a '25th word' mnemonic passphrase leads to different
|
||||
// public keys derived than not specifying the passphrase.
|
||||
func TestImportedKeymanager_Recover_25Words(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
km, err := NewKeymanager(ctx, &SetupConfig{
|
||||
Wallet: wallet,
|
||||
ListenForChanges: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
numAccounts := 5
|
||||
err = km.RecoverKeystoresFromMnemonic(ctx, constant.TestMnemonic, "mnemonicpass", numAccounts)
|
||||
require.NoError(t, err)
|
||||
without25thWord, err := km.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
wallet = &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
km, err = NewKeymanager(ctx, &SetupConfig{
|
||||
Wallet: wallet,
|
||||
ListenForChanges: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// No mnemonic passphrase this time.
|
||||
err = km.RecoverKeystoresFromMnemonic(ctx, constant.TestMnemonic, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
with25thWord, err := km.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
for i, k := range with25thWord {
|
||||
without := without25thWord[i]
|
||||
assert.DeepNotEqual(t, k, without)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_Recover_RoundTrip(t *testing.T) {
|
||||
mnemonicEntropy := make([]byte, 32)
|
||||
n, err := rand.NewGenerator().Read(mnemonicEntropy)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n, len(mnemonicEntropy))
|
||||
mnemonic, err := bip39.NewMnemonic(mnemonicEntropy)
|
||||
require.NoError(t, err)
|
||||
wanted := bip39.NewSeed(mnemonic, "")
|
||||
|
||||
got, err := seedFromMnemonic(mnemonic, "" /* no passphrase */)
|
||||
require.NoError(t, err)
|
||||
// Ensure the derived seed matches.
|
||||
assert.DeepEqual(t, wanted, got)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/async"
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/config/features"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
@@ -17,6 +18,13 @@ import (
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
// SubscribeAccountChanges creates an event subscription for a channel
|
||||
// to listen for public key changes at runtime, such as when new validator accounts
|
||||
// are imported into the keymanager while the validator process is running.
|
||||
func (km *Keymanager) SubscribeAccountChanges(pubKeysChan chan [][48]byte) event.Subscription {
|
||||
return km.accountsChangedFeed.Subscribe(pubKeysChan)
|
||||
}
|
||||
|
||||
// Listen for changes to the all-accounts.keystore.json file in our wallet
|
||||
// to load in new keys we observe into our keymanager. This uses the fsnotify
|
||||
// library to listen for file-system changes and debounces these events to
|
||||
|
||||
29
validator/keymanager/imported/sign.go
Normal file
29
validator/keymanager/imported/sign.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Sign signs a message using a validator key.
|
||||
func (km *Keymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (bls.Signature, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "keymanager.Sign")
|
||||
defer span.End()
|
||||
|
||||
publicKey := req.PublicKey
|
||||
if publicKey == nil {
|
||||
return nil, errors.New("nil public key in request")
|
||||
}
|
||||
lock.RLock()
|
||||
secretKey, ok := secretKeysCache[bytesutil.ToBytes48(publicKey)]
|
||||
lock.RUnlock()
|
||||
if !ok {
|
||||
return nil, errors.New("no signing key found in keys cache")
|
||||
}
|
||||
return secretKey.Sign(req.SigningRoot), nil
|
||||
}
|
||||
100
validator/keymanager/imported/sign_test.go
Normal file
100
validator/keymanager/imported/sign_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package imported
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
mock "github.com/prysmaticlabs/prysm/validator/accounts/testing"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
func TestImportedKeymanager_Sign(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &accountStore{},
|
||||
}
|
||||
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
numAccounts := 10
|
||||
keystores := make([]*keymanager.Keystore, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
keystores[i] = createRandomKeystore(t, password)
|
||||
}
|
||||
require.NoError(t, dr.ImportKeystores(ctx, keystores, password))
|
||||
|
||||
var encodedKeystore []byte
|
||||
for k, v := range wallet.Files[AccountsPath] {
|
||||
if strings.Contains(k, "keystore") {
|
||||
encodedKeystore = v
|
||||
}
|
||||
}
|
||||
keystoreFile := &keymanager.Keystore{}
|
||||
require.NoError(t, json.Unmarshal(encodedKeystore, keystoreFile))
|
||||
|
||||
// We extract the validator signing private key from the keystore
|
||||
// by utilizing the password and initialize a new BLS secret key from
|
||||
// its raw bytes.
|
||||
decryptor := keystorev4.New()
|
||||
enc, err := decryptor.Decrypt(keystoreFile.Crypto, dr.wallet.Password())
|
||||
require.NoError(t, err)
|
||||
store := &accountStore{}
|
||||
require.NoError(t, json.Unmarshal(enc, store))
|
||||
require.Equal(t, len(store.PublicKeys), len(store.PrivateKeys))
|
||||
require.NotEqual(t, 0, len(store.PublicKeys))
|
||||
dr.accountsStore = store
|
||||
require.NoError(t, dr.initializeKeysCachesFromKeystore())
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(publicKeys), len(store.PublicKeys))
|
||||
|
||||
// We prepare naive data to sign.
|
||||
data := []byte("hello world")
|
||||
signRequest := &validatorpb.SignRequest{
|
||||
PublicKey: publicKeys[0][:],
|
||||
SigningRoot: data,
|
||||
}
|
||||
sig, err := dr.Sign(ctx, signRequest)
|
||||
require.NoError(t, err)
|
||||
pubKey, err := bls.PublicKeyFromBytes(publicKeys[0][:])
|
||||
require.NoError(t, err)
|
||||
wrongPubKey, err := bls.PublicKeyFromBytes(publicKeys[1][:])
|
||||
require.NoError(t, err)
|
||||
if !sig.Verify(pubKey, data) {
|
||||
t.Fatalf("Expected sig to verify for pubkey %#x and data %v", pubKey.Marshal(), data)
|
||||
}
|
||||
if sig.Verify(wrongPubKey, data) {
|
||||
t.Fatalf("Expected sig not to verify for pubkey %#x and data %v", wrongPubKey.Marshal(), data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_Sign_NoPublicKeySpecified(t *testing.T) {
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: nil,
|
||||
}
|
||||
dr := &Keymanager{}
|
||||
_, err := dr.Sign(context.Background(), req)
|
||||
assert.ErrorContains(t, "nil public key", err)
|
||||
}
|
||||
|
||||
func TestImportedKeymanager_Sign_NoPublicKeyInCache(t *testing.T) {
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: []byte("hello world"),
|
||||
}
|
||||
secretKeysCache = make(map[[48]byte]bls.SecretKey)
|
||||
dr := &Keymanager{}
|
||||
_, err := dr.Sign(context.Background(), req)
|
||||
assert.ErrorContains(t, "no signing key found in keys cache", err)
|
||||
}
|
||||
@@ -2,23 +2,62 @@ package keymanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
|
||||
)
|
||||
|
||||
// IKeymanager defines a general keymanager interface for Prysm wallets.
|
||||
const (
|
||||
Local = "imported"
|
||||
Derived = "derived"
|
||||
Remote = "remote"
|
||||
)
|
||||
|
||||
// IKeymanager defines a struct which can be used to manage keys with important
|
||||
// actions such as listing public keys, signing data, and subscribing to key changes.
|
||||
// It is defined by the simple composition of a few base features. We can assert whether
|
||||
// or not a keymanager has "extra" functionality by casting it to other, useful key
|
||||
// management interfaces defined in this package.
|
||||
type IKeymanager interface {
|
||||
// FetchValidatingPublicKeys fetches the list of active public keys that should be used to validate with.
|
||||
PublicKeysFetcher
|
||||
Signer
|
||||
KeyChangeSubscriber
|
||||
}
|
||||
|
||||
// KeysFetcher for validating private and public keys.
|
||||
type KeysFetcher interface {
|
||||
FetchValidatingPrivateKeys(ctx context.Context) ([][32]byte, error)
|
||||
PublicKeysFetcher
|
||||
}
|
||||
|
||||
// PublicKeysFetcher for validating public keys.
|
||||
type PublicKeysFetcher interface {
|
||||
FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error)
|
||||
// Sign signs a message using a validator key.
|
||||
}
|
||||
|
||||
// Signer allows signing messages using a validator private key.
|
||||
type Signer interface {
|
||||
Sign(context.Context, *validatorpb.SignRequest) (bls.Signature, error)
|
||||
// SubscribeAccountChanges subscribes to changes made to the underlying keys.
|
||||
}
|
||||
|
||||
// Importer can import new keystores into the keymanager.
|
||||
type Importer interface {
|
||||
ImportKeystores(ctx context.Context, keystores []*Keystore, importsPassword string) error
|
||||
}
|
||||
|
||||
// KeyChangeSubscriber allows subscribing to changes made to the underlying keys.
|
||||
type KeyChangeSubscriber interface {
|
||||
SubscribeAccountChanges(pubKeysChan chan [][48]byte) event.Subscription
|
||||
}
|
||||
|
||||
// KeyRecoverer allows recovering a keystore from a recovery phrase.
|
||||
type KeyRecoverer interface {
|
||||
RecoverKeystoresFromMnemonic(
|
||||
ctx context.Context, mnemonic, mnemonicPassphrase string, numKeys int,
|
||||
) error
|
||||
}
|
||||
|
||||
// Keystore json file representation as a Go struct.
|
||||
type Keystore struct {
|
||||
Crypto map[string]interface{} `json:"crypto"`
|
||||
@@ -28,47 +67,6 @@ type Keystore struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Kind defines an enum for either imported, derived, or remote-signing
|
||||
// keystores for Prysm wallets.
|
||||
type Kind int
|
||||
|
||||
const (
|
||||
// Imported keymanager defines an on-disk, encrypted keystore-capable store.
|
||||
Imported Kind = iota
|
||||
// Derived keymanager using a hierarchical-deterministic algorithm.
|
||||
Derived
|
||||
// Remote keymanager capable of remote-signing data.
|
||||
Remote
|
||||
)
|
||||
|
||||
// IncorrectPasswordErrMsg defines a common error string representing an EIP-2335
|
||||
// keystore password was incorrect.
|
||||
const IncorrectPasswordErrMsg = "invalid checksum"
|
||||
|
||||
// String marshals a keymanager kind to a string value.
|
||||
func (k Kind) String() string {
|
||||
switch k {
|
||||
case Derived:
|
||||
return "derived"
|
||||
case Imported:
|
||||
return "direct"
|
||||
case Remote:
|
||||
return "remote"
|
||||
default:
|
||||
return fmt.Sprintf("%d", int(k))
|
||||
}
|
||||
}
|
||||
|
||||
// ParseKind from a raw string, returning a keymanager kind.
|
||||
func ParseKind(k string) (Kind, error) {
|
||||
switch k {
|
||||
case "derived":
|
||||
return Derived, nil
|
||||
case "direct":
|
||||
return Imported, nil
|
||||
case "remote":
|
||||
return Remote, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("%s is not an allowed keymanager", k)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ package keymanager_test
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/remote"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = keymanager.IKeymanager(&imported.Keymanager{})
|
||||
_ = keymanager.IKeymanager(&derived.Keymanager{})
|
||||
_ = keymanager.IKeymanager(&remote.Keymanager{})
|
||||
//_ = keymanager.IKeymanager(&remote.Keymanager{})
|
||||
)
|
||||
|
||||
@@ -177,7 +177,7 @@ func (c *ValidatorClient) initializeFromCLI(cliCtx *cli.Context) error {
|
||||
if cliCtx.IsSet(flags.InteropNumValidators.Name) {
|
||||
numValidatorKeys := cliCtx.Uint64(flags.InteropNumValidators.Name)
|
||||
offset := cliCtx.Uint64(flags.InteropStartIndex.Name)
|
||||
keyManager, err = imported.NewInteropKeymanager(cliCtx.Context, offset, numValidatorKeys)
|
||||
keyManager, err = imported.NewDeterministicKeymanager(cliCtx.Context, offset, numValidatorKeys)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not generate interop keys")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user