Skip to content

Commit

Permalink
Import Accounts RPC Implementation (prysmaticlabs#7389)
Browse files Browse the repository at this point in the history
* implement the import function

* begin tests for import keystores and add preconditions

* import implementation done

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
rauljordan and prylabs-bulldozer[bot] authored Sep 30, 2020
1 parent e07b71b commit e1f8a37
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 2 deletions.
44 changes: 42 additions & 2 deletions validator/rpc/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rpc

import (
"context"
"encoding/hex"
"encoding/json"
"io/ioutil"
"path/filepath"
Expand Down Expand Up @@ -280,9 +281,48 @@ func (s *Server) ChangePassword(ctx context.Context, req *pb.ChangePasswordReque
return &ptypes.Empty{}, nil
}

// ImportKeystores --
// ImportKeystores allows importing new keystores via RPC into the wallet
// which will be decrypted using the specified password .
func (s *Server) ImportKeystores(
ctx context.Context, req *pb.ImportKeystoresRequest,
) (*pb.ImportKeystoresResponse, error) {
return nil, status.Error(codes.Unimplemented, "Unimplemented")
if s.wallet == nil {
return nil, status.Error(codes.FailedPrecondition, "No wallet initialized")
}
if s.wallet.KeymanagerKind() != v2keymanager.Direct {
return nil, status.Error(codes.FailedPrecondition, "Only Non-HD wallets can import keystores")
}
if req.KeystoresPassword == "" {
return nil, status.Error(codes.InvalidArgument, "Password required for keystores")
}
// Needs to unmarshal the keystores from the requests.
if req.KeystoresImported == nil || len(req.KeystoresImported) < 1 {
return nil, status.Error(codes.InvalidArgument, "No keystores included for import")
}
keystores := make([]*v2keymanager.Keystore, len(req.KeystoresImported))
importedPubKeys := make([][]byte, len(req.KeystoresImported))
for i := 0; i < len(req.KeystoresImported); i++ {
encoded := req.KeystoresImported[i]
keystore := &v2keymanager.Keystore{}
if err := json.Unmarshal([]byte(encoded), &keystore); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Not a valid EIP-2335 keystore JSON file: %v", err)
}
keystores[i] = keystore
pubKey, err := hex.DecodeString(keystore.Pubkey)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Not a valid BLS public key in keystore file: %v", err)
}
importedPubKeys[i] = pubKey
}
// Import the uploaded accounts.
if err := v2.ImportAccounts(ctx, &v2.ImportAccountsConfig{
Wallet: s.wallet,
Keystores: keystores,
AccountPassword: req.KeystoresPassword,
}); err != nil {
return nil, err
}
return &pb.ImportKeystoresResponse{
ImportedPublicKeys: importedPubKeys,
}, nil
}
128 changes: 128 additions & 0 deletions validator/rpc/wallet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,131 @@ func TestServer_HasWallet(t *testing.T) {
WalletExists: true,
}, resp)
}

func TestServer_ImportKeystores_FailedPreconditions_WrongKeymanagerKind(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
ctx := context.Background()
strongPass := "29384283xasjasd32%%&*@*#*"
w, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: defaultWalletPath,
KeymanagerKind: v2keymanager.Derived,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss := &Server{
wallet: w,
keymanager: km,
}
_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{})
assert.ErrorContains(t, "Only Non-HD wallets can import", err)
}

func TestServer_ImportKeystores_FailedPreconditions(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
ctx := context.Background()
strongPass := "29384283xasjasd32%%&*@*#*"
w, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: defaultWalletPath,
KeymanagerKind: v2keymanager.Direct,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(ctx))
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss := &Server{
keymanager: km,
}
_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{})
assert.ErrorContains(t, "No wallet initialized", err)
ss.wallet = w
_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{})
assert.ErrorContains(t, "Password required for keystores", err)
_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{
KeystoresPassword: strongPass,
})
assert.ErrorContains(t, "No keystores included for import", err)
_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{
KeystoresPassword: strongPass,
KeystoresImported: []string{"badjson"},
})
assert.ErrorContains(t, "Not a valid EIP-2335 keystore", err)
}

func TestServer_ImportKeystores_OK(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
ctx := context.Background()
strongPass := "29384283xasjasd32%%&*@*#*"
w, err := v2.CreateWalletWithKeymanager(ctx, &v2.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: defaultWalletPath,
KeymanagerKind: v2keymanager.Direct,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(ctx))
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss := &Server{
keymanager: km,
wallet: w,
}

// Create 3 keystores.
encryptor := keystorev4.New()
keystores := make([]string, 3)
pubKeys := make([][]byte, 3)
for i := 0; i < len(keystores); i++ {
privKey := bls.RandKey()
pubKey := fmt.Sprintf("%x", privKey.PublicKey().Marshal())
id, err := uuid.NewRandom()
require.NoError(t, err)
cryptoFields, err := encryptor.Encrypt(privKey.Marshal(), strongPass)
require.NoError(t, err)
item := &v2keymanager.Keystore{
Crypto: cryptoFields,
ID: id.String(),
Version: encryptor.Version(),
Pubkey: pubKey,
Name: encryptor.Name(),
}
encodedFile, err := json.MarshalIndent(item, "", "\t")
require.NoError(t, err)
keystores[i] = string(encodedFile)
pubKeys[i] = privKey.PublicKey().Marshal()
}

// Check the wallet has no accounts to start with.
keys, err := km.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
assert.Equal(t, 0, len(keys))

// Import the 3 keystores and verify the wallet has 3 new accounts.
res, err := ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{
KeystoresPassword: strongPass,
KeystoresImported: keystores,
})
require.NoError(t, err)
assert.DeepEqual(t, &pb.ImportKeystoresResponse{
ImportedPublicKeys: pubKeys,
}, res)

km, err = w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
keys, err = km.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
assert.Equal(t, 3, len(keys))
}

0 comments on commit e1f8a37

Please sign in to comment.