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

Recover private validator key #8099

Merged
merged 15 commits into from
Dec 8, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* Updated gRPC dependency to v1.33.2
* Updated iavl dependency to v0.15-rc2
* (version) [\#7848](https://github.com/cosmos/cosmos-sdk/pull/7848) [\#7941](https://github.com/cosmos/cosmos-sdk/pull/7941) `version --long` output now shows the list of build dependencies and replaced build dependencies.
* (x/genutil) [\#8099](https://github.com/cosmos/cosmos-sdk/pull/8099) `init` now supports a `--recover` flag to recover the private validator key from a given mnemonic

### State Machine Breaking Changes
* (x/upgrade) [\#7979](https://github.com/cosmos/cosmos-sdk/pull/7979) keeper pubkey storage serialization migration from bech32 to protobuf.
Expand Down
24 changes: 23 additions & 1 deletion x/genutil/client/cli/init.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package cli

import (
"bufio"
alessio marked this conversation as resolved.
Show resolved Hide resolved
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/cosmos/go-bip39"
"github.com/pkg/errors"
"github.com/spf13/cobra"
cfg "github.com/tendermint/tendermint/config"
Expand All @@ -16,6 +18,7 @@ import (

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/server"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
Expand All @@ -25,6 +28,9 @@ import (
const (
// FlagOverwrite defines a flag to overwrite an existing genesis JSON file.
FlagOverwrite = "overwrite"

// FlagSeed defines a flag to initialize the private validator key from a specific seed.
FlagRecover = "recover"
)

type printInfo struct {
Expand Down Expand Up @@ -78,7 +84,22 @@ func InitCmd(mbm module.BasicManager, defaultNodeHome string) *cobra.Command {
chainID = fmt.Sprintf("test-chain-%v", tmrand.Str(6))
}

nodeID, _, err := genutil.InitializeNodeValidatorFiles(config)
// Get bip39 mnemonic
var mnemonic string
recover, _ := cmd.Flags().GetBool(FlagRecover)
if recover {
inBuf := bufio.NewReader(cmd.InOrStdin())
mnemonic, err := input.GetString("Enter your bip39 mnemonic", inBuf)
if err != nil {
return err
}

if !bip39.IsMnemonicValid(mnemonic) {
return errors.New("invalid mnemonic")
}
}

nodeID, _, err := genutil.InitializeNodeValidatorFilesFromMnemonic(config, mnemonic)
if err != nil {
return err
}
Expand Down Expand Up @@ -124,6 +145,7 @@ func InitCmd(mbm module.BasicManager, defaultNodeHome string) *cobra.Command {

cmd.Flags().String(cli.HomeFlag, defaultNodeHome, "node's home directory")
cmd.Flags().BoolP(FlagOverwrite, "o", false, "overwrite the genesis.json file")
cmd.Flags().Bool(FlagRecover, false, "provide seed phrase to recover existing key instead of creating")
cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")

return cmd
Expand Down
32 changes: 32 additions & 0 deletions x/genutil/client/cli/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/mock"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/genutil"
Expand Down Expand Up @@ -86,6 +87,37 @@ func TestInitCmd(t *testing.T) {

}

func TestInitRecover(t *testing.T) {
home := t.TempDir()
logger := log.NewNopLogger()
cfg, err := genutiltest.CreateDefaultTendermintConfig(home)
require.NoError(t, err)

serverCtx := server.NewContext(viper.New(), cfg, logger)
interfaceRegistry := types.NewInterfaceRegistry()
marshaler := codec.NewProtoCodec(interfaceRegistry)
clientCtx := client.Context{}.
WithJSONMarshaler(marshaler).
WithLegacyAmino(makeCodec()).
WithHomeDir(home)

ctx := context.Background()
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx)

cmd := genutilcli.InitCmd(testMbm, home)
mockIn := testutil.ApplyMockIODiscardOutErr(cmd)

cmd.SetArgs([]string{
"appnode-test",
fmt.Sprintf("--%s=true", genutilcli.FlagRecover),
})

// use valid mnemonic and complete recovery key generation successfully
mockIn.Reset("decide praise business actor peasant farm drastic weather extend front hurt later song give verb rhythm worry fun pond reform school tumble august one\n")
require.NoError(t, cmd.ExecuteContext(ctx))
}

func TestEmptyState(t *testing.T) {
home := t.TempDir()
logger := log.NewNopLogger()
Expand Down
23 changes: 22 additions & 1 deletion x/genutil/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package genutil

import (
"encoding/json"
"fmt"
"path/filepath"
"time"

"github.com/cosmos/go-bip39"
cfg "github.com/tendermint/tendermint/config"
tmed25519 "github.com/tendermint/tendermint/crypto/ed25519"
tmos "github.com/tendermint/tendermint/libs/os"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/privval"
Expand Down Expand Up @@ -48,6 +51,16 @@ func ExportGenesisFileWithTime(

// InitializeNodeValidatorFiles creates private validator and p2p configuration files.
func InitializeNodeValidatorFiles(config *cfg.Config) (nodeID string, valPubKey cryptotypes.PubKey, err error) {
return InitializeNodeValidatorFilesFromMnemonic(config, "")
}

// InitializeNodeValidatorFiles creates private validator and p2p configuration files using the given mnemonic.
// If no valid mnemonic is given, a random one will be used instead.
func InitializeNodeValidatorFilesFromMnemonic(config *cfg.Config, mnemonic string) (nodeID string, valPubKey cryptotypes.PubKey, err error) {
if len(mnemonic) > 0 && !bip39.IsMnemonicValid(mnemonic) {
return "", nil, fmt.Errorf("invalid mnemonic")
}

nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return "", nil, err
Expand All @@ -65,7 +78,15 @@ func InitializeNodeValidatorFiles(config *cfg.Config) (nodeID string, valPubKey
return "", nil, err
}

tmValPubKey, err := privval.LoadOrGenFilePV(pvKeyFile, pvStateFile).GetPubKey()
var filePV *privval.FilePV
if len(mnemonic) == 0 {
filePV = privval.LoadOrGenFilePV(pvKeyFile, pvStateFile)
} else {
privKey := tmed25519.GenPrivKeyFromSecret([]byte(mnemonic))
filePV = privval.NewFilePV(privKey, pvKeyFile, pvStateFile)
}

tmValPubKey, err := filePV.GetPubKey()
if err != nil {
return "", nil, err
}
Expand Down
45 changes: 45 additions & 0 deletions x/genutil/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package genutil

import (
"encoding/json"
"os"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/config"
)

func TestExportGenesisFileWithTime(t *testing.T) {
Expand All @@ -16,3 +18,46 @@ func TestExportGenesisFileWithTime(t *testing.T) {

require.NoError(t, ExportGenesisFileWithTime(fname, "test", nil, json.RawMessage(`{"account_owner": "Bob"}`), time.Now()))
}

func TestInitializeNodeValidatorFilesFromMnemonic(t *testing.T) {
t.Parallel()

cfg := config.TestConfig()
cfg.RootDir = t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(cfg.RootDir, "config"), 0755))

tests := []struct {
name string
mnemonic string
expError bool
}{
{
name: "invalid mnemonic returns error",
mnemonic: "side video kiss hotel essence",
expError: true,
},
{
name: "empty mnemonic does not return error",
mnemonic: "",
expError: false,
},
{
name: "valid mnemonic does not return error",
mnemonic: "side video kiss hotel essence door angle student degree during vague adjust submit trick globe muscle frozen vacuum artwork million shield bind useful wave",
expError: false,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
_, _, err := InitializeNodeValidatorFilesFromMnemonic(cfg, tt.mnemonic)

if tt.expError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}