diff --git a/CHANGELOG.md b/CHANGELOG.md index 162c3bbea90..84555f11f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,19 +70,22 @@ and provided directly the IAVL store. * (modules) [\#5572](https://github.com/cosmos/cosmos-sdk/pull/5572) Move account balance logic and APIs from `x/auth` to `x/bank`. * (types) [\#5533](https://github.com/cosmos/cosmos-sdk/pull/5533) Refactored `AppModuleBasic` and `AppModuleGenesis` to now accept a `codec.JSONMarshaler` for modular serialization of genesis state. -* (crypto/keyring) [\#5735](https://github.com/cosmos/cosmos-sdk/pull/5735) Keyring's `Update()` function is now no-op. * (types/rest) [\#5779](https://github.com/cosmos/cosmos-sdk/pull/5779) Drop unused Parse{Int64OrReturnBadRequest,QueryParamBool}() functions. * (keys) [\#5820](https://github.com/cosmos/cosmos-sdk/pull/5820/) Removed method CloseDB from Keybase interface. * (baseapp) [\#5837](https://github.com/cosmos/cosmos-sdk/issues/5837) Transaction simulation now returns a `SimulationResponse` which contains the `GasInfo` and `Result` from the execution. -* (crypto/keyring) [\#5866](https://github.com/cosmos/cosmos-sdk/pull/5866) Move `Keyring` and `Keybase` implementations and their associated types from `crypto/keys/` to `crypto/keyring/`. -* (crypto) [\#5880](https://github.com/cosmos/cosmos-sdk/pull/5880) Merge `crypto/keys/mintkey` into `crypto`. -* (crypto/keyring) [\#5858](https://github.com/cosmos/cosmos-sdk/pull/5858) Make Keyring store keys by name and address's hexbytes representation. -* (crypto/keyring) [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) Deprecate old keybase implementation: - - Remove `Update` from the `Keybase` interface. - - `NewKeyring()` now accepts a new backend: `MemoryBackend`. - - `New()` has been renamed to`NewLegacy()`, which now returns a `LegacyKeybase` type that only allows migration of keys from the legacy keybase to a new keyring. +* (client/input) [\#5904](https://github.com/cosmos/cosmos-sdk/pull/5904) Removal of unnecessary `GetCheckPassword`, `PrintPrefixed` functions. * (client/keys) [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) Rename `NewKeyBaseFromDir()` -> `NewLegacyKeyBaseFromDir()`. +* (crypto) [\#5880](https://github.com/cosmos/cosmos-sdk/pull/5880) Merge `crypto/keys/mintkey` into `crypto`. +* (crypto/hd) [\#5904](https://github.com/cosmos/cosmos-sdk/pull/5904) `crypto/keys/hd` moved to `crypto/hd`. +* (crypto/keyring): + - [\#5866](https://github.com/cosmos/cosmos-sdk/pull/5866) Rename `crypto/keys/` to `crypto/keyring/`. + - [\#5904](https://github.com/cosmos/cosmos-sdk/pull/5904) `Keybase` -> `Keyring` interfaces migration. `LegacyKeybase` interface is added in order +to guarantee limited backward compatibility with the old Keybase interface for the sole purpose of migrating keys across the new keyring backends. `NewLegacy` +constructor is provided [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) to allow for smooth migration of keys from the legacy LevelDB based implementation +to new keyring backends. Plus, the package and the new keyring no longer depends on the sdk.Config singleton. Please consult the package documentation for more +information on how to implement the new `Keyring` interface. + - [\#5858](https://github.com/cosmos/cosmos-sdk/pull/5858) Make Keyring store keys by name and address's hexbytes representation. ### Features @@ -92,8 +95,8 @@ to now accept a `codec.JSONMarshaler` for modular serialization of genesis state * (types) [\#5741](https://github.com/cosmos/cosmos-sdk/issues/5741) Prevent ChainAnteDecorators() from panicking when empty AnteDecorator slice is supplied. * (modules) [\#5569](https://github.com/cosmos/cosmos-sdk/issues/5569) `InitGenesis`, for the relevant modules, now ensures module accounts exist. -* (crypto/keyring) [\#5844](https://github.com/cosmos/cosmos-sdk/pull/5844) Keybase/Keyring `Sign()` methods no longer decode amino signatures -when method receivers are offline/multisig keys. +* (crypto/keyring) [\#5844](https://github.com/cosmos/cosmos-sdk/pull/5844) `Keyring.Sign()` methods no longer decode amino signatures when method receivers +are offline/multisig keys. * (x/auth) [\#5892](https://github.com/cosmos/cosmos-sdk/pull/5892) Add `RegisterKeyTypeCodec` to register new types (eg. keys) to the `auth` module internal amino codec. * (rest) [\#5906](https://github.com/cosmos/cosmos-sdk/pull/5906) Fix an issue that make some REST calls panic when sending diff --git a/client/context/context.go b/client/context/context.go index 5e536251b0b..c908a2044a8 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -25,8 +25,8 @@ type CLIContext struct { Client rpcclient.Client ChainID string Marshaler codec.Marshaler - Keybase keyring.Keybase Input io.Reader + Keyring keyring.Keyring Output io.Writer OutputFormat string Height int64 @@ -58,8 +58,19 @@ func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext { var nodeURI string var rpc rpcclient.Client + homedir := viper.GetString(flags.FlagHome) genOnly := viper.GetBool(flags.FlagGenerateOnly) - fromAddress, fromName, err := GetFromFields(input, from, genOnly) + backend := viper.GetString(flags.FlagKeyringBackend) + if len(backend) == 0 { + backend = keyring.BackendMemory + } + + keyring, err := newKeyringFromFlags(backend, homedir, input, genOnly) + if err != nil { + panic(fmt.Errorf("couldn't acquire keyring: %v", err)) + } + + fromAddress, fromName, err := GetFromFields(keyring, from, genOnly) if err != nil { fmt.Printf("failed to get from fields: %v\n", err) os.Exit(1) @@ -84,9 +95,10 @@ func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext { Output: os.Stdout, NodeURI: nodeURI, From: viper.GetString(flags.FlagFrom), + Keyring: keyring, OutputFormat: viper.GetString(cli.OutputFlag), Height: viper.GetInt64(flags.FlagHeight), - HomeDir: viper.GetString(flags.FlagHome), + HomeDir: homedir, TrustNode: viper.GetBool(flags.FlagTrustNode), UseLedger: viper.GetBool(flags.FlagUseLedger), BroadcastMode: viper.GetString(flags.FlagBroadcastMode), @@ -129,6 +141,12 @@ func NewCLIContextWithInput(input io.Reader) CLIContext { return NewCLIContextWithInputAndFrom(input, viper.GetString(flags.FlagFrom)) } +// WithKeyring returns a copy of the context with an updated keyring. +func (ctx CLIContext) WithKeyring(k keyring.Keyring) CLIContext { + ctx.Keyring = k + return ctx +} + // WithInput returns a copy of the context with an updated input. func (ctx CLIContext) WithInput(r io.Reader) CLIContext { ctx.Input = r @@ -307,7 +325,7 @@ func (ctx CLIContext) PrintOutput(toPrint interface{}) error { // GetFromFields returns a from account address and Keybase name given either // an address or key name. If genOnly is true, only a valid Bech32 cosmos // address is returned. -func GetFromFields(input io.Reader, from string, genOnly bool) (sdk.AccAddress, string, error) { +func GetFromFields(kr keyring.Keyring, from string, genOnly bool) (sdk.AccAddress, string, error) { if from == "" { return nil, "", nil } @@ -321,20 +339,14 @@ func GetFromFields(input io.Reader, from string, genOnly bool) (sdk.AccAddress, return addr, "", nil } - keybase, err := keyring.NewKeyring(sdk.KeyringServiceName(), - viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), input) - if err != nil { - return nil, "", err - } - var info keyring.Info if addr, err := sdk.AccAddressFromBech32(from); err == nil { - info, err = keybase.GetByAddress(addr) + info, err = kr.KeyByAddress(addr) if err != nil { return nil, "", err } } else { - info, err = keybase.Get(from) + info, err = kr.Key(from) if err != nil { return nil, "", err } @@ -342,3 +354,10 @@ func GetFromFields(input io.Reader, from string, genOnly bool) (sdk.AccAddress, return info.GetAddress(), info.GetName(), nil } + +func newKeyringFromFlags(backend, homedir string, input io.Reader, genOnly bool) (keyring.Keyring, error) { + if genOnly { + return keyring.New(sdk.KeyringServiceName(), keyring.BackendMemory, homedir, input) + } + return keyring.New(sdk.KeyringServiceName(), backend, homedir, input) +} diff --git a/client/context/context_test.go b/client/context/context_test.go index 3b6ea0e71af..7429fc18605 100644 --- a/client/context/context_test.go +++ b/client/context/context_test.go @@ -1,13 +1,16 @@ -package context +package context_test import ( + "os" "testing" + "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/viper" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/flags" ) @@ -15,7 +18,7 @@ func TestCLIContext_WithOffline(t *testing.T) { viper.Set(flags.FlagOffline, true) viper.Set(flags.FlagNode, "tcp://localhost:26657") - ctx := NewCLIContext() + ctx := context.NewCLIContext() require.True(t, ctx.Offline) require.Nil(t, ctx.Client) @@ -24,7 +27,7 @@ func TestCLIContext_WithOffline(t *testing.T) { viper.Set(flags.FlagOffline, false) viper.Set(flags.FlagNode, "tcp://localhost:26657") - ctx = NewCLIContext() + ctx = context.NewCLIContext() require.False(t, ctx.Offline) require.NotNil(t, ctx.Client) } @@ -59,10 +62,26 @@ func TestCLIContext_WithGenOnly(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - ctx := NewCLIContextWithFrom(tt.from) + ctx := context.NewCLIContextWithFrom(tt.from) require.Equal(t, tt.expectedFromAddr, ctx.FromAddress) require.Equal(t, tt.expectedFromName, ctx.FromName) }) } } + +func TestCLIContext_WithKeyring(t *testing.T) { + viper.Set(flags.FlagGenerateOnly, true) + ctx := context.NewCLIContextWithFrom("cosmos1q7380u26f7ntke3facjmynajs4umlr329vr4ja") + require.NotNil(t, ctx.Keyring) + kr := ctx.Keyring + ctx = ctx.WithKeyring(nil) + require.Nil(t, ctx.Keyring) + ctx = ctx.WithKeyring(kr) + require.Equal(t, kr, ctx.Keyring) +} + +func TestMain(m *testing.M) { + viper.Set(flags.FlagKeyringBackend, keyring.BackendMemory) + os.Exit(m.Run()) +} diff --git a/client/input/input.go b/client/input/input.go index 73dd822fd36..4e36bbe5192 100644 --- a/client/input/input.go +++ b/client/input/input.go @@ -2,8 +2,8 @@ package input import ( "bufio" - "errors" "fmt" + "io" "os" "strings" @@ -36,40 +36,15 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) { return pass, nil } -// GetCheckPassword will prompt for a password twice to verify they -// match (for creating a new password). -// It enforces the password length. Only parses password once if -// input is piped in. -func GetCheckPassword(prompt, prompt2 string, buf *bufio.Reader) (string, error) { - // simple read on no-tty - if !inputIsTty() { - return GetPassword(prompt, buf) - } - - // TODO: own function??? - pass, err := GetPassword(prompt, buf) - if err != nil { - return "", err - } - pass2, err := GetPassword(prompt2, buf) - if err != nil { - return "", err - } - if pass != pass2 { - return "", errors.New("passphrases don't match") - } - return pass, nil -} - // GetConfirmation will request user give the confirmation from stdin. // "y", "Y", "yes", "YES", and "Yes" all count as confirmations. // If the input is not recognized, it returns false and a nil error. -func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) { +func GetConfirmation(prompt string, r *bufio.Reader, w io.Writer) (bool, error) { if inputIsTty() { fmt.Printf("%s [y/N]: ", prompt) } - response, err := readLineFromBuf(buf) + response, err := readLineFromBuf(r) if err != nil { return false, err } @@ -90,7 +65,7 @@ func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) { // GetString simply returns the trimmed string output of a given reader. func GetString(prompt string, buf *bufio.Reader) (string, error) { if inputIsTty() && prompt != "" { - PrintPrefixed(prompt) + fmt.Fprintf(os.Stderr, "> %s\n", prompt) } out, err := readLineFromBuf(buf) @@ -117,9 +92,3 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) { } return strings.TrimSpace(pass), nil } - -// PrintPrefixed prints a string with > prefixed for use in prompts. -func PrintPrefixed(msg string) { - msg = fmt.Sprintf("> %s\n", msg) - fmt.Fprint(os.Stderr, msg) -} diff --git a/client/keys/add.go b/client/keys/add.go index 1170c714ee3..c6a396e5d3a 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" @@ -27,6 +28,7 @@ const ( flagInteractive = "interactive" flagRecover = "recover" flagNoBackup = "no-backup" + flagCoinType = "coin-type" flagAccount = "account" flagIndex = "index" flagMultisig = "multisig" @@ -73,19 +75,20 @@ the flag --nosort is set. cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)") cmd.Flags().Bool(flags.FlagDryRun, false, "Perform action, but don't add key to local keystore") cmd.Flags().String(flagHDPath, "", "Manual HD Path derivation (overrides BIP44 config)") + cmd.Flags().Uint32(flagCoinType, sdk.CoinType, "coin type number for HD derivation") cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation") cmd.Flags().Uint32(flagIndex, 0, "Address index number for HD derivation") cmd.Flags().Bool(flags.FlagIndentResponse, false, "Add indent to JSON response") - cmd.Flags().String(flagKeyAlgo, string(keyring.Secp256k1), "Key signing algorithm to generate keys for") + cmd.Flags().String(flagKeyAlgo, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for") return cmd } -func getKeybase(transient bool, buf io.Reader) (keyring.Keybase, error) { +func getKeybase(transient bool, buf io.Reader) (keyring.Keyring, error) { if transient { - return keyring.NewKeyring(sdk.KeyringServiceName(), keyring.BackendMemory, viper.GetString(flags.FlagHome), buf) + return keyring.New(sdk.KeyringServiceName(), keyring.BackendMemory, viper.GetString(flags.FlagHome), buf) } - return keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf) + return keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf) } func runAddCmd(cmd *cobra.Command, args []string) error { @@ -107,7 +110,7 @@ input output - armor encrypted private key (saved to file) */ -func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *bufio.Reader) error { +func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keyring, inBuf *bufio.Reader) error { var err error name := args[0] @@ -115,25 +118,27 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf interactive := viper.GetBool(flagInteractive) showMnemonic := !viper.GetBool(flagNoBackup) - algo := keyring.SigningAlgo(viper.GetString(flagKeyAlgo)) - if algo == keyring.SigningAlgo("") { - algo = keyring.Secp256k1 - } - if !keyring.IsSupportedAlgorithm(kb.SupportedAlgos(), algo) { - return keyring.ErrUnsupportedSigningAlgo + algo, err := keyring.NewSigningAlgoFromString(viper.GetString(flagKeyAlgo)) + if err != nil { + algo = hd.Secp256k1 } if !viper.GetBool(flags.FlagDryRun) { - _, err = kb.Get(name) + _, err = kb.Key(name) if err == nil { // account exists, ask for user confirmation - response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", name), inBuf) + response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", name), inBuf, cmd.ErrOrStderr()) if err2 != nil { return err2 } if !response { return errors.New("aborted") } + + err2 = kb.Delete(name) + if err2 != nil { + return err2 + } } multisigKeys := viper.GetStringSlice(flagMultisig) @@ -146,7 +151,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf } for _, keyname := range multisigKeys { - k, err := kb.Get(keyname) + k, err := kb.Key(keyname) if err != nil { return err } @@ -161,7 +166,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf } pk := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks) - if _, err := kb.CreateMulti(name, pk); err != nil { + if _, err := kb.SaveMultisig(name, pk); err != nil { return err } @@ -175,13 +180,14 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf if err != nil { return err } - _, err = kb.CreateOffline(name, pk, algo) + _, err = kb.SavePubKey(name, pk, algo.Name()) if err != nil { return err } return nil } + coinType := uint32(viper.GetInt(flagCoinType)) account := uint32(viper.GetInt(flagAccount)) index := uint32(viper.GetInt(flagIndex)) @@ -189,7 +195,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf var hdPath string if useBIP44 { - hdPath = keyring.CreateHDPath(account, index).String() + hdPath = hd.CreateHDPath(coinType, account, index).String() } else { hdPath = viper.GetString(flagHDPath) } @@ -201,12 +207,8 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf return errors.New("cannot set custom bip32 path with ledger") } - if !keyring.IsSupportedAlgorithm(kb.SupportedAlgosLedger(), algo) { - return keyring.ErrUnsupportedSigningAlgo - } - bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix() - info, err := kb.CreateLedger(name, keyring.Secp256k1, bech32PrefixAccAddr, account, index) + info, err := kb.SaveLedgerKey(name, hd.Secp256k1, bech32PrefixAccAddr, coinType, account, index) if err != nil { return err } @@ -269,7 +271,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf } } - info, err := kb.CreateAccount(name, mnemonic, bip39Passphrase, DefaultKeyPass, hdPath, algo) + info, err := kb.NewAccount(name, mnemonic, bip39Passphrase, hdPath, algo) if err != nil { return err } diff --git a/client/keys/add_ledger_test.go b/client/keys/add_ledger_test.go index b21a0b1850d..dc78c778a48 100644 --- a/client/keys/add_ledger_test.go +++ b/client/keys/add_ledger_test.go @@ -41,6 +41,9 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) { t.Cleanup(kbCleanUp) viper.Set(flags.FlagHome, kbHome) viper.Set(flags.FlagUseLedger, true) + viper.Set(flagAccount, "0") + viper.Set(flagIndex, "0") + viper.Set(flagCoinType, "330") /// Test Text viper.Set(cli.OutputFlag, OutputFormatText) @@ -50,14 +53,14 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) { require.NoError(t, runAddCmd(cmd, []string{"keyname1"})) // Now check that it has been stored properly - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) require.NoError(t, err) require.NotNil(t, kb) t.Cleanup(func() { - kb.Delete("keyname1", "", false) + kb.Delete("keyname1") }) mockIn.Reset("test1234\n") - key1, err := kb.Get("keyname1") + key1, err := kb.Key("keyname1") require.NoError(t, err) require.NotNil(t, key1) @@ -90,17 +93,18 @@ func Test_runAddCmdLedger(t *testing.T) { viper.Set(cli.OutputFlag, OutputFormatText) // Now enter password mockIn.Reset("test1234\ntest1234\n") + viper.Set(flagCoinType, sdk.CoinType) require.NoError(t, runAddCmd(cmd, []string{"keyname1"})) // Now check that it has been stored properly - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) require.NoError(t, err) require.NotNil(t, kb) t.Cleanup(func() { - kb.Delete("keyname1", "", false) + kb.Delete("keyname1") }) mockIn.Reset("test1234\n") - key1, err := kb.Get("keyname1") + key1, err := kb.Key("keyname1") require.NoError(t, err) require.NotNil(t, key1) diff --git a/client/keys/add_test.go b/client/keys/add_test.go index 172d7d9e1ce..5776aa8b074 100644 --- a/client/keys/add_test.go +++ b/client/keys/add_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/spf13/viper" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/cli" @@ -17,36 +16,37 @@ import ( func Test_runAddCmdBasic(t *testing.T) { cmd := AddKeyCommand() - assert.NotNil(t, cmd) + require.NotNil(t, cmd) mockIn, _, _ := tests.ApplyMockIO(cmd) kbHome, kbCleanUp := tests.NewTestCaseDir(t) - assert.NotNil(t, kbHome) + require.NotNil(t, kbHome) t.Cleanup(kbCleanUp) viper.Set(flags.FlagHome, kbHome) viper.Set(cli.OutputFlag, OutputFormatText) + viper.Set(flags.FlagUseLedger, false) mockIn.Reset("y\n") - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) require.NoError(t, err) t.Cleanup(func() { - kb.Delete("keyname1", "", false) // nolint:errcheck - kb.Delete("keyname2", "", false) // nolint:errcheck + kb.Delete("keyname1") // nolint:errcheck + kb.Delete("keyname2") // nolint:errcheck }) - assert.NoError(t, runAddCmd(cmd, []string{"keyname1"})) + require.NoError(t, runAddCmd(cmd, []string{"keyname1"})) mockIn.Reset("N\n") - assert.Error(t, runAddCmd(cmd, []string{"keyname1"})) + require.Error(t, runAddCmd(cmd, []string{"keyname1"})) - assert.NoError(t, runAddCmd(cmd, []string{"keyname2"})) - assert.Error(t, runAddCmd(cmd, []string{"keyname2"})) + require.NoError(t, runAddCmd(cmd, []string{"keyname2"})) + require.Error(t, runAddCmd(cmd, []string{"keyname2"})) mockIn.Reset("y\n") - assert.NoError(t, runAddCmd(cmd, []string{"keyname2"})) + require.NoError(t, runAddCmd(cmd, []string{"keyname2"})) // test --dry-run - assert.NoError(t, runAddCmd(cmd, []string{"keyname4"})) - assert.Error(t, runAddCmd(cmd, []string{"keyname4"})) + require.NoError(t, runAddCmd(cmd, []string{"keyname4"})) + require.Error(t, runAddCmd(cmd, []string{"keyname4"})) viper.Set(flags.FlagDryRun, true) - assert.NoError(t, runAddCmd(cmd, []string{"keyname4"})) + require.NoError(t, runAddCmd(cmd, []string{"keyname4"})) } diff --git a/client/keys/codec_test.go b/client/keys/codec_test.go index 7aca4510f95..33f6103d188 100644 --- a/client/keys/codec_test.go +++ b/client/keys/codec_test.go @@ -1,12 +1,12 @@ -package keys +package keys_test import ( - "fmt" - "reflect" + "bytes" "testing" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/crypto/keyring" ) @@ -58,15 +58,9 @@ func TestMarshalJSON(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - got, err := MarshalJSON(tt.args.o) - if (err != nil) != tt.wantErr { - t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) - return - } - fmt.Printf("%s\n", got) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("MarshalJSON() = %v, want %v", got, tt.want) - } + got, err := keys.MarshalJSON(tt.args.o) + require.Equal(t, tt.wantErr, err != nil) + require.True(t, bytes.Equal(got, tt.want)) }) } } @@ -94,10 +88,8 @@ func TestUnmarshalJSON(t *testing.T) { for idx, tt := range tests { idx, tt := idx, tt t.Run(tt.name, func(t *testing.T) { - if err := UnmarshalJSON(tt.args.bz, tt.args.ptr); (err != nil) != tt.wantErr { - t.Errorf("unmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) - } - + err := keys.UnmarshalJSON(tt.args.bz, tt.args.ptr) + require.Equal(t, tt.wantErr, err != nil) // Confirm deserialized objects are the same require.Equal(t, data.Keys[idx], data.Answers[idx]) }) diff --git a/client/keys/delete.go b/client/keys/delete.go index 0c379e49bca..e0501969998 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -2,7 +2,6 @@ package keys import ( "bufio" - "errors" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" @@ -43,49 +42,36 @@ private keys stored in a ledger device cannot be deleted with the CLI. func runDeleteCmd(cmd *cobra.Command, args []string) error { buf := bufio.NewReader(cmd.InOrStdin()) - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf) if err != nil { return err } for _, name := range args { - info, err := kb.Get(name) + info, err := kb.Key(name) if err != nil { return err } - if info.GetType() == keyring.TypeLedger || info.GetType() == keyring.TypeOffline { - // confirm deletion, unless -y is passed - if !viper.GetBool(flagYes) { - if err := confirmDeletion(buf); err != nil { - return err - } - } - - if err := kb.Delete(name, "", true); err != nil { + // confirm deletion, unless -y is passed + if !viper.GetBool(flagYes) { + if yes, err := input.GetConfirmation("Key reference will be deleted. Continue?", buf, cmd.ErrOrStderr()); err != nil { return err + } else if !yes { + continue } - cmd.PrintErrln("Public key reference deleted") - return nil } - // old password and skip flag arguments are ignored - if err := kb.Delete(name, "", true); err != nil { + if err := kb.Delete(name); err != nil { return err } + + if info.GetType() == keyring.TypeLedger || info.GetType() == keyring.TypeOffline { + cmd.PrintErrln("Public key reference deleted") + continue + } cmd.PrintErrln("Key deleted forever (uh oh!)") } return nil } - -func confirmDeletion(buf *bufio.Reader) error { - answer, err := input.GetConfirmation("Key reference will be deleted. Continue?", buf) - if err != nil { - return err - } - if !answer { - return errors.New("aborted") - } - return nil -} diff --git a/client/keys/delete_test.go b/client/keys/delete_test.go index 73db7faecb6..3d2136a7252 100644 --- a/client/keys/delete_test.go +++ b/client/keys/delete_test.go @@ -1,13 +1,13 @@ package keys import ( - "bufio" - "strings" "testing" "github.com/spf13/viper" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/tests" @@ -20,30 +20,25 @@ func Test_runDeleteCmd(t *testing.T) { yesF, _ := deleteKeyCommand.Flags().GetBool(flagYes) forceF, _ := deleteKeyCommand.Flags().GetBool(flagForce) + require.False(t, yesF) require.False(t, forceF) fakeKeyName1 := "runDeleteCmd_Key1" fakeKeyName2 := "runDeleteCmd_Key2" - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) - require.NoError(t, err) - t.Cleanup(func() { - kb.Delete("runDeleteCmd_Key1", "", false) // nolint:errcheck - kb.Delete("runDeleteCmd_Key2", "", false) // nolint:errcheck - - }) // Now add a temporary keybase kbHome, cleanUp := tests.NewTestCaseDir(t) t.Cleanup(cleanUp) viper.Set(flags.FlagHome, kbHome) // Now - kb, err = keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) + path := sdk.GetConfig().GetFullFundraiserPath() + backend := viper.GetString(flags.FlagKeyringBackend) + kb, err := keyring.New(sdk.KeyringServiceName(), backend, kbHome, mockIn) require.NoError(t, err) - _, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keyring.Secp256k1) + _, err = kb.NewAccount(fakeKeyName1, tests.TestMnemonic, "", path, hd.Secp256k1) require.NoError(t, err) - - _, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keyring.Secp256k1) + _, _, err = kb.NewMnemonic(fakeKeyName2, keyring.English, sdk.FullFundraiserPath, hd.Secp256k1) require.NoError(t, err) err = runDeleteCmd(deleteKeyCommand, []string{"blah"}) @@ -55,53 +50,21 @@ func Test_runDeleteCmd(t *testing.T) { require.Error(t, err) require.Equal(t, "EOF", err.Error()) - { - _, err = kb.Get(fakeKeyName1) - require.NoError(t, err) + _, err = kb.Key(fakeKeyName1) + require.NoError(t, err) - // Now there is a confirmation - viper.Set(flagYes, true) - require.NoError(t, runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1})) + // Now there is a confirmation + viper.Set(flagYes, true) + require.NoError(t, runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1})) - _, err = kb.Get(fakeKeyName1) - require.Error(t, err) // Key1 is gone - } + _, err = kb.Key(fakeKeyName1) + require.Error(t, err) // Key1 is gone viper.Set(flagYes, true) - _, err = kb.Get(fakeKeyName2) + _, err = kb.Key(fakeKeyName2) require.NoError(t, err) err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName2}) require.NoError(t, err) - _, err = kb.Get(fakeKeyName2) + _, err = kb.Key(fakeKeyName2) require.Error(t, err) // Key2 is gone } - -func Test_confirmDeletion(t *testing.T) { - type args struct { - buf *bufio.Reader - } - - answerYes := bufio.NewReader(strings.NewReader("y\n")) - answerYes2 := bufio.NewReader(strings.NewReader("Y\n")) - answerNo := bufio.NewReader(strings.NewReader("n\n")) - answerInvalid := bufio.NewReader(strings.NewReader("245\n")) - - tests := []struct { - name string - args args - wantErr bool - }{ - {"Y", args{answerYes}, false}, - {"y", args{answerYes2}, false}, - {"N", args{answerNo}, true}, - {"BAD", args{answerInvalid}, true}, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - if err := confirmDeletion(tt.args.buf); (err != nil) != tt.wantErr { - t.Errorf("confirmDeletion() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/client/keys/export.go b/client/keys/export.go index dba66d8352a..2c17bfe84f7 100644 --- a/client/keys/export.go +++ b/client/keys/export.go @@ -25,21 +25,17 @@ func ExportKeyCommand() *cobra.Command { func runExportCmd(cmd *cobra.Command, args []string) error { buf := bufio.NewReader(cmd.InOrStdin()) - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf) if err != nil { return err } - decryptPassword, err := input.GetPassword("Enter passphrase to decrypt your key:", buf) - if err != nil { - return err - } encryptPassword, err := input.GetPassword("Enter passphrase to encrypt the exported key:", buf) if err != nil { return err } - armored, err := kb.ExportPrivKey(args[0], decryptPassword, encryptPassword) + armored, err := kb.ExportPrivKeyArmor(args[0], encryptPassword) if err != nil { return err } diff --git a/client/keys/export_test.go b/client/keys/export_test.go index 175acdfe92f..99262ceaa94 100644 --- a/client/keys/export_test.go +++ b/client/keys/export_test.go @@ -6,6 +6,8 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/tests" @@ -22,13 +24,14 @@ func Test_runExportCmd(t *testing.T) { viper.Set(flags.FlagHome, kbHome) // create a key - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) require.NoError(t, err) t.Cleanup(func() { - kb.Delete("keyname1", "", false) // nolint:errcheck + kb.Delete("keyname1") // nolint:errcheck }) - _, err = kb.CreateAccount("keyname1", tests.TestMnemonic, "", "123456789", "", keyring.Secp256k1) + path := sdk.GetConfig().GetFullFundraiserPath() + _, err = kb.NewAccount("keyname1", tests.TestMnemonic, "", path, hd.Secp256k1) require.NoError(t, err) // Now enter password diff --git a/client/keys/import.go b/client/keys/import.go index 89a1f3769f8..cbba4598dcb 100644 --- a/client/keys/import.go +++ b/client/keys/import.go @@ -26,7 +26,7 @@ func ImportKeyCommand() *cobra.Command { func runImportCmd(cmd *cobra.Command, args []string) error { buf := bufio.NewReader(cmd.InOrStdin()) - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf) if err != nil { return err } diff --git a/client/keys/import_test.go b/client/keys/import_test.go index cad3d6a44d3..6451277d3e7 100644 --- a/client/keys/import_test.go +++ b/client/keys/import_test.go @@ -23,10 +23,10 @@ func Test_runImportCmd(t *testing.T) { t.Cleanup(cleanUp) viper.Set(flags.FlagHome, kbHome) - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) require.NoError(t, err) t.Cleanup(func() { - kb.Delete("keyname1", "", false) // nolint:errcheck + kb.Delete("keyname1") // nolint:errcheck }) keyfile := filepath.Join(kbHome, "key.asc") diff --git a/client/keys/list.go b/client/keys/list.go index 770bde2f146..9ec4df43bf4 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -26,7 +26,7 @@ along with their associated name and address.`, } func runListCmd(cmd *cobra.Command, _ []string) error { - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin()) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin()) if err != nil { return err } diff --git a/client/keys/list_test.go b/client/keys/list_test.go index d5d4eebcf70..849ac9b8d8d 100644 --- a/client/keys/list_test.go +++ b/client/keys/list_test.go @@ -7,6 +7,8 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/tests" @@ -31,14 +33,15 @@ func Test_runListCmd(t *testing.T) { viper.Set(flags.FlagHome, kbHome2) mockIn, _, _ := tests.ApplyMockIO(cmdBasic) - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) require.NoError(t, err) - _, err = kb.CreateAccount("something", tests.TestMnemonic, "", "", "", keyring.Secp256k1) + path := "" //sdk.GetConfig().GetFullFundraiserPath() + _, err = kb.NewAccount("something", tests.TestMnemonic, "", path, hd.Secp256k1) require.NoError(t, err) t.Cleanup(func() { - kb.Delete("something", "", false) // nolint:errcheck + kb.Delete("something") // nolint:errcheck }) testData := []struct { diff --git a/client/keys/migrate.go b/client/keys/migrate.go index 840db70f10e..8d52bd83faf 100644 --- a/client/keys/migrate.go +++ b/client/keys/migrate.go @@ -60,21 +60,21 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error { keyringServiceName := sdk.KeyringServiceName() var ( - tmpDir string - keybase keyring.Keybase + tmpDir string + migrator keyring.InfoImporter ) if viper.GetBool(flags.FlagDryRun) { - tmpDir, err = ioutil.TempDir("", "keybase-migrate-dryrun") + tmpDir, err = ioutil.TempDir("", "migrator-migrate-dryrun") if err != nil { return errors.Wrap(err, "failed to create temporary directory for dryrun migration") } defer os.RemoveAll(tmpDir) - keybase, err = keyring.NewKeyring(keyringServiceName, "test", tmpDir, buf) + migrator, err = keyring.NewInfoImporter(keyringServiceName, "test", tmpDir, buf) } else { - keybase, err = keyring.NewKeyring(keyringServiceName, viper.GetString(flags.FlagKeyringBackend), rootDir, buf) + migrator, err = keyring.NewInfoImporter(keyringServiceName, viper.GetString(flags.FlagKeyringBackend), rootDir, buf) } if err != nil { return errors.Wrap(err, fmt.Sprintf( @@ -92,16 +92,10 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error { keyName := key.GetName() keyType := key.GetType() - // skip key if already migrated - if _, err := keybase.Get(keyName); err == nil { - cmd.PrintErrf("Key '%s (%s)' already exists; skipping ...\n", key.GetName(), keyType) - continue - } - cmd.PrintErrf("Migrating key: '%s (%s)' ...\n", key.GetName(), keyType) // allow user to skip migrating specific keys - ok, err := input.GetConfirmation("Skip key migration?", buf) + ok, err := input.GetConfirmation("Skip key migration?", buf, cmd.ErrOrStderr()) if err != nil { return err } @@ -110,7 +104,7 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error { } if keyType != keyring.TypeLocal { - if err := keybase.Import(keyName, legKeyInfo); err != nil { + if err := migrator.Import(keyName, legKeyInfo); err != nil { return err } @@ -130,7 +124,7 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error { return err } - if err := keybase.ImportPrivKey(keyName, armoredPriv, migratePassphrase); err != nil { + if err := migrator.Import(keyName, armoredPriv); err != nil { return err } } diff --git a/client/keys/migrate_test.go b/client/keys/migrate_test.go index aec085ecc1d..9b82b18ba16 100644 --- a/client/keys/migrate_test.go +++ b/client/keys/migrate_test.go @@ -3,6 +3,8 @@ package keys import ( "testing" + "github.com/otiai10/copy" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/tests" @@ -18,18 +20,18 @@ func Test_runMigrateCmd(t *testing.T) { mockIn, _, _ := tests.ApplyMockIO(cmd) kbHome, kbCleanUp := tests.NewTestCaseDir(t) + copy.Copy("testdata", kbHome) assert.NotNil(t, kbHome) t.Cleanup(kbCleanUp) viper.Set(flags.FlagHome, kbHome) viper.Set(cli.OutputFlag, OutputFormatText) - mockIn.Reset("test1234\ntest1234\n") assert.NoError(t, runAddCmd(cmd, []string{"keyname1"})) viper.Set(flags.FlagDryRun, true) cmd = MigrateCommand() mockIn, _, _ = tests.ApplyMockIO(cmd) - mockIn.Reset("test1234\n") + mockIn.Reset("test1234\ntest1234\n") assert.NoError(t, runMigrateCmd(cmd, []string{})) } diff --git a/client/keys/mnemonic.go b/client/keys/mnemonic.go index e665130ee4c..66b3b17d027 100644 --- a/client/keys/mnemonic.go +++ b/client/keys/mnemonic.go @@ -46,7 +46,7 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error { if len(inputEntropy) < 43 { return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy)) } - conf, err := input.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf) + conf, err := input.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf, cmd.ErrOrStderr()) if err != nil { return err } diff --git a/client/keys/root_test.go b/client/keys/root_test.go index 29fbe145504..e995da9796d 100644 --- a/client/keys/root_test.go +++ b/client/keys/root_test.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" ) func TestCommands(t *testing.T) { @@ -21,5 +22,6 @@ func TestCommands(t *testing.T) { func TestMain(m *testing.M) { viper.Set(flags.FlagKeyringBackend, keyring.BackendTest) + viper.Set(flagCoinType, sdk.CoinType) os.Exit(m.Run()) } diff --git a/client/keys/show.go b/client/keys/show.go index c423caa1aae..11473aba407 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -57,7 +57,7 @@ consisting of all the keys provided by name and multisig threshold.`, func runShowCmd(cmd *cobra.Command, args []string) (err error) { var info keyring.Info - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin()) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin()) if err != nil { return err } @@ -142,15 +142,15 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { return nil } -func fetchKey(kb keyring.Keybase, keyref string) (keyring.Info, error) { - info, err := kb.Get(keyref) +func fetchKey(kb keyring.Keyring, keyref string) (keyring.Info, error) { + info, err := kb.Key(keyref) if err != nil { accAddr, err := sdk.AccAddressFromBech32(keyref) if err != nil { return info, err } - info, err = kb.GetByAddress(accAddr) + info, err = kb.KeyByAddress(accAddr) if err != nil { return info, errors.New("key not found") } diff --git a/client/keys/show_test.go b/client/keys/show_test.go index cf157c4c588..54746e0207b 100644 --- a/client/keys/show_test.go +++ b/client/keys/show_test.go @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" @@ -24,7 +25,7 @@ func Test_multiSigKey_Properties(t *testing.T) { require.Equal(t, "myMultisig", tmp.GetName()) require.Equal(t, keyring.TypeMulti, tmp.GetType()) require.Equal(t, "D3923267FA8A3DD367BB768FA8BDC8FF7F89DA3F", tmp.GetPubKey().Address().String()) - require.Equal(t, "cosmos16wfryel63g7axeamw68630wglalcnk3l0zuadc", tmp.GetAddress().String()) + require.Equal(t, "cosmos16wfryel63g7axeamw68630wglalcnk3l0zuadc", sdk.MustBech32ifyAddressBytes("cosmos", tmp.GetAddress())) } func Test_showKeysCmd(t *testing.T) { @@ -48,16 +49,19 @@ func Test_runShowCmd(t *testing.T) { fakeKeyName1 := "runShowCmd_Key1" fakeKeyName2 := "runShowCmd_Key2" - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) require.NoError(t, err) t.Cleanup(func() { - kb.Delete("runShowCmd_Key1", "", false) - kb.Delete("runShowCmd_Key2", "", false) + kb.Delete("runShowCmd_Key1") + kb.Delete("runShowCmd_Key2") }) - _, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keyring.Secp256k1) + + path := hd.NewFundraiserParams(1, sdk.CoinType, 0).String() + _, err = kb.NewAccount(fakeKeyName1, tests.TestMnemonic, "", path, hd.Secp256k1) require.NoError(t, err) - _, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keyring.Secp256k1) + path2 := hd.NewFundraiserParams(1, sdk.CoinType, 1).String() + _, err = kb.NewAccount(fakeKeyName2, tests.TestMnemonic, "", path2, hd.Secp256k1) require.NoError(t, err) // Now try single key @@ -69,7 +73,7 @@ func Test_runShowCmd(t *testing.T) { // try fetch by name require.NoError(t, runShowCmd(cmd, []string{fakeKeyName1})) // try fetch by addr - info, err := kb.Get(fakeKeyName1) + info, err := kb.Key(fakeKeyName1) require.NoError(t, err) require.NoError(t, runShowCmd(cmd, []string{info.GetAddress().String()})) diff --git a/client/tx/factory.go b/client/tx/factory.go index 4b05b2419a4..25cff13d672 100644 --- a/client/tx/factory.go +++ b/client/tx/factory.go @@ -21,7 +21,7 @@ type AccountRetriever interface { // Factory defines a client transaction factory that facilitates generating and // signing an application-specific transaction. type Factory struct { - keybase keyring.Keybase + keybase keyring.Keyring txGenerator Generator accountRetriever AccountRetriever accountNumber uint64 @@ -36,7 +36,7 @@ type Factory struct { } func NewFactoryFromCLI(input io.Reader) Factory { - kb, err := keyring.NewKeyring( + kb, err := keyring.New( sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), @@ -68,7 +68,7 @@ func (f Factory) AccountNumber() uint64 { return f.accountNumber } func (f Factory) Sequence() uint64 { return f.sequence } func (f Factory) Gas() uint64 { return f.gas } func (f Factory) GasAdjustment() float64 { return f.gasAdjustment } -func (f Factory) Keybase() keyring.Keybase { return f.keybase } +func (f Factory) Keybase() keyring.Keyring { return f.keybase } func (f Factory) ChainID() string { return f.chainID } func (f Factory) Memo() string { return f.memo } func (f Factory) Fees() sdk.Coins { return f.fees } @@ -126,7 +126,7 @@ func (f Factory) WithGasPrices(gasPrices string) Factory { } // WithKeybase returns a copy of the Factory with updated Keybase. -func (f Factory) WithKeybase(keybase keyring.Keybase) Factory { +func (f Factory) WithKeybase(keybase keyring.Keyring) Factory { f.keybase = keybase return f } diff --git a/client/tx/tx.go b/client/tx/tx.go index 7958122dad3..0cd4e71f3dd 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -127,7 +127,7 @@ func BroadcastTx(ctx context.CLIContext, txf Factory, msgs ...sdk.Msg) error { _, _ = fmt.Fprintf(os.Stderr, "%s\n\n", out) buf := bufio.NewReader(os.Stdin) - ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf) + ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr) if err != nil || !ok { _, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction") return err @@ -332,7 +332,7 @@ func Sign(txf Factory, name, passphrase string, tx ClientTx) ([]byte, error) { return nil, err } - sigBytes, pubkey, err := txf.keybase.Sign(name, passphrase, signBytes) + sigBytes, pubkey, err := txf.keybase.Sign(name, signBytes) if err != nil { return nil, err } diff --git a/crypto/armor_test.go b/crypto/armor_test.go index 29aeb6af64a..9ebb7cc8c81 100644 --- a/crypto/armor_test.go +++ b/crypto/armor_test.go @@ -15,8 +15,11 @@ import ( "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/xsalsa20symmetric" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/types" ) func TestArmorUnarmorPrivKey(t *testing.T) { @@ -26,7 +29,7 @@ func TestArmorUnarmorPrivKey(t *testing.T) { require.Error(t, err) decrypted, algo, err := crypto.UnarmorDecryptPrivKey(armored, "passphrase") require.NoError(t, err) - require.Equal(t, string(keyring.Secp256k1), algo) + require.Equal(t, string(hd.Secp256k1Type), algo) require.True(t, priv.Equals(decrypted)) // empty string @@ -70,14 +73,14 @@ func TestArmorUnarmorPubKey(t *testing.T) { cstore := keyring.NewInMemory() // Add keys and see they return in alphabetical order - info, _, err := cstore.CreateMnemonic("Bob", keyring.English, "passphrase", keyring.Secp256k1) + info, _, err := cstore.NewMnemonic("Bob", keyring.English, types.FullFundraiserPath, hd.Secp256k1) require.NoError(t, err) armored := crypto.ArmorPubKeyBytes(info.GetPubKey().Bytes(), "") pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armored) require.NoError(t, err) pub, err := cryptoAmino.PubKeyFromBytes(pubBytes) require.NoError(t, err) - require.Equal(t, string(keyring.Secp256k1), algo) + require.Equal(t, string(hd.Secp256k1Type), algo) require.True(t, pub.Equals(info.GetPubKey())) armored = crypto.ArmorPubKeyBytes(info.GetPubKey().Bytes(), "unknown") @@ -88,7 +91,7 @@ func TestArmorUnarmorPubKey(t *testing.T) { require.Equal(t, "unknown", algo) require.True(t, pub.Equals(info.GetPubKey())) - armored, err = cstore.ExportPrivKey("Bob", "passphrase", "alessio") + armored, err = cstore.ExportPrivKeyArmor("Bob", "passphrase") require.NoError(t, err) _, _, err = crypto.UnarmorPubKeyBytes(armored) require.Error(t, err) diff --git a/crypto/hd/algo.go b/crypto/hd/algo.go new file mode 100644 index 00000000000..264f0ae8dfe --- /dev/null +++ b/crypto/hd/algo.go @@ -0,0 +1,68 @@ +package hd + +import ( + "github.com/cosmos/go-bip39" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" +) + +// PubKeyType defines an algorithm to derive key-pairs which can be used for cryptographic signing. +type PubKeyType string + +const ( + // MultiType implies that a pubkey is a multisignature + MultiType = PubKeyType("multi") + // Secp256k1Type uses the Bitcoin secp256k1 ECDSA parameters. + Secp256k1Type = PubKeyType("secp256k1") + // Ed25519Type represents the Ed25519Type signature system. + // It is currently not supported for end-user keys (wallets/ledgers). + Ed25519Type = PubKeyType("ed25519") + // Sr25519Type represents the Sr25519Type signature system. + Sr25519Type = PubKeyType("sr25519") +) + +var ( + // Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters. + Secp256k1 = secp256k1Algo{} +) + +type DeriveFn func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) +type GenerateFn func(bz []byte) crypto.PrivKey + +type WalletGenerator interface { + Derive(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) + Generate(bz []byte) crypto.PrivKey +} + +type secp256k1Algo struct { +} + +func (s secp256k1Algo) Name() PubKeyType { + return Secp256k1Type +} + +// Derive derives and returns the secp256k1 private key for the given seed and HD path. +func (s secp256k1Algo) Derive() DeriveFn { + return func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) { + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) + if err != nil { + return nil, err + } + + masterPriv, ch := ComputeMastersFromSeed(seed) + if len(hdPath) == 0 { + return masterPriv[:], nil + } + derivedKey, err := DerivePrivateKeyForPath(masterPriv, ch, hdPath) + return derivedKey[:], err + } +} + +// Generate generates a secp256k1 private key from the given bytes. +func (s secp256k1Algo) Generate() GenerateFn { + return func(bz []byte) crypto.PrivKey { + var bzArr [32]byte + copy(bzArr[:], bz) + return secp256k1.PrivKeySecp256k1(bzArr) + } +} diff --git a/crypto/hd/algo_test.go b/crypto/hd/algo_test.go new file mode 100644 index 00000000000..767b4215403 --- /dev/null +++ b/crypto/hd/algo_test.go @@ -0,0 +1,16 @@ +package hd_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/hd" +) + +func TestDefaults(t *testing.T) { + require.Equal(t, hd.PubKeyType("multi"), hd.MultiType) + require.Equal(t, hd.PubKeyType("secp256k1"), hd.Secp256k1Type) + require.Equal(t, hd.PubKeyType("ed25519"), hd.Ed25519Type) + require.Equal(t, hd.PubKeyType("sr25519"), hd.Sr25519Type) +} diff --git a/crypto/hd/doc.go b/crypto/hd/doc.go new file mode 100644 index 00000000000..38d65213c17 --- /dev/null +++ b/crypto/hd/doc.go @@ -0,0 +1,12 @@ +// Package hd provides support for hierarchical deterministic wallets generation and derivation. +// +// The user must understand the overall concept of the BIP 32 and the BIP 44 specs: +// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +// +// In combination with the bip39 package in go-crypto this package provides the functionality for +// deriving keys using a BIP 44 HD path, or, more general, by passing a BIP 32 path. +// +// In particular, this package (together with bip39) provides all necessary functionality to derive +// keys from mnemonics generated during the cosmos fundraiser. +package hd diff --git a/crypto/keys/hd/fundraiser_test.go b/crypto/hd/fundraiser_test.go similarity index 78% rename from crypto/keys/hd/fundraiser_test.go rename to crypto/hd/fundraiser_test.go index 6fa4ca725f0..0f6a539d4fd 100644 --- a/crypto/keys/hd/fundraiser_test.go +++ b/crypto/hd/fundraiser_test.go @@ -1,4 +1,4 @@ -package hd +package hd_test import ( "encoding/hex" @@ -13,6 +13,8 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/secp256k1" + + "github.com/cosmos/cosmos-sdk/crypto/hd" ) type addrData struct { @@ -24,19 +26,23 @@ type addrData struct { Addr string } +func TestFullFundraiserPath(t *testing.T) { + require.Equal(t, "44'/118'/0'/0/0", hd.NewFundraiserParams(0, 118, 0).String()) +} + func initFundraiserTestVectors(t *testing.T) []addrData { // NOTE: atom fundraiser address // var hdPath string = "m/44'/118'/0'/0/0" var hdToAddrTable []addrData - b, err := ioutil.ReadFile("test.json") + b, err := ioutil.ReadFile("testdata/test.json") if err != nil { - t.Fatalf("could not read fundraiser test vector file (test.json): %s", err) + t.Fatalf("could not read fundraiser test vector file (testdata/test.json): %s", err) } err = json.Unmarshal(b, &hdToAddrTable) if err != nil { - t.Fatalf("could not decode test vectors (test.json): %s", err) + t.Fatalf("could not decode test vectors (testdata/test.json): %s", err) } return hdToAddrTable } @@ -56,8 +62,8 @@ func TestFundraiserCompatibility(t *testing.T) { t.Log("================================") t.Logf("ROUND: %d MNEMONIC: %s", i, d.Mnemonic) - master, ch := ComputeMastersFromSeed(seed) - priv, err := DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") + master, ch := hd.ComputeMastersFromSeed(seed) + priv, err := hd.DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") require.NoError(t, err) pub := secp256k1.PrivKeySecp256k1(priv).PubKey() diff --git a/crypto/keys/hd/hdpath.go b/crypto/hd/hdpath.go similarity index 91% rename from crypto/keys/hd/hdpath.go rename to crypto/hd/hdpath.go index a92d79be1f2..76326708748 100644 --- a/crypto/keys/hd/hdpath.go +++ b/crypto/hd/hdpath.go @@ -1,20 +1,8 @@ -// Package hd provides basic functionality Hierarchical Deterministic Wallets. -// -// The user must understand the overall concept of the BIP 32 and the BIP 44 specs: -// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki -// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki -// -// In combination with the bip39 package in go-crypto this package provides the functionality for deriving keys using a -// BIP 44 HD path, or, more general, by passing a BIP 32 path. -// -// In particular, this package (together with bip39) provides all necessary functionality to derive keys from -// mnemonics generated during the cosmos fundraiser. package hd import ( "crypto/hmac" "crypto/sha512" - "encoding/binary" "errors" "fmt" @@ -255,3 +243,8 @@ func i64(key []byte, data []byte) (il [32]byte, ir [32]byte) { return } + +// CreateHDPath returns BIP 44 object from account and index parameters. +func CreateHDPath(coinType, account, index uint32) *BIP44Params { + return NewFundraiserParams(account, coinType, index) +} diff --git a/crypto/keys/hd/hdpath_test.go b/crypto/hd/hdpath_test.go similarity index 64% rename from crypto/keys/hd/hdpath_test.go rename to crypto/hd/hdpath_test.go index a5110e2cbde..9f95f7edec3 100644 --- a/crypto/keys/hd/hdpath_test.go +++ b/crypto/hd/hdpath_test.go @@ -1,10 +1,11 @@ -package hd +package hd_test import ( "encoding/hex" "fmt" "testing" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/types" bip39 "github.com/cosmos/go-bip39" @@ -21,9 +22,9 @@ func mnemonicToSeed(mnemonic string) []byte { // nolint:govet func ExampleStringifyPathParams() { - path := NewParams(44, 0, 0, false, 0) + path := hd.NewParams(44, 0, 0, false, 0) fmt.Println(path.String()) - path = NewParams(44, 33, 7, true, 9) + path = hd.NewParams(44, 33, 7, true, 9) fmt.Println(path.String()) // Output: // 44'/0'/0'/0/0 @@ -31,40 +32,40 @@ func ExampleStringifyPathParams() { } func TestStringifyFundraiserPathParams(t *testing.T) { - path := NewFundraiserParams(4, types.CoinType, 22) + path := hd.NewFundraiserParams(4, types.CoinType, 22) require.Equal(t, "44'/118'/4'/0/22", path.String()) - path = NewFundraiserParams(4, types.CoinType, 57) + path = hd.NewFundraiserParams(4, types.CoinType, 57) require.Equal(t, "44'/118'/4'/0/57", path.String()) - path = NewFundraiserParams(4, 12345, 57) + path = hd.NewFundraiserParams(4, 12345, 57) require.Equal(t, "44'/12345'/4'/0/57", path.String()) } func TestPathToArray(t *testing.T) { - path := NewParams(44, 118, 1, false, 4) + path := hd.NewParams(44, 118, 1, false, 4) require.Equal(t, "[44 118 1 0 4]", fmt.Sprintf("%v", path.DerivationPath())) - path = NewParams(44, 118, 2, true, 15) + path = hd.NewParams(44, 118, 2, true, 15) require.Equal(t, "[44 118 2 1 15]", fmt.Sprintf("%v", path.DerivationPath())) } func TestParamsFromPath(t *testing.T) { goodCases := []struct { - params *BIP44Params + params *hd.BIP44Params path string }{ - {&BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"}, - {&BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"}, - {&BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"}, - {&BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"}, - {&BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"}, - {&BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"}, - {&BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"}, + {&hd.BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"}, + {&hd.BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"}, + {&hd.BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"}, + {&hd.BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"}, + {&hd.BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"}, + {&hd.BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"}, + {&hd.BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"}, } for i, c := range goodCases { - params, err := NewParamsFromPath(c.path) + params, err := hd.NewParamsFromPath(c.path) errStr := fmt.Sprintf("%d %v", i, c) assert.NoError(t, err, errStr) assert.EqualValues(t, c.params, params, errStr) @@ -93,7 +94,7 @@ func TestParamsFromPath(t *testing.T) { } for i, c := range badCases { - params, err := NewParamsFromPath(c.path) + params, err := hd.NewParamsFromPath(c.path) errStr := fmt.Sprintf("%d %v", i, c) assert.Nil(t, params, errStr) assert.Error(t, err, errStr) @@ -106,38 +107,38 @@ func ExampleSomeBIP32TestVecs() { seed := mnemonicToSeed("barrel original fuel morning among eternal " + "filter ball stove pluck matrix mechanic") - master, ch := ComputeMastersFromSeed(seed) + master, ch := hd.ComputeMastersFromSeed(seed) fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") fmt.Println() // cosmos - priv, err := DerivePrivateKeyForPath(master, ch, types.FullFundraiserPath) + priv, err := hd.DerivePrivateKeyForPath(master, ch, types.FullFundraiserPath) if err != nil { fmt.Println("INVALID") } else { fmt.Println(hex.EncodeToString(priv[:])) } // bitcoin - priv, err = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") + priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") if err != nil { fmt.Println("INVALID") } else { fmt.Println(hex.EncodeToString(priv[:])) } // ether - priv, err = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") + priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") if err != nil { fmt.Println("INVALID") } else { fmt.Println(hex.EncodeToString(priv[:])) } // INVALID - priv, err = DerivePrivateKeyForPath(master, ch, "X/0'/0'/0/0") + priv, err = hd.DerivePrivateKeyForPath(master, ch, "X/0'/0'/0/0") if err != nil { fmt.Println("INVALID") } else { fmt.Println(hex.EncodeToString(priv[:])) } - priv, err = DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0") + priv, err = hd.DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0") if err != nil { fmt.Println("INVALID") } else { @@ -151,14 +152,14 @@ func ExampleSomeBIP32TestVecs() { seed = mnemonicToSeed( "advice process birth april short trust crater change bacon monkey medal garment " + "gorilla ranch hour rival razor call lunar mention taste vacant woman sister") - master, ch = ComputeMastersFromSeed(seed) - priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") + master, ch = hd.ComputeMastersFromSeed(seed) + priv, _ = hd.DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") fmt.Println(hex.EncodeToString(priv[:])) seed = mnemonicToSeed("idea naive region square margin day captain habit " + "gun second farm pact pulse someone armed") - master, ch = ComputeMastersFromSeed(seed) - priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") + master, ch = hd.ComputeMastersFromSeed(seed) + priv, _ = hd.DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") fmt.Println(hex.EncodeToString(priv[:])) fmt.Println() @@ -167,8 +168,8 @@ func ExampleSomeBIP32TestVecs() { // bip32 path: m/0/7 seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") - master, ch = ComputeMastersFromSeed(seed) - priv, _ = DerivePrivateKeyForPath(master, ch, "0/7") + master, ch = hd.ComputeMastersFromSeed(seed) + priv, _ = hd.DerivePrivateKeyForPath(master, ch, "0/7") fmt.Println(hex.EncodeToString(priv[:])) // Output: keys from fundraiser test-vector (cosmos, bitcoin, ether) @@ -188,3 +189,27 @@ func ExampleSomeBIP32TestVecs() { // // c4c11d8c03625515905d7e89d25dfc66126fbc629ecca6db489a1a72fc4bda78 } + +func TestCreateHDPath(t *testing.T) { + type args struct { + coinType uint32 + account uint32 + index uint32 + } + tests := []struct { + name string + args args + want hd.BIP44Params + }{ + {"44'/0'/0'/0/0", args{0, 0, 0}, hd.BIP44Params{Purpose: 44}}, + {"44'/114'/0'/0/0", args{114, 0, 0}, hd.BIP44Params{Purpose: 44, CoinType: 114, Account: 0, AddressIndex: 0}}, + {"44'/114'/1'/1/0", args{114, 1, 1}, hd.BIP44Params{Purpose: 44, CoinType: 114, Account: 1, AddressIndex: 1}}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + tt := tt + require.Equal(t, tt.want, *hd.CreateHDPath(tt.args.coinType, tt.args.account, tt.args.index)) + }) + } +} diff --git a/crypto/keys/hd/test.json b/crypto/hd/testdata/test.json similarity index 100% rename from crypto/keys/hd/test.json rename to crypto/hd/testdata/test.json diff --git a/crypto/keyring/base_keybase.go b/crypto/keyring/base_keybase.go deleted file mode 100644 index 039b09e8ff1..00000000000 --- a/crypto/keyring/base_keybase.go +++ /dev/null @@ -1,234 +0,0 @@ -package keyring - -import ( - "github.com/pkg/errors" - - tmcrypto "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/secp256k1" - - "github.com/cosmos/cosmos-sdk/crypto" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" - "github.com/cosmos/cosmos-sdk/types" - bip39 "github.com/cosmos/go-bip39" -) - -type ( - // baseKeybase is an auxiliary type that groups Keybase storage agnostic features - // together. - baseKeybase struct { - options kbOptions - } - - keyWriter interface { - writeLocalKeyer - infoWriter - } - - writeLocalKeyer interface { - writeLocalKey(name string, priv tmcrypto.PrivKey, algo SigningAlgo) Info - } - - infoWriter interface { - writeInfo(name string, info Info) - } -) - -var fundraiserPath = types.GetConfig().GetFullFundraiserPath() - -// newBaseKeybase generates the base keybase with defaulting to tendermint SECP256K1 key type -func newBaseKeybase(optionsFns ...KeybaseOption) baseKeybase { - // Default options for keybase - options := kbOptions{ - keygenFunc: StdPrivKeyGen, - deriveFunc: StdDeriveKey, - supportedAlgos: []SigningAlgo{Secp256k1}, - supportedAlgosLedger: []SigningAlgo{Secp256k1}, - } - - for _, optionFn := range optionsFns { - optionFn(&options) - } - - return baseKeybase{options: options} -} - -// StdPrivKeyGen is the default PrivKeyGen function in the keybase. -// For now, it only supports Secp256k1 -func StdPrivKeyGen(bz []byte, algo SigningAlgo) (tmcrypto.PrivKey, error) { - if algo == Secp256k1 { - return SecpPrivKeyGen(bz), nil - } - return nil, ErrUnsupportedSigningAlgo -} - -// SecpPrivKeyGen generates a secp256k1 private key from the given bytes -func SecpPrivKeyGen(bz []byte) tmcrypto.PrivKey { - var bzArr [32]byte - copy(bzArr[:], bz) - return secp256k1.PrivKeySecp256k1(bzArr) -} - -// CreateAccount creates an account Info object. -func (kb baseKeybase) CreateAccount( - keyWriter keyWriter, name, mnemonic, bip39Passphrase, encryptPasswd, hdPath string, algo SigningAlgo, -) (Info, error) { - - // create master key and derive first key for keyring - derivedPriv, err := kb.options.deriveFunc(mnemonic, bip39Passphrase, hdPath, algo) - if err != nil { - return nil, err - } - - privKey, err := kb.options.keygenFunc(derivedPriv, algo) - if err != nil { - return nil, err - } - - var info Info - - if encryptPasswd != "" { - info = keyWriter.writeLocalKey(name, privKey, algo) - } else { - info = kb.writeOfflineKey(keyWriter, name, privKey.PubKey(), algo) - } - - return info, nil -} - -// CreateLedger creates a new reference to a Ledger key pair. It returns a public -// key and a derivation path. It returns an error if the device could not be queried. -func (kb baseKeybase) CreateLedger( - w infoWriter, name string, algo SigningAlgo, hrp string, account, index uint32, -) (Info, error) { - - if !IsSupportedAlgorithm(kb.SupportedAlgosLedger(), algo) { - return nil, ErrUnsupportedSigningAlgo - } - - coinType := types.GetConfig().GetCoinType() - hdPath := hd.NewFundraiserParams(account, coinType, index) - - priv, _, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath, hrp) - if err != nil { - return nil, err - } - - return kb.writeLedgerKey(w, name, priv.PubKey(), *hdPath, algo), nil -} - -// CreateMnemonic generates a new key with the given algorithm and language pair. -func (kb baseKeybase) CreateMnemonic( - keyWriter keyWriter, name string, language Language, passwd string, algo SigningAlgo, -) (info Info, mnemonic string, err error) { - - if language != English { - return nil, "", ErrUnsupportedLanguage - } - - if !IsSupportedAlgorithm(kb.SupportedAlgos(), algo) { - return nil, "", ErrUnsupportedSigningAlgo - } - - // Default number of words (24): This generates a mnemonic directly from the - // number of words by reading system entropy. - entropy, err := bip39.NewEntropy(defaultEntropySize) - if err != nil { - return nil, "", err - } - - mnemonic, err = bip39.NewMnemonic(entropy) - if err != nil { - return nil, "", err - } - - info, err = kb.CreateAccount(keyWriter, name, mnemonic, DefaultBIP39Passphrase, passwd, fundraiserPath, algo) - if err != nil { - return nil, "", err - } - - return info, mnemonic, err -} - -func (kb baseKeybase) writeLedgerKey(w infoWriter, name string, pub tmcrypto.PubKey, path hd.BIP44Params, algo SigningAlgo) Info { - info := newLedgerInfo(name, pub, path, algo) - w.writeInfo(name, info) - return info -} - -func (kb baseKeybase) writeOfflineKey(w infoWriter, name string, pub tmcrypto.PubKey, algo SigningAlgo) Info { - info := newOfflineInfo(name, pub, algo) - w.writeInfo(name, info) - return info -} - -func (kb baseKeybase) writeMultisigKey(w infoWriter, name string, pub tmcrypto.PubKey) Info { - info := NewMultiInfo(name, pub) - w.writeInfo(name, info) - return info -} - -// StdDeriveKey is the default DeriveKey function in the keybase. -// For now, it only supports Secp256k1 -func StdDeriveKey(mnemonic string, bip39Passphrase, hdPath string, algo SigningAlgo) ([]byte, error) { - if algo == Secp256k1 { - return SecpDeriveKey(mnemonic, bip39Passphrase, hdPath) - } - return nil, ErrUnsupportedSigningAlgo -} - -// SecpDeriveKey derives and returns the secp256k1 private key for the given seed and HD path. -func SecpDeriveKey(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) { - seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) - if err != nil { - return nil, err - } - - masterPriv, ch := hd.ComputeMastersFromSeed(seed) - if len(hdPath) == 0 { - return masterPriv[:], nil - } - derivedKey, err := hd.DerivePrivateKeyForPath(masterPriv, ch, hdPath) - return derivedKey[:], err -} - -// CreateHDPath returns BIP 44 object from account and index parameters. -func CreateHDPath(account uint32, index uint32) *hd.BIP44Params { - return hd.NewFundraiserParams(account, types.GetConfig().GetCoinType(), index) -} - -// SupportedAlgos returns a list of supported signing algorithms. -func (kb baseKeybase) SupportedAlgos() []SigningAlgo { - return kb.options.supportedAlgos -} - -// SupportedAlgosLedger returns a list of supported ledger signing algorithms. -func (kb baseKeybase) SupportedAlgosLedger() []SigningAlgo { - return kb.options.supportedAlgosLedger -} - -// SignWithLedger signs a binary message with the ledger device referenced by an Info object -// and returns the signed bytes and the public key. It returns an error if the device could -// not be queried or it returned an error. -func SignWithLedger(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { - switch info.(type) { - case *ledgerInfo, ledgerInfo: - default: - return nil, nil, errors.New("not a ledger object") - } - path, err := info.GetPath() - if err != nil { - return - } - - priv, err := crypto.NewPrivKeyLedgerSecp256k1Unsafe(*path) - if err != nil { - return - } - - sig, err = priv.Sign(msg) - if err != nil { - return nil, nil, err - } - - return sig, priv.PubKey(), nil -} diff --git a/crypto/keyring/codec.go b/crypto/keyring/codec.go index a8cd37d13fe..35860e71ce4 100644 --- a/crypto/keyring/codec.go +++ b/crypto/keyring/codec.go @@ -4,7 +4,7 @@ import ( cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/hd" ) // CryptoCdc defines the codec required for keys and info diff --git a/crypto/keyring/doc.go b/crypto/keyring/doc.go index 369f053b76a..0a76866e4b4 100644 --- a/crypto/keyring/doc.go +++ b/crypto/keyring/doc.go @@ -19,9 +19,9 @@ // generated keys are discarded when the process terminates or the type instance is garbage // collected. // -// NewKeyring +// New // -// The NewKeyring constructor returns an implementation backed by a keyring library +// The New constructor returns an implementation backed by a keyring library // (https://github.com/99designs/keyring), whose aim is to provide a common abstraction and uniform // interface between secret stores available for Windows, macOS, and most GNU/Linux distributions // as well as operating system-agnostic encrypted file-based backends. diff --git a/crypto/keyring/info.go b/crypto/keyring/info.go index b9df095a973..a391d86a943 100644 --- a/crypto/keyring/info.go +++ b/crypto/keyring/info.go @@ -6,7 +6,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/multisig" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/types" ) @@ -23,7 +23,7 @@ type Info interface { // Bip44 Path GetPath() (*hd.BIP44Params, error) // Algo - GetAlgo() SigningAlgo + GetAlgo() hd.PubKeyType } var ( @@ -39,10 +39,10 @@ type localInfo struct { Name string `json:"name"` PubKey crypto.PubKey `json:"pubkey"` PrivKeyArmor string `json:"privkey.armor"` - Algo SigningAlgo `json:"algo"` + Algo hd.PubKeyType `json:"algo"` } -func newLocalInfo(name string, pub crypto.PubKey, privArmor string, algo SigningAlgo) Info { +func newLocalInfo(name string, pub crypto.PubKey, privArmor string, algo hd.PubKeyType) Info { return &localInfo{ Name: name, PubKey: pub, @@ -72,7 +72,7 @@ func (i localInfo) GetAddress() types.AccAddress { } // GetType implements Info interface -func (i localInfo) GetAlgo() SigningAlgo { +func (i localInfo) GetAlgo() hd.PubKeyType { return i.Algo } @@ -87,10 +87,10 @@ type ledgerInfo struct { Name string `json:"name"` PubKey crypto.PubKey `json:"pubkey"` Path hd.BIP44Params `json:"path"` - Algo SigningAlgo `json:"algo"` + Algo hd.PubKeyType `json:"algo"` } -func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params, algo SigningAlgo) Info { +func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params, algo hd.PubKeyType) Info { return &ledgerInfo{ Name: name, PubKey: pub, @@ -120,7 +120,7 @@ func (i ledgerInfo) GetAddress() types.AccAddress { } // GetPath implements Info interface -func (i ledgerInfo) GetAlgo() SigningAlgo { +func (i ledgerInfo) GetAlgo() hd.PubKeyType { return i.Algo } @@ -135,10 +135,10 @@ func (i ledgerInfo) GetPath() (*hd.BIP44Params, error) { type offlineInfo struct { Name string `json:"name"` PubKey crypto.PubKey `json:"pubkey"` - Algo SigningAlgo `json:"algo"` + Algo hd.PubKeyType `json:"algo"` } -func newOfflineInfo(name string, pub crypto.PubKey, algo SigningAlgo) Info { +func newOfflineInfo(name string, pub crypto.PubKey, algo hd.PubKeyType) Info { return &offlineInfo{ Name: name, PubKey: pub, @@ -162,7 +162,7 @@ func (i offlineInfo) GetPubKey() crypto.PubKey { } // GetAlgo returns the signing algorithm for the key -func (i offlineInfo) GetAlgo() SigningAlgo { +func (i offlineInfo) GetAlgo() hd.PubKeyType { return i.Algo } @@ -228,8 +228,8 @@ func (i multiInfo) GetAddress() types.AccAddress { } // GetPath implements Info interface -func (i multiInfo) GetAlgo() SigningAlgo { - return MultiAlgo +func (i multiInfo) GetAlgo() hd.PubKeyType { + return hd.MultiType } // GetPath implements Info interface diff --git a/crypto/keyring/keybase.go b/crypto/keyring/keybase.go deleted file mode 100644 index 149069ff50b..00000000000 --- a/crypto/keyring/keybase.go +++ /dev/null @@ -1,76 +0,0 @@ -package keyring - -import ( - "github.com/tendermint/tendermint/crypto" - - "github.com/cosmos/cosmos-sdk/types" -) - -// Keybase exposes operations on a generic keystore -type Keybase interface { - // CRUD on the keystore - List() ([]Info, error) - // Get returns the public information about one key. - Get(name string) (Info, error) - // Get performs a by-address lookup and returns the public - // information about one key if there's any. - GetByAddress(address types.AccAddress) (Info, error) - // Delete removes a key. - Delete(name, passphrase string, skipPass bool) error - // Sign bytes, looking up the private key to use. - Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) - - // CreateMnemonic generates a new mnemonic, derives a hierarchical deterministic - // key from that. and persists it to storage, encrypted using the provided password. - // It returns the generated mnemonic and the key Info. It returns an error if it fails to - // generate a key for the given algo type, or if another key is already stored under the - // same name. - CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) - - // CreateAccount converts a mnemonic to a private key and BIP 32 HD Path - // and persists it, encrypted with the given password. - CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo) (Info, error) - - // CreateLedger creates, stores, and returns a new Ledger key reference - CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) - - // CreateOffline creates, stores, and returns a new offline key reference - CreateOffline(name string, pubkey crypto.PubKey, algo SigningAlgo) (info Info, err error) - - // CreateMulti creates, stores, and returns a new multsig (offline) key reference - CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) - - // Import imports ASCII armored Info objects. - Import(name string, armor string) (err error) - - // ImportPrivKey imports a private key in ASCII armor format. - // It returns an error if a key with the same name exists or a wrong encryption passphrase is - // supplied. - ImportPrivKey(name, armor, passphrase string) error - - // ImportPubKey imports ASCII-armored public keys. - // Store a new Info object holding a public key only, i.e. it will - // not be possible to sign with it as it lacks the secret key. - ImportPubKey(name string, armor string) (err error) - - // Export exports an Info object in ASCII armored format. - Export(name string) (armor string, err error) - - // ExportPubKey returns public keys in ASCII armored format. - // Retrieve a Info object by its name and return the public key in - // a portable format. - ExportPubKey(name string) (armor string, err error) - - // ExportPrivKey returns a private key in ASCII armored format. - // It returns an error if the key does not exist or a wrong encryption passphrase is supplied. - ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) - - // ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API - ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) - - // SupportedAlgos returns a list of signing algorithms supported by the keybase - SupportedAlgos() []SigningAlgo - - // SupportedAlgosLedger returns a list of signing algorithms supported by the keybase's ledger integration - SupportedAlgosLedger() []SigningAlgo -} diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index 3f77ce4bb30..de834428792 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -12,15 +12,16 @@ import ( "strings" "github.com/99designs/keyring" + "github.com/cosmos/go-bip39" "github.com/pkg/errors" - "github.com/tendermint/crypto/bcrypt" tmcrypto "github.com/tendermint/tendermint/crypto" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/cosmos/cosmos-sdk/client/input" "github.com/cosmos/cosmos-sdk/crypto" - "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/crypto/hd" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -34,48 +35,116 @@ const ( ) const ( - keyringDirNameFmt = "keyring-%s" - testKeyringDirNameFmt = "keyring-test-%s" - passKeyringPrefix = keyringDirNameFmt + keyringFileDirName = "keyring-file" + keyringTestDirName = "keyring-test" + passKeyringPrefix = "keyring-%s" +) + +var ( + _ Keyring = &keystore{} + maxPassphraseEntryAttempts = 3 ) -var _ Keybase = keyringKeybase{} +// Keyring exposes operations over a backend supported by github.com/99designs/keyring. +type Keyring interface { + // List all keys. + List() ([]Info, error) + + // Key and KeyByAddress return keys by uid and address respectively. + Key(uid string) (Info, error) + KeyByAddress(address sdk.Address) (Info, error) + + // Delete and DeleteByAddress remove keys from the keyring. + Delete(uid string) error + DeleteByAddress(address sdk.Address) error + + // NewMnemonic generates a new mnemonic, derives a hierarchical deterministic + // key from that, and persists it to the storage. Returns the generated mnemonic and the key + // Info. It returns an error if it fails to generate a key for the given algo type, or if + // another key is already stored under the same name. + NewMnemonic(uid string, language Language, hdPath string, algo SignatureAlgo) (Info, string, error) + + // NewAccount converts a mnemonic to a private key and BIP-39 HD Path and persists it. + NewAccount(uid, mnemonic, bip39Passwd, hdPath string, algo SignatureAlgo) (Info, error) + + // SaveLedgerKey retrieves a public key reference from a Ledger device and persists it. + SaveLedgerKey(uid string, algo SignatureAlgo, hrp string, coinType, account, index uint32) (Info, error) + + // SavePubKey stores a public key and returns the persisted Info structure. + SavePubKey(uid string, pubkey tmcrypto.PubKey, algo hd.PubKeyType) (Info, error) + + // SaveMultisig stores and returns a new multsig (offline) key reference. + SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error) -// keyringKeybase implements the Keybase interface by using the Keyring library -// for account key persistence. -type keyringKeybase struct { - base baseKeybase - db keyring.Keyring + Signer + + Importer + Exporter } -var maxPassphraseEntryAttempts = 3 +// Signer is implemented by key stores that want to provide signing capabilities. +type Signer interface { + // Sign sign byte messages with a user key. + Sign(uid string, msg []byte) ([]byte, tmcrypto.PubKey, error) -func newKeyringKeybase(db keyring.Keyring, opts ...KeybaseOption) Keybase { - return keyringKeybase{ - db: db, - base: newBaseKeybase(opts...), - } + // SignByAddress sign byte messages with a user key providing the address. + SignByAddress(address sdk.Address, msg []byte) ([]byte, tmcrypto.PubKey, error) +} + +// Importer is implemented by key stores that support import of public and private keys. +type Importer interface { + // ImportPrivKey imports ASCII armored passphrase-encrypted private keys. + ImportPrivKey(uid, armor, passphrase string) error + // ImportPubKey imports ASCII armored public keys. + ImportPubKey(uid string, armor string) error +} + +// Exporter is implemented by key stores that support export of public and private keys. +type Exporter interface { + // Export public key + ExportPubKeyArmor(uid string) (string, error) + ExportPubKeyArmorByAddress(address sdk.Address) (string, error) + // ExportPrivKey returns a private key in ASCII armored format. + // It returns an error if the key does not exist or a wrong encryption passphrase is supplied. + ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error) + ExportPrivKeyArmorByAddress(address sdk.Address, encryptPassphrase string) (armor string, err error) +} + +// Option overrides keyring configuration options. +type Option func(options *Options) + +//Options define the options of the Keyring +type Options struct { + SupportedAlgos SigningAlgoList + SupportedAlgosLedger SigningAlgoList } -// NewKeyring creates a new instance of a keyring. Keybase -// options can be applied when generating this new Keybase. -// Available backends are "os", "file", "kwallet", "pass", "test". -func NewKeyring( - appName, backend, rootDir string, userInput io.Reader, opts ...KeybaseOption, -) (Keybase, error) { +// NewInMemory creates a transient keyring useful for testing +// purposes and on-the-fly key generation. +// Keybase options can be applied when generating this new Keybase. +func NewInMemory(opts ...Option) Keyring { + return newKeystore(keyring.NewArrayKeyring(nil), opts...) +} + +// NewKeyring creates a new instance of a keyring. +// Keyring ptions can be applied when generating the new instance. +// Available backends are "os", "file", "kwallet", "memory", "pass", "test". +func New( + appName, backend, rootDir string, userInput io.Reader, opts ...Option, +) (Keyring, error) { var db keyring.Keyring var err error switch backend { case BackendMemory: - return NewInMemory(opts...), nil + return NewInMemory(opts...), err case BackendTest: - db, err = keyring.Open(lkbToKeyringConfig(appName, rootDir, nil, true)) + db, err = keyring.Open(newTestBackendKeyringConfig(appName, rootDir)) case BackendFile: db, err = keyring.Open(newFileBackendKeyringConfig(appName, rootDir, userInput)) case BackendOS: - db, err = keyring.Open(lkbToKeyringConfig(appName, rootDir, userInput, false)) + db, err = keyring.Open(newOSBackendKeyringConfig(appName, rootDir, userInput)) case BackendKWallet: db, err = keyring.Open(newKWalletBackendKeyringConfig(appName, rootDir, userInput)) case BackendPass: @@ -83,135 +152,151 @@ func NewKeyring( default: return nil, fmt.Errorf("unknown keyring backend %v", backend) } + if err != nil { return nil, err } - return newKeyringKeybase(db, opts...), nil + return newKeystore(db, opts...), nil } -// NewInMemory creates a transient keyring useful for testing -// purposes and on-the-fly key generation. -// Keybase options can be applied when generating this new Keybase. -func NewInMemory(opts ...KeybaseOption) Keybase { - return newKeyringKeybase(keyring.NewArrayKeyring(nil), opts...) +type keystore struct { + db keyring.Keyring + options Options } -// CreateMnemonic generates a new key and persists it to storage, encrypted -// using the provided password. It returns the generated mnemonic and the key Info. -// An error is returned if it fails to generate a key for the given algo type, -// or if another key is already stored under the same name. -func (kb keyringKeybase) CreateMnemonic( - name string, language Language, passwd string, algo SigningAlgo, -) (info Info, mnemonic string, err error) { +func newKeystore(kr keyring.Keyring, opts ...Option) keystore { + // Default options for keybase + options := Options{ + SupportedAlgos: SigningAlgoList{hd.Secp256k1}, + SupportedAlgosLedger: SigningAlgoList{hd.Secp256k1}, + } - return kb.base.CreateMnemonic(kb, name, language, passwd, algo) + for _, optionFn := range opts { + optionFn(&options) + } + + return keystore{kr, options} } -// CreateAccount converts a mnemonic to a private key and persists it, encrypted -// with the given password. -func (kb keyringKeybase) CreateAccount( - name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo, -) (Info, error) { +func (ks keystore) ExportPubKeyArmor(uid string) (string, error) { + bz, err := ks.Key(uid) + if err != nil { + return "", err + } - return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo) + if bz == nil { + return "", fmt.Errorf("no key to export with name: %s", uid) + } + + return crypto.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil } -// CreateLedger creates a new locally-stored reference to a Ledger keypair. -// It returns the created key info and an error if the Ledger could not be queried. -func (kb keyringKeybase) CreateLedger( - name string, algo SigningAlgo, hrp string, account, index uint32, -) (Info, error) { +func (ks keystore) ExportPubKeyArmorByAddress(address sdk.Address) (string, error) { + info, err := ks.KeyByAddress(address) + if err != nil { + return "", err + } - return kb.base.CreateLedger(kb, name, algo, hrp, account, index) + return ks.ExportPubKeyArmor(info.GetName()) } -// CreateOffline creates a new reference to an offline keypair. It returns the -// created key info. -func (kb keyringKeybase) CreateOffline(name string, pub tmcrypto.PubKey, algo SigningAlgo) (Info, error) { - return kb.base.writeOfflineKey(kb, name, pub, algo), nil -} +func (ks keystore) ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error) { + priv, err := ks.ExportPrivateKeyObject(uid) + if err != nil { + return "", err + } -// CreateMulti creates a new reference to a multisig (offline) keypair. It -// returns the created key Info object. -func (kb keyringKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) { - return kb.base.writeMultisigKey(kb, name, pub), nil + info, err := ks.Key(uid) + if err != nil { + return "", err + } + + return crypto.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil } -// List returns the keys from storage in alphabetical order. -func (kb keyringKeybase) List() ([]Info, error) { - var res []Info - keys, err := kb.db.Keys() +// ExportPrivateKeyObject exports an armored private key object. +func (ks keystore) ExportPrivateKeyObject(uid string) (tmcrypto.PrivKey, error) { + info, err := ks.Key(uid) if err != nil { return nil, err } - sort.Strings(keys) + var priv tmcrypto.PrivKey - for _, key := range keys { - if strings.HasSuffix(key, infoSuffix) { - rawInfo, err := kb.db.Get(key) - if err != nil { - return nil, err - } + switch linfo := info.(type) { + case localInfo: + if linfo.PrivKeyArmor == "" { + err = fmt.Errorf("private key not available") + return nil, err + } - if len(rawInfo.Data) == 0 { - return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, key) - } + priv, err = cryptoAmino.PrivKeyFromBytes([]byte(linfo.PrivKeyArmor)) + if err != nil { + return nil, err + } - info, err := unmarshalInfo(rawInfo.Data) - if err != nil { - return nil, err - } + case ledgerInfo, offlineInfo, multiInfo: + return nil, errors.New("only works on local private keys") + } - res = append(res, info) - } + return priv, nil +} + +func (ks keystore) ExportPrivKeyArmorByAddress(address sdk.Address, encryptPassphrase string) (armor string, err error) { + byAddress, err := ks.KeyByAddress(address) + if err != nil { + return "", err } - return res, nil + return ks.ExportPrivKeyArmor(byAddress.GetName(), encryptPassphrase) } -// Get returns the public information about one key. -func (kb keyringKeybase) Get(name string) (Info, error) { - key := infoKey(name) +func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error { + if _, err := ks.Key(uid); err == nil { + return fmt.Errorf("cannot overwrite key: %s", uid) + } - bs, err := kb.db.Get(string(key)) + privKey, algo, err := crypto.UnarmorDecryptPrivKey(armor, passphrase) if err != nil { - return nil, err + return errors.Wrap(err, "failed to decrypt private key") } - if len(bs.Data) == 0 { - return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, name) + _, err = ks.writeLocalKey(uid, privKey, hd.PubKeyType(algo)) + if err != nil { + return err } - return unmarshalInfo(bs.Data) + return nil } -// GetByAddress fetches a key by address and returns its public information. -func (kb keyringKeybase) GetByAddress(address types.AccAddress) (Info, error) { - ik, err := kb.db.Get(string(addrHexKey(address))) +func (ks keystore) ImportPubKey(uid string, armor string) error { + if _, err := ks.Key(uid); err == nil { + return fmt.Errorf("cannot overwrite key: %s", uid) + } + + pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armor) if err != nil { - return nil, err + return err } - if len(ik.Data) == 0 { - return nil, fmt.Errorf("key with address %s not found", address) + pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes) + if err != nil { + return err } - bs, err := kb.db.Get(string(ik.Data)) + _, err = ks.writeOfflineKey(uid, pubKey, hd.PubKeyType(algo)) if err != nil { - return nil, err + return err } - return unmarshalInfo(bs.Data) + return nil } -// Sign signs an arbitrary set of bytes with the named key. It returns an error -// if the key doesn't exist or the decryption fails. -func (kb keyringKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { - info, err := kb.Get(name) +func (ks keystore) Sign(uid string, msg []byte) ([]byte, tmcrypto.PubKey, error) { + info, err := ks.Key(uid) if err != nil { - return + return nil, nil, err } var priv tmcrypto.PrivKey @@ -234,7 +319,7 @@ func (kb keyringKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, return nil, info.GetPubKey(), errors.New("cannot sign with offline keys") } - sig, err = priv.Sign(msg) + sig, err := priv.Sign(msg) if err != nil { return nil, nil, err } @@ -242,91 +327,74 @@ func (kb keyringKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, return sig, priv.PubKey(), nil } -// ExportPrivateKeyObject exports an armored private key object. -func (kb keyringKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) { - info, err := kb.Get(name) +func (ks keystore) SignByAddress(address sdk.Address, msg []byte) ([]byte, tmcrypto.PubKey, error) { + key, err := ks.KeyByAddress(address) if err != nil { - return nil, err + return nil, nil, err } - var priv tmcrypto.PrivKey + return ks.Sign(key.GetName(), msg) +} - switch linfo := info.(type) { - case localInfo: - if linfo.PrivKeyArmor == "" { - err = fmt.Errorf("private key not available") - return nil, err - } +func (ks keystore) SaveLedgerKey(uid string, algo SignatureAlgo, hrp string, coinType, account, index uint32) (Info, error) { + if !ks.options.SupportedAlgosLedger.Contains(algo) { + return nil, ErrUnsupportedSigningAlgo + } - priv, err = cryptoAmino.PrivKeyFromBytes([]byte(linfo.PrivKeyArmor)) - if err != nil { - return nil, err - } + hdPath := hd.NewFundraiserParams(account, coinType, index) - case ledgerInfo, offlineInfo, multiInfo: - return nil, errors.New("only works on local private keys") + priv, _, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath, hrp) + if err != nil { + return nil, err } - return priv, nil + return ks.writeLedgerKey(uid, priv.PubKey(), *hdPath, algo.Name()) } -// Export exports armored private key to the caller. -func (kb keyringKeybase) Export(name string) (armor string, err error) { - bz, err := kb.db.Get(string(infoKey(name))) +func (ks keystore) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params, algo hd.PubKeyType) (Info, error) { + info := newLedgerInfo(name, pub, path, algo) + err := ks.writeInfo(info) if err != nil { - return "", err + return nil, err } - if bz.Data == nil { - return "", fmt.Errorf("no key to export with name: %s", name) - } + return info, nil +} + +func (ks keystore) SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error) { + return ks.writeMultisigKey(uid, pubkey) +} - return crypto.ArmorInfoBytes(bz.Data), nil +func (ks keystore) SavePubKey(uid string, pubkey tmcrypto.PubKey, algo hd.PubKeyType) (Info, error) { + return ks.writeOfflineKey(uid, pubkey, algo) } -// ExportPubKey returns public keys in ASCII armored format. It retrieves an Info -// object by its name and return the public key in a portable format. -func (kb keyringKeybase) ExportPubKey(name string) (armor string, err error) { - bz, err := kb.Get(name) +func (ks keystore) DeleteByAddress(address sdk.Address) error { + info, err := ks.KeyByAddress(address) if err != nil { - return "", err + return err } - if bz == nil { - return "", fmt.Errorf("no key to export with name: %s", name) + err = ks.Delete(info.GetName()) + if err != nil { + return err } - return crypto.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil + return nil } -// Import imports armored private key. -func (kb keyringKeybase) Import(name string, armor string) error { - bz, _ := kb.Get(name) - - if bz != nil { - pubkey := bz.GetPubKey() - - if len(pubkey.Bytes()) > 0 { - return fmt.Errorf("cannot overwrite data for name: %s", name) - } - } - - infoBytes, err := crypto.UnarmorInfoBytes(armor) +func (ks keystore) Delete(uid string) error { + info, err := ks.Key(uid) if err != nil { return err } - info, err := unmarshalInfo(infoBytes) + err = ks.db.Remove(addrHexKeyAsString(info.GetAddress())) if err != nil { return err } - kb.writeInfo(name, info) - - err = kb.db.Set(keyring.Item{ - Key: string(addrHexKey(info.GetAddress())), - Data: infoKey(name), - }) + err = ks.db.Remove(string(infoKey(uid))) if err != nil { return err } @@ -334,150 +402,148 @@ func (kb keyringKeybase) Import(name string, armor string) error { return nil } -// ExportPrivKey returns a private key in ASCII armored format. An error is returned -// if the key does not exist or a wrong encryption passphrase is supplied. -func (kb keyringKeybase) ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) { - priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase) +func (ks keystore) KeyByAddress(address sdk.Address) (Info, error) { + ik, err := ks.db.Get(addrHexKeyAsString(address)) if err != nil { - return "", err + return nil, err } - info, err := kb.Get(name) + if len(ik.Data) == 0 { + return nil, fmt.Errorf("key with address %s not found", address) + } + + bs, err := ks.db.Get(string(ik.Data)) if err != nil { - return "", err + return nil, err } - return crypto.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil + return unmarshalInfo(bs.Data) } -// ImportPrivKey imports a private key in ASCII armor format. An error is returned -// if a key with the same name exists or a wrong encryption passphrase is -// supplied. -func (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error { - if kb.HasKey(name) { - return fmt.Errorf("cannot overwrite key: %s", name) +func (ks keystore) List() ([]Info, error) { + var res []Info + keys, err := ks.db.Keys() + if err != nil { + return nil, err } - privKey, algo, err := crypto.UnarmorDecryptPrivKey(armor, passphrase) - if err != nil { - return errors.Wrap(err, "failed to decrypt private key") + sort.Strings(keys) + + for _, key := range keys { + if strings.HasSuffix(key, infoSuffix) { + rawInfo, err := ks.db.Get(key) + if err != nil { + return nil, err + } + + if len(rawInfo.Data) == 0 { + return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, key) + } + + info, err := unmarshalInfo(rawInfo.Data) + if err != nil { + return nil, err + } + + res = append(res, info) + } } - // NOTE: The keyring keystore has no need for a passphrase. - kb.writeLocalKey(name, privKey, SigningAlgo(algo)) - return nil + return res, nil } -// HasKey returns whether the key exists in the keyring. -func (kb keyringKeybase) HasKey(name string) bool { - bz, _ := kb.Get(name) - return bz != nil -} +func (ks keystore) NewMnemonic(uid string, language Language, hdPath string, algo SignatureAlgo) (Info, string, error) { + if language != English { + return nil, "", ErrUnsupportedLanguage + } -// ImportPubKey imports an ASCII-armored public key. It will store a new Info -// object holding a public key only, i.e. it will not be possible to sign with -// it as it lacks the secret key. -func (kb keyringKeybase) ImportPubKey(name string, armor string) error { - bz, _ := kb.Get(name) - if bz != nil { - pubkey := bz.GetPubKey() + if !ks.isSupportedSigningAlgo(algo) { + return nil, "", ErrUnsupportedSigningAlgo + } - if len(pubkey.Bytes()) > 0 { - return fmt.Errorf("cannot overwrite data for name: %s", name) - } + // Default number of words (24): This generates a mnemonic directly from the + // number of words by reading system entropy. + entropy, err := bip39.NewEntropy(defaultEntropySize) + if err != nil { + return nil, "", err } - pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armor) + mnemonic, err := bip39.NewMnemonic(entropy) if err != nil { - return err + return nil, "", err } - pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes) + info, err := ks.NewAccount(uid, mnemonic, DefaultBIP39Passphrase, hdPath, algo) if err != nil { - return err + return nil, "", err } - kb.base.writeOfflineKey(kb, name, pubKey, SigningAlgo(algo)) - return nil + return info, mnemonic, err } -// Delete removes key forever, but we must present the proper passphrase before -// deleting it (for security). It returns an error if the key doesn't exist or -// passphrases don't match. The passphrase is ignored when deleting references to -// offline and Ledger / HW wallet keys. -func (kb keyringKeybase) Delete(name, _ string, _ bool) error { - // verify we have the proper password before deleting - info, err := kb.Get(name) - if err != nil { - return err +func (ks keystore) NewAccount(uid string, mnemonic string, bip39Passphrase string, hdPath string, algo SignatureAlgo) (Info, error) { + if !ks.isSupportedSigningAlgo(algo) { + return nil, ErrUnsupportedSigningAlgo } - err = kb.db.Remove(string(addrHexKey(info.GetAddress()))) + // create master key and derive first key for keyring + derivedPriv, err := algo.Derive()(mnemonic, bip39Passphrase, hdPath) if err != nil { - return err + return nil, err } - err = kb.db.Remove(string(infoKey(name))) - if err != nil { - return err - } + privKey := algo.Generate()(derivedPriv) - return nil + return ks.writeLocalKey(uid, privKey, algo.Name()) } -// SupportedAlgos returns a list of supported signing algorithms. -func (kb keyringKeybase) SupportedAlgos() []SigningAlgo { - return kb.base.SupportedAlgos() +func (ks keystore) isSupportedSigningAlgo(algo SignatureAlgo) bool { + return ks.options.SupportedAlgos.Contains(algo) } -// SupportedAlgosLedger returns a list of supported ledger signing algorithms. -func (kb keyringKeybase) SupportedAlgosLedger() []SigningAlgo { - return kb.base.SupportedAlgosLedger() -} +func (ks keystore) Key(uid string) (Info, error) { + key := infoKey(uid) -func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, algo SigningAlgo) Info { - // encrypt private key using keyring - pub := priv.PubKey() - info := newLocalInfo(name, pub, string(priv.Bytes()), algo) + bs, err := ks.db.Get(string(key)) + if err != nil { + return nil, err + } - kb.writeInfo(name, info) - return info -} + if len(bs.Data) == 0 { + return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, uid) + } -func (kb keyringKeybase) writeInfo(name string, info Info) { - // write the info by key - key := infoKey(name) - serializedInfo := marshalInfo(info) + return unmarshalInfo(bs.Data) +} - err := kb.db.Set(keyring.Item{ - Key: string(key), - Data: serializedInfo, - }) +// SignWithLedger signs a binary message with the ledger device referenced by an Info object +// and returns the signed bytes and the public key. It returns an error if the device could +// not be queried or it returned an error. +func SignWithLedger(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { + switch info.(type) { + case *ledgerInfo, ledgerInfo: + default: + return nil, nil, errors.New("not a ledger object") + } + path, err := info.GetPath() if err != nil { - panic(err) + return } - err = kb.db.Set(keyring.Item{ - Key: string(addrHexKey(info.GetAddress())), - Data: key, - }) + priv, err := crypto.NewPrivKeyLedgerSecp256k1Unsafe(*path) if err != nil { - panic(err) + return } -} -func lkbToKeyringConfig(appName, dir string, buf io.Reader, test bool) keyring.Config { - if test { - return keyring.Config{ - AllowedBackends: []keyring.BackendType{keyring.FileBackend}, - ServiceName: appName, - FileDir: filepath.Join(dir, fmt.Sprintf(testKeyringDirNameFmt, appName)), - FilePasswordFunc: func(_ string) (string, error) { - return "test", nil - }, - } + sig, err = priv.Sign(msg) + if err != nil { + return nil, nil, err } + return sig, priv.PubKey(), nil +} + +func newOSBackendKeyringConfig(appName, dir string, buf io.Reader) keyring.Config { return keyring.Config{ ServiceName: appName, FileDir: dir, @@ -485,6 +551,17 @@ func lkbToKeyringConfig(appName, dir string, buf io.Reader, test bool) keyring.C } } +func newTestBackendKeyringConfig(appName, dir string) keyring.Config { + return keyring.Config{ + AllowedBackends: []keyring.BackendType{keyring.FileBackend}, + ServiceName: appName, + FileDir: filepath.Join(dir, keyringTestDirName), + FilePasswordFunc: func(_ string) (string, error) { + return "test", nil + }, + } +} + func newKWalletBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Config { return keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.KWalletBackend}, @@ -494,7 +571,7 @@ func newKWalletBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Conf } } -func newPassBackendKeyringConfig(appName, dir string, _ io.Reader) keyring.Config { +func newPassBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Config { prefix := fmt.Sprintf(passKeyringPrefix, appName) return keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.PassBackend}, @@ -504,7 +581,7 @@ func newPassBackendKeyringConfig(appName, dir string, _ io.Reader) keyring.Confi } func newFileBackendKeyringConfig(name, dir string, buf io.Reader) keyring.Config { - fileDir := filepath.Join(dir, fmt.Sprintf(keyringDirNameFmt, name)) + fileDir := filepath.Join(dir, keyringFileDirName) return keyring.Config{ AllowedBackends: []keyring.BackendType{keyring.FileBackend}, ServiceName: name, @@ -586,6 +663,88 @@ func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) { } } -func addrHexKey(address types.AccAddress) []byte { - return []byte(fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix)) +func (ks keystore) writeLocalKey(name string, priv tmcrypto.PrivKey, algo hd.PubKeyType) (Info, error) { + // encrypt private key using keyring + pub := priv.PubKey() + + info := newLocalInfo(name, pub, string(priv.Bytes()), algo) + err := ks.writeInfo(info) + if err != nil { + return nil, err + } + + return info, nil +} + +func (ks keystore) writeInfo(info Info) error { + // write the info by key + key := infoKey(info.GetName()) + serializedInfo := marshalInfo(info) + + exists, err := ks.existsInDb(info) + if exists { + return fmt.Errorf("public key already exist in keybase") + } + if err != nil { + return err + } + + err = ks.db.Set(keyring.Item{ + Key: string(key), + Data: serializedInfo, + }) + if err != nil { + return err + } + + err = ks.db.Set(keyring.Item{ + Key: addrHexKeyAsString(info.GetAddress()), + Data: key, + }) + if err != nil { + return err + } + + return nil +} + +func (ks keystore) existsInDb(info Info) (bool, error) { + if _, err := ks.db.Get(addrHexKeyAsString(info.GetAddress())); err == nil { + return true, nil // address lookup succeeds - info exists + } else if err != keyring.ErrKeyNotFound { + return false, err // received unexpected error - returns error + } + + if _, err := ks.db.Get(string(infoKey(info.GetName()))); err == nil { + return true, nil // uid lookup succeeds - info exists + } else if err != keyring.ErrKeyNotFound { + return false, err // received unexpected error - returns + } + + // both lookups failed, info does not exist + return false, nil +} + +func (ks keystore) writeOfflineKey(name string, pub tmcrypto.PubKey, algo hd.PubKeyType) (Info, error) { + info := newOfflineInfo(name, pub, algo) + err := ks.writeInfo(info) + if err != nil { + return nil, err + } + + return info, nil +} + +func (ks keystore) writeMultisigKey(name string, pub tmcrypto.PubKey) (Info, error) { + info := NewMultiInfo(name, pub) + err := ks.writeInfo(info) + if err != nil { + return nil, err + } + + return info, nil +} + +func addrHexKeyAsString(address sdk.Address) string { + return fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix) } diff --git a/crypto/keyring/keyring_ledger_test.go b/crypto/keyring/keyring_ledger_test.go new file mode 100644 index 00000000000..7c4bc37b7f1 --- /dev/null +++ b/crypto/keyring/keyring_ledger_test.go @@ -0,0 +1,128 @@ +//+build ledger test_ledger_mock + +package keyring + +import ( + "bytes" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestInMemoryCreateLedger(t *testing.T) { + kb := NewInMemory() + + ledger, err := kb.SaveLedgerKey("some_account", hd.Secp256k1, "cosmos", 118, 3, 1) + + if err != nil { + require.Error(t, err) + require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error()) + require.Nil(t, ledger) + t.Skip("ledger nano S: support for ledger devices is not available in this executable") + return + } + + // The mock is available, check that the address is correct + pubKey := ledger.GetPubKey() + pk, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey) + require.NoError(t, err) + require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk) + + // Check that restoring the key gets the same results + restoredKey, err := kb.Key("some_account") + require.NoError(t, err) + require.NotNil(t, restoredKey) + require.Equal(t, "some_account", restoredKey.GetName()) + require.Equal(t, TypeLedger, restoredKey.GetType()) + pubKey = restoredKey.GetPubKey() + pk, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey) + require.NoError(t, err) + require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk) + + path, err := restoredKey.GetPath() + require.NoError(t, err) + require.Equal(t, "44'/118'/3'/0/1", path.String()) +} + +// TestSignVerify does some detailed checks on how we sign and validate +// signatures +func TestSignVerifyKeyRingWithLedger(t *testing.T) { + dir, cleanup := tests.NewTestCaseDir(t) + t.Cleanup(cleanup) + kb, err := New("keybasename", "test", dir, nil) + require.NoError(t, err) + + i1, err := kb.SaveLedgerKey("key", hd.Secp256k1, "cosmos", 118, 0, 0) + if err != nil { + require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error()) + t.Skip("ledger nano S: support for ledger devices is not available in this executable") + return + } + require.Equal(t, "key", i1.GetName()) + + d1 := []byte("my first message") + s1, pub1, err := kb.Sign("key", d1) + require.NoError(t, err) + + s2, pub2, err := SignWithLedger(i1, d1) + require.NoError(t, err) + + require.True(t, pub1.Equals(pub2)) + require.True(t, bytes.Equal(s1, s2)) + + require.Equal(t, i1.GetPubKey(), pub1) + require.Equal(t, i1.GetPubKey(), pub2) + require.True(t, pub1.VerifyBytes(d1, s1)) + require.True(t, i1.GetPubKey().VerifyBytes(d1, s1)) + require.True(t, bytes.Equal(s1, s2)) + + localInfo, _, err := kb.NewMnemonic("test", English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + _, _, err = SignWithLedger(localInfo, d1) + require.Error(t, err) + require.Equal(t, "not a ledger object", err.Error()) +} + +func TestAltKeyring_SaveLedgerKey(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + // Test unsupported Algo + _, err = keyring.SaveLedgerKey("key", notSupportedAlgo{}, "cosmos", 118, 0, 0) + require.EqualError(t, err, ErrUnsupportedSigningAlgo.Error()) + + ledger, err := keyring.SaveLedgerKey("some_account", hd.Secp256k1, "cosmos", 118, 3, 1) + if err != nil { + require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error()) + t.Skip("ledger nano S: support for ledger devices is not available in this executable") + return + } + // The mock is available, check that the address is correct + require.Equal(t, "some_account", ledger.GetName()) + pubKey := ledger.GetPubKey() + pk, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey) + require.NoError(t, err) + require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk) + + // Check that restoring the key gets the same results + restoredKey, err := keyring.Key("some_account") + require.NoError(t, err) + require.NotNil(t, restoredKey) + require.Equal(t, "some_account", restoredKey.GetName()) + require.Equal(t, TypeLedger, restoredKey.GetType()) + pubKey = restoredKey.GetPubKey() + pk, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey) + require.NoError(t, err) + require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk) + + path, err := restoredKey.GetPath() + require.NoError(t, err) + require.Equal(t, "44'/118'/3'/0/1", path.String()) +} diff --git a/crypto/keyring/keyring_test.go b/crypto/keyring/keyring_test.go index 83cd5a409d3..566fd2c87ba 100644 --- a/crypto/keyring/keyring_test.go +++ b/crypto/keyring/keyring_test.go @@ -1,46 +1,49 @@ package keyring import ( - "bytes" - "errors" "fmt" "strings" "testing" - "github.com/stretchr/testify/assert" + "github.com/99designs/keyring" + "github.com/cosmos/go-bip39" "github.com/stretchr/testify/require" - "github.com/tendermint/go-amino" tmcrypto "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - tmamino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" ) +const ( + someKey = "theKey" + theID = "theID" + otherID = "otherID" +) + func init() { crypto.BcryptSecurityParameter = 1 } -const ( - nums = "1234" - foobar = "foobar" -) - func TestNewKeyring(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) mockIn := strings.NewReader("") t.Cleanup(cleanup) - kr, err := NewKeyring("cosmos", BackendFile, dir, mockIn) + kr, err := New("cosmos", BackendFile, dir, mockIn) require.NoError(t, err) + nilKr, err := New("cosmos", "fuzzy", dir, mockIn) + require.Error(t, err) + require.Nil(t, nilKr) + require.Equal(t, "unknown keyring backend fuzzy", err.Error()) + mockIn.Reset("password\npassword\n") - info, _, err := kr.CreateMnemonic("foo", English, "password", Secp256k1) + info, _, err := kr.NewMnemonic("foo", English, types.FullFundraiserPath, hd.Secp256k1) require.NoError(t, err) require.Equal(t, "foo", info.GetName()) } @@ -48,41 +51,40 @@ func TestNewKeyring(t *testing.T) { func TestKeyManagementKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) + kb, err := New("keybasename", "test", dir, nil) require.NoError(t, err) - algo := Secp256k1 + algo := hd.Secp256k1 n1, n2, n3 := "personal", "business", "other" - p1, p2 := "1234", "really-secure!@#$" // Check empty state l, err := kb.List() require.Nil(t, err) - assert.Empty(t, l) + require.Empty(t, l) - _, _, err = kb.CreateMnemonic(n1, English, p1, Ed25519) + _, _, err = kb.NewMnemonic(n1, English, types.FullFundraiserPath, notSupportedAlgo{}) require.Error(t, err, "ed25519 keys are currently not supported by keybase") // create some keys - _, err = kb.Get(n1) + _, err = kb.Key(n1) require.Error(t, err) - i, _, err := kb.CreateMnemonic(n1, English, p1, algo) + i, _, err := kb.NewMnemonic(n1, English, types.FullFundraiserPath, algo) require.NoError(t, err) require.Equal(t, n1, i.GetName()) - _, _, err = kb.CreateMnemonic(n2, English, p2, algo) + _, _, err = kb.NewMnemonic(n2, English, types.FullFundraiserPath, algo) require.NoError(t, err) // we can get these keys - i2, err := kb.Get(n2) + i2, err := kb.Key(n2) require.NoError(t, err) - _, err = kb.Get(n3) + _, err = kb.Key(n3) require.NotNil(t, err) - _, err = kb.GetByAddress(accAddr(i2)) + _, err = kb.KeyByAddress(accAddr(i2)) require.NoError(t, err) addr, err := sdk.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t") require.NoError(t, err) - _, err = kb.GetByAddress(addr) + _, err = kb.KeyByAddress(addr) require.NotNil(t, err) // list shows them in order @@ -95,21 +97,21 @@ func TestKeyManagementKeyRing(t *testing.T) { require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey()) // deleting a key removes it - err = kb.Delete("bad name", "foo", false) + err = kb.Delete("bad name") require.NotNil(t, err) - err = kb.Delete(n1, p1, false) + err = kb.Delete(n1) require.NoError(t, err) keyS, err = kb.List() require.NoError(t, err) require.Equal(t, 1, len(keyS)) - _, err = kb.Get(n1) + _, err = kb.Key(n1) require.Error(t, err) // create an offline key o1 := "offline" priv1 := ed25519.GenPrivKey() pub1 := priv1.PubKey() - i, err = kb.CreateOffline(o1, pub1, Ed25519) + i, err = kb.SavePubKey(o1, pub1, hd.Ed25519Type) require.Nil(t, err) require.Equal(t, pub1, i.GetPubKey()) require.Equal(t, o1, i.GetName()) @@ -118,98 +120,52 @@ func TestKeyManagementKeyRing(t *testing.T) { require.Equal(t, 2, len(keyS)) // delete the offline key - err = kb.Delete(o1, "", false) + err = kb.Delete(o1) require.NoError(t, err) keyS, err = kb.List() require.NoError(t, err) require.Equal(t, 1, len(keyS)) // addr cache gets nuked - and test skip flag - require.NoError(t, kb.Delete(n2, "", true)) -} - -// TestSignVerify does some detailed checks on how we sign and validate -// signatures -func TestSignVerifyKeyRingWithLedger(t *testing.T) { - dir, cleanup := tests.NewTestCaseDir(t) - t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) - require.NoError(t, err) - - i1, err := kb.CreateLedger("key", Secp256k1, "cosmos", 0, 0) - if err != nil { - require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error()) - t.Skip("ledger nano S: support for ledger devices is not available in this executable") - return - } - require.Equal(t, "key", i1.GetName()) - - p1 := "1234" - d1 := []byte("my first message") - s1, pub1, err := kb.Sign("key", p1, d1) - require.NoError(t, err) - - s2, pub2, err := SignWithLedger(i1, d1) - require.NoError(t, err) - - require.Equal(t, i1.GetPubKey(), pub1) - require.Equal(t, i1.GetPubKey(), pub2) - require.True(t, pub1.VerifyBytes(d1, s1)) - require.True(t, i1.GetPubKey().VerifyBytes(d1, s1)) - require.True(t, bytes.Equal(s1, s2)) - - localInfo, _, err := kb.CreateMnemonic("test", English, p1, Secp256k1) - require.NoError(t, err) - _, _, err = SignWithLedger(localInfo, d1) - require.Error(t, err) - require.Equal(t, "not a ledger object", err.Error()) + require.NoError(t, kb.Delete(n2)) } func TestSignVerifyKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) + + kb, err := New("keybasename", "test", dir, nil) require.NoError(t, err) - algo := Secp256k1 + algo := hd.Secp256k1 n1, n2, n3 := "some dude", "a dudette", "dude-ish" - p1, p2, p3 := "1234", "foobar", "foobar" // create two users and get their info - i1, _, err := kb.CreateMnemonic(n1, English, p1, algo) + i1, _, err := kb.NewMnemonic(n1, English, types.FullFundraiserPath, algo) require.Nil(t, err) - i2, _, err := kb.CreateMnemonic(n2, English, p2, algo) + i2, _, err := kb.NewMnemonic(n2, English, types.FullFundraiserPath, algo) require.Nil(t, err) - // Import a public key - armor, err := kb.ExportPubKey(n2) - require.Nil(t, err) - err = kb.ImportPubKey(n3, armor) - require.NoError(t, err) - i3, err := kb.Get(n3) - require.NoError(t, err) - require.Equal(t, i3.GetName(), n3) - // let's try to sign some messages d1 := []byte("my first message") d2 := []byte("some other important info!") d3 := []byte("feels like I forgot something...") // try signing both data with both .. - s11, pub1, err := kb.Sign(n1, p1, d1) + s11, pub1, err := kb.Sign(n1, d1) require.Nil(t, err) require.Equal(t, i1.GetPubKey(), pub1) - s12, pub1, err := kb.Sign(n1, p1, d2) + s12, pub1, err := kb.Sign(n1, d2) require.Nil(t, err) require.Equal(t, i1.GetPubKey(), pub1) - s21, pub2, err := kb.Sign(n2, p2, d1) + s21, pub2, err := kb.Sign(n2, d1) require.Nil(t, err) require.Equal(t, i2.GetPubKey(), pub2) - s22, pub2, err := kb.Sign(n2, p2, d2) + s22, pub2, err := kb.Sign(n2, d2) require.Nil(t, err) require.Equal(t, i2.GetPubKey(), pub2) @@ -238,7 +194,17 @@ func TestSignVerifyKeyRing(t *testing.T) { } // Now try to sign data with a secret-less key - _, _, err = kb.Sign(n3, p3, d3) + // Import a public key + armor, err := kb.ExportPubKeyArmor(n2) + require.NoError(t, err) + require.NoError(t, kb.Delete(n2)) + + require.NoError(t, kb.ImportPubKey(n3, armor)) + i3, err := kb.Key(n3) + require.NoError(t, err) + require.Equal(t, i3.GetName(), n3) + + _, _, err = kb.Sign(n3, d3) require.Error(t, err) require.Equal(t, "cannot sign with offline keys", err.Error()) } @@ -246,149 +212,138 @@ func TestSignVerifyKeyRing(t *testing.T) { func TestExportImportKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) + kb, err := New("keybasename", "test", dir, nil) require.NoError(t, err) - info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1) + info, _, err := kb.NewMnemonic("john", English, types.FullFundraiserPath, hd.Secp256k1) require.NoError(t, err) require.Equal(t, info.GetName(), "john") - john, err := kb.Get("john") + john, err := kb.Key("john") require.NoError(t, err) require.Equal(t, info.GetName(), "john") johnAddr := info.GetPubKey().Address() - armor, err := kb.Export("john") + armor, err := kb.ExportPrivKeyArmor("john", "apassphrase") + require.NoError(t, err) + err = kb.Delete("john") require.NoError(t, err) - err = kb.Import("john2", armor) + err = kb.ImportPrivKey("john2", armor, "apassphrase") require.NoError(t, err) - john2, err := kb.Get("john2") + john2, err := kb.Key("john2") require.NoError(t, err) require.Equal(t, john.GetPubKey().Address(), johnAddr) require.Equal(t, john.GetName(), "john") - require.Equal(t, john, john2) + require.Equal(t, john.GetAddress(), john2.GetAddress()) + require.Equal(t, john.GetAlgo(), john2.GetAlgo()) + require.Equal(t, john.GetPubKey(), john2.GetPubKey()) + require.Equal(t, john.GetType(), john2.GetType()) } func TestExportImportPubKeyKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) + kb, err := New("keybasename", "test", dir, nil) require.NoError(t, err) - algo := Secp256k1 + algo := hd.Secp256k1 // CreateMnemonic a private-public key pair and ensure consistency - notPasswd := "n9y25ah7" - info, _, err := kb.CreateMnemonic("john", English, notPasswd, algo) + info, _, err := kb.NewMnemonic("john", English, types.FullFundraiserPath, algo) require.Nil(t, err) require.NotEqual(t, info, "") require.Equal(t, info.GetName(), "john") addr := info.GetPubKey().Address() - john, err := kb.Get("john") + john, err := kb.Key("john") require.NoError(t, err) require.Equal(t, john.GetName(), "john") require.Equal(t, john.GetPubKey().Address(), addr) // Export the public key only - armor, err := kb.ExportPubKey("john") + armor, err := kb.ExportPubKeyArmor("john") + require.NoError(t, err) + err = kb.Delete("john") require.NoError(t, err) + // Import it under a different name err = kb.ImportPubKey("john-pubkey-only", armor) require.NoError(t, err) + // Ensure consistency - john2, err := kb.Get("john-pubkey-only") + john2, err := kb.Key("john-pubkey-only") require.NoError(t, err) + // Compare the public keys require.True(t, john.GetPubKey().Equals(john2.GetPubKey())) - // Ensure the original key hasn't changed - john, err = kb.Get("john") - require.NoError(t, err) - require.Equal(t, john.GetPubKey().Address(), addr) - require.Equal(t, john.GetName(), "john") // Ensure keys cannot be overwritten err = kb.ImportPubKey("john-pubkey-only", armor) require.NotNil(t, err) } -func TestExportPrivateKeyObjectKeyRing(t *testing.T) { - dir, cleanup := tests.NewTestCaseDir(t) - t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) - require.NoError(t, err) - - info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1) - require.NoError(t, err) - require.Equal(t, info.GetName(), "john") - - // export private key object - exported, err := kb.ExportPrivateKeyObject("john", "secretcpw") - require.Nil(t, err, "%+v", err) - require.True(t, exported.PubKey().Equals(info.GetPubKey())) -} - func TestAdvancedKeyManagementKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) + + kb, err := New("keybasename", "test", dir, nil) require.NoError(t, err) - algo := Secp256k1 + algo := hd.Secp256k1 n1, n2 := "old-name", "new name" - p1 := "1234" // make sure key works with initial password - _, _, err = kb.CreateMnemonic(n1, English, p1, algo) + _, _, err = kb.NewMnemonic(n1, English, types.FullFundraiserPath, algo) require.Nil(t, err, "%+v", err) - _, err = kb.Export(n1 + ".notreal") + _, err = kb.ExportPubKeyArmor(n1 + ".notreal") require.NotNil(t, err) - _, err = kb.Export(" " + n1) + _, err = kb.ExportPubKeyArmor(" " + n1) require.NotNil(t, err) - _, err = kb.Export(n1 + " ") + _, err = kb.ExportPubKeyArmor(n1 + " ") require.NotNil(t, err) - _, err = kb.Export("") + _, err = kb.ExportPubKeyArmor("") require.NotNil(t, err) - exported, err := kb.Export(n1) + exported, err := kb.ExportPubKeyArmor(n1) require.Nil(t, err, "%+v", err) + err = kb.Delete(n1) + require.NoError(t, err) // import succeeds - err = kb.Import(n2, exported) + err = kb.ImportPubKey(n2, exported) require.NoError(t, err) // second import fails - err = kb.Import(n2, exported) + err = kb.ImportPubKey(n2, exported) require.NotNil(t, err) } func TestSeedPhraseKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) + kb, err := New("keybasename", "test", dir, nil) require.NoError(t, err) - algo := Secp256k1 + algo := hd.Secp256k1 n1, n2 := "lost-key", "found-again" - p1, p2 := "1234", "foobar" // make sure key works with initial password - info, mnemonic, err := kb.CreateMnemonic(n1, English, p1, algo) + info, mnemonic, err := kb.NewMnemonic(n1, English, types.FullFundraiserPath, algo) require.Nil(t, err, "%+v", err) require.Equal(t, n1, info.GetName()) - assert.NotEmpty(t, mnemonic) + require.NotEmpty(t, mnemonic) // now, let us delete this key - err = kb.Delete(n1, p1, false) + err = kb.Delete(n1) require.Nil(t, err, "%+v", err) - _, err = kb.Get(n1) + _, err = kb.Key(n1) require.NotNil(t, err) // let us re-create it from the mnemonic-phrase params := *hd.NewFundraiserParams(0, sdk.CoinType, 0) hdPath := params.String() - newInfo, err := kb.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, Secp256k1) + newInfo, err := kb.NewAccount(n2, mnemonic, DefaultBIP39Passphrase, hdPath, hd.Secp256k1) require.NoError(t, err) require.Equal(t, n2, newInfo.GetName()) require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) @@ -398,192 +353,98 @@ func TestSeedPhraseKeyRing(t *testing.T) { func TestKeyringKeybaseExportImportPrivKey(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) + kb, err := New("keybasename", "test", dir, nil) require.NoError(t, err) - _, _, err = kb.CreateMnemonic("john", English, "password", Secp256k1) + + _, _, err = kb.NewMnemonic("john", English, types.FullFundraiserPath, hd.Secp256k1) require.NoError(t, err) - // no error, password is irrelevant, keystr cointains ASCII armored private key - keystr, err := kb.ExportPrivKey("john", "wrongpassword", "password") + keystr, err := kb.ExportPrivKeyArmor("john", "somepassword") require.NoError(t, err) require.NotEmpty(t, keystr) + err = kb.Delete("john") + require.NoError(t, err) // try import the key - wrong password - err = kb.ImportPrivKey("john2", keystr, "somepassword") + err = kb.ImportPrivKey("john2", keystr, "bad pass") require.Equal(t, "failed to decrypt private key: ciphertext decryption failed", err.Error()) // try import the key with the correct password - require.NoError(t, kb.ImportPrivKey("john2", keystr, "password")) + require.NoError(t, kb.ImportPrivKey("john2", keystr, "somepassword")) // overwrite is not allowed err = kb.ImportPrivKey("john2", keystr, "password") require.Equal(t, "cannot overwrite key: john2", err.Error()) // try export non existing key - _, err = kb.ExportPrivKey("john3", "wrongpassword", "password") + _, err = kb.ExportPrivKeyArmor("john3", "wrongpassword") require.Equal(t, "The specified item could not be found in the keyring", err.Error()) } -func TestSupportedAlgos(t *testing.T) { - dir, cleanup := tests.NewTestCaseDir(t) - t.Cleanup(cleanup) - kb, err := NewKeyring("keybasename", "test", dir, nil) - require.NoError(t, err) - require.Equal(t, []SigningAlgo{"secp256k1"}, kb.SupportedAlgos()) - require.Equal(t, []SigningAlgo{"secp256k1"}, kb.SupportedAlgosLedger()) -} - -func TestCustomDerivFuncKey(t *testing.T) { - kb := NewInMemory(WithDeriveFunc(func(mnemonic string, bip39Passphrase, hdPath string, algo SigningAlgo) ([]byte, error) { - return nil, errors.New("cannot derive keys") - })) - _, _, err := kb.CreateMnemonic("test", English, "", "") - require.Error(t, err, "cannot derive keys") -} - func TestInMemoryLanguage(t *testing.T) { kb := NewInMemory() - _, _, err := kb.CreateMnemonic("something", Japanese, "no_pass", Secp256k1) + _, _, err := kb.NewMnemonic("something", Japanese, types.FullFundraiserPath, hd.Secp256k1) require.Error(t, err) require.Equal(t, "unsupported language: only english is supported", err.Error()) } func TestInMemoryCreateMultisig(t *testing.T) { - kb, err := NewKeyring("keybasename", "memory", "", nil) + kb, err := New("keybasename", "memory", "", nil) require.NoError(t, err) multi := multisig.PubKeyMultisigThreshold{ K: 1, PubKeys: []tmcrypto.PubKey{secp256k1.GenPrivKey().PubKey()}, } - _, err = kb.CreateMulti("multi", multi) + _, err = kb.SaveMultisig("multi", multi) require.NoError(t, err) } func TestInMemoryCreateAccountInvalidMnemonic(t *testing.T) { kb := NewInMemory() - _, err := kb.CreateAccount( + _, err := kb.NewAccount( "some_account", "malarkey pair crucial catch public canyon evil outer stage ten gym tornado", - "", "", CreateHDPath(0, 0).String(), Secp256k1) + "", hd.CreateHDPath(118, 0, 0).String(), hd.Secp256k1) require.Error(t, err) require.Equal(t, "Invalid mnemonic", err.Error()) } -func TestInMemoryCreateLedgerUnsupportedAlgo(t *testing.T) { - kb := NewInMemory() - - supportedLedgerAlgos := kb.SupportedAlgosLedger() - for _, supportedAlgo := range supportedLedgerAlgos { - if Ed25519 == supportedAlgo { - require.FailNow(t, "Was not an unsupported algorithm") - } - } - - _, err := kb.CreateLedger("some_account", Ed25519, "cosmos", 0, 1) - require.Error(t, err) - require.Equal(t, "unsupported signing algo", err.Error()) -} - -func TestInMemoryCreateLedger(t *testing.T) { - kb := NewInMemory(WithSupportedAlgosLedger([]SigningAlgo{Secp256k1, Ed25519})) - - // test_cover and test_unit will result in different answers - // test_cover does not compile some dependencies so ledger is disabled - // test_unit may add a ledger mock - // both cases are acceptable - supportedLedgerAlgos := kb.SupportedAlgosLedger() - secpSupported := false - edSupported := false - for _, supportedAlgo := range supportedLedgerAlgos { - secpSupported = secpSupported || (supportedAlgo == Secp256k1) - edSupported = edSupported || (supportedAlgo == Ed25519) - } - require.True(t, secpSupported) - require.True(t, edSupported) - - ledger, err := kb.CreateLedger("some_account", Secp256k1, "cosmos", 3, 1) - - if err != nil { - require.Error(t, err) - require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error()) - require.Nil(t, ledger) - t.Skip("ledger nano S: support for ledger devices is not available in this executable") - return - } - - // The mock is available, check that the address is correct - pubKey := ledger.GetPubKey() - pk, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey) - require.NoError(t, err) - require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk) - - // Check that restoring the key gets the same results - restoredKey, err := kb.Get("some_account") - require.NoError(t, err) - require.NotNil(t, restoredKey) - require.Equal(t, "some_account", restoredKey.GetName()) - require.Equal(t, TypeLedger, restoredKey.GetType()) - pubKey = restoredKey.GetPubKey() - pk, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey) - require.NoError(t, err) - require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk) - - path, err := restoredKey.GetPath() - require.NoError(t, err) - require.Equal(t, "44'/118'/3'/0/1", path.String()) -} - // TestInMemoryKeyManagement makes sure we can manipulate these keys well func TestInMemoryKeyManagement(t *testing.T) { // make the storage with reasonable defaults - cstore := NewInMemory(WithSupportedAlgos([]SigningAlgo{Secp256k1, Sr25519})) - - // Test modified supported algos - supportedAlgos := cstore.SupportedAlgos() - secpSupported := false - edSupported := false - srSupported := false - for _, supportedAlgo := range supportedAlgos { - secpSupported = secpSupported || (supportedAlgo == Secp256k1) - edSupported = edSupported || (supportedAlgo == Ed25519) - srSupported = srSupported || (supportedAlgo == Sr25519) - } - require.True(t, secpSupported) - require.False(t, edSupported) - require.True(t, srSupported) + cstore := NewInMemory() - algo := Secp256k1 + algo := hd.Secp256k1 n1, n2, n3 := "personal", "business", "other" - p1, p2 := nums, "really-secure!@#$" // Check empty state l, err := cstore.List() require.Nil(t, err) require.Empty(t, l) - _, _, err = cstore.CreateMnemonic(n1, English, p1, Ed25519) + _, _, err = cstore.NewMnemonic(n1, English, types.FullFundraiserPath, notSupportedAlgo{}) require.Error(t, err, "ed25519 keys are currently not supported by keybase") // create some keys - _, err = cstore.Get(n1) + _, err = cstore.Key(n1) require.Error(t, err) - i, _, err := cstore.CreateMnemonic(n1, English, p1, algo) + i, _, err := cstore.NewMnemonic(n1, English, types.FullFundraiserPath, algo) require.NoError(t, err) require.Equal(t, n1, i.GetName()) - _, _, err = cstore.CreateMnemonic(n2, English, p2, algo) + _, _, err = cstore.NewMnemonic(n2, English, types.FullFundraiserPath, algo) require.NoError(t, err) // we can get these keys - i2, err := cstore.Get(n2) + i2, err := cstore.Key(n2) require.NoError(t, err) - _, err = cstore.Get(n3) + _, err = cstore.Key(n3) require.NotNil(t, err) - _, err = cstore.GetByAddress(accAddr(i2)) + _, err = cstore.KeyByAddress(accAddr(i2)) require.NoError(t, err) addr, err := sdk.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t") require.NoError(t, err) - _, err = cstore.GetByAddress(addr) + _, err = cstore.KeyByAddress(addr) require.NotNil(t, err) // list shows them in order @@ -596,39 +457,39 @@ func TestInMemoryKeyManagement(t *testing.T) { require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey()) // deleting a key removes it - err = cstore.Delete("bad name", "foo", false) + err = cstore.Delete("bad name") require.NotNil(t, err) - err = cstore.Delete(n1, p1, false) + err = cstore.Delete(n1) require.NoError(t, err) keyS, err = cstore.List() require.NoError(t, err) require.Equal(t, 1, len(keyS)) - _, err = cstore.Get(n1) + _, err = cstore.Key(n1) require.Error(t, err) // create an offline key o1 := "offline" priv1 := ed25519.GenPrivKey() pub1 := priv1.PubKey() - i, err = cstore.CreateOffline(o1, pub1, algo) + i, err = cstore.SavePubKey(o1, pub1, hd.Ed25519Type) require.Nil(t, err) require.Equal(t, pub1, i.GetPubKey()) require.Equal(t, o1, i.GetName()) iOffline := i.(*offlineInfo) - require.Equal(t, algo, iOffline.GetAlgo()) + require.Equal(t, hd.Ed25519Type, iOffline.GetAlgo()) keyS, err = cstore.List() require.NoError(t, err) require.Equal(t, 2, len(keyS)) // delete the offline key - err = cstore.Delete(o1, "", false) + err = cstore.Delete(o1) require.NoError(t, err) keyS, err = cstore.List() require.NoError(t, err) require.Equal(t, 1, len(keyS)) // addr cache gets nuked - and test skip flag - err = cstore.Delete(n2, "", true) + err = cstore.Delete(n2) require.NoError(t, err) } @@ -636,26 +497,16 @@ func TestInMemoryKeyManagement(t *testing.T) { // signatures func TestInMemorySignVerify(t *testing.T) { cstore := NewInMemory() - algo := Secp256k1 + algo := hd.Secp256k1 n1, n2, n3 := "some dude", "a dudette", "dude-ish" - p1, p2, p3 := nums, foobar, foobar // create two users and get their info - i1, _, err := cstore.CreateMnemonic(n1, English, p1, algo) - require.Nil(t, err) - - i2, _, err := cstore.CreateMnemonic(n2, English, p2, algo) + i1, _, err := cstore.NewMnemonic(n1, English, types.FullFundraiserPath, algo) require.Nil(t, err) - // Import a public key - armor, err := cstore.ExportPubKey(n2) + i2, _, err := cstore.NewMnemonic(n2, English, types.FullFundraiserPath, algo) require.Nil(t, err) - err = cstore.ImportPubKey(n3, armor) - require.NoError(t, err) - i3, err := cstore.Get(n3) - require.NoError(t, err) - require.Equal(t, i3.GetName(), n3) // let's try to sign some messages d1 := []byte("my first message") @@ -663,19 +514,19 @@ func TestInMemorySignVerify(t *testing.T) { d3 := []byte("feels like I forgot something...") // try signing both data with both .. - s11, pub1, err := cstore.Sign(n1, p1, d1) + s11, pub1, err := cstore.Sign(n1, d1) require.Nil(t, err) require.Equal(t, i1.GetPubKey(), pub1) - s12, pub1, err := cstore.Sign(n1, p1, d2) + s12, pub1, err := cstore.Sign(n1, d2) require.Nil(t, err) require.Equal(t, i1.GetPubKey(), pub1) - s21, pub2, err := cstore.Sign(n2, p2, d1) + s21, pub2, err := cstore.Sign(n2, d1) require.Nil(t, err) require.Equal(t, i2.GetPubKey(), pub2) - s22, pub2, err := cstore.Sign(n2, p2, d2) + s22, pub2, err := cstore.Sign(n2, d2) require.Nil(t, err) require.Equal(t, i2.GetPubKey(), pub2) @@ -703,8 +554,19 @@ func TestInMemorySignVerify(t *testing.T) { require.Equal(t, tc.valid, valid, "%d", i) } + // Import a public key + armor, err := cstore.ExportPubKeyArmor(n2) + require.Nil(t, err) + err = cstore.Delete(n2) + require.NoError(t, err) + err = cstore.ImportPubKey(n3, armor) + require.NoError(t, err) + i3, err := cstore.Key(n3) + require.NoError(t, err) + require.Equal(t, i3.GetName(), n3) + // Now try to sign data with a secret-less key - _, _, err = cstore.Sign(n3, p3, d3) + _, _, err = cstore.Sign(n3, d3) require.Error(t, err) require.Equal(t, "cannot sign with offline keys", err.Error()) } @@ -714,52 +576,55 @@ func TestInMemoryExportImport(t *testing.T) { // make the storage with reasonable defaults cstore := NewInMemory() - info, _, err := cstore.CreateMnemonic("john", English, "secretcpw", Secp256k1) + info, _, err := cstore.NewMnemonic("john", English, types.FullFundraiserPath, hd.Secp256k1) require.NoError(t, err) require.Equal(t, info.GetName(), "john") - john, err := cstore.Get("john") + john, err := cstore.Key("john") require.NoError(t, err) require.Equal(t, info.GetName(), "john") johnAddr := info.GetPubKey().Address() - armor, err := cstore.Export("john") + armor, err := cstore.ExportPubKeyArmor("john") + require.NoError(t, err) + err = cstore.Delete("john") require.NoError(t, err) - err = cstore.Import("john2", armor) + err = cstore.ImportPubKey("john2", armor) require.NoError(t, err) - john2, err := cstore.Get("john2") + john2, err := cstore.Key("john2") require.NoError(t, err) require.Equal(t, john.GetPubKey().Address(), johnAddr) require.Equal(t, john.GetName(), "john") - require.Equal(t, john, john2) + require.Equal(t, john.GetAddress(), john2.GetAddress()) + require.Equal(t, john.GetAlgo(), john2.GetAlgo()) + require.Equal(t, john.GetPubKey(), john2.GetPubKey()) } func TestInMemoryExportImportPrivKey(t *testing.T) { kb := NewInMemory() - info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1) + info, _, err := kb.NewMnemonic("john", English, types.FullFundraiserPath, hd.Secp256k1) require.NoError(t, err) require.Equal(t, info.GetName(), "john") - priv1, err := kb.Get("john") + priv1, err := kb.Key("john") require.NoError(t, err) - // decrypt local private key, and produce encrypted ASCII armored output - armored, err := kb.ExportPrivKey("john", "secretcpw", "new_secretcpw") + armored, err := kb.ExportPrivKeyArmor("john", "secretcpw") require.NoError(t, err) // delete exported key - require.NoError(t, kb.Delete("john", "", true)) - _, err = kb.Get("john") + require.NoError(t, kb.Delete("john")) + _, err = kb.Key("john") require.Error(t, err) // import armored key - require.NoError(t, kb.ImportPrivKey("john", armored, "new_secretcpw")) + require.NoError(t, kb.ImportPrivKey("john", armored, "secretcpw")) // ensure old and new keys match - priv2, err := kb.Get("john") + priv2, err := kb.Key("john") require.NoError(t, err) require.True(t, priv1.GetPubKey().Equals(priv2.GetPubKey())) } @@ -769,133 +634,131 @@ func TestInMemoryExportImportPubKey(t *testing.T) { cstore := NewInMemory() // CreateMnemonic a private-public key pair and ensure consistency - notPasswd := "n9y25ah7" - info, _, err := cstore.CreateMnemonic("john", English, notPasswd, Secp256k1) + info, _, err := cstore.NewMnemonic("john", English, types.FullFundraiserPath, hd.Secp256k1) require.Nil(t, err) require.NotEqual(t, info, "") require.Equal(t, info.GetName(), "john") addr := info.GetPubKey().Address() - john, err := cstore.Get("john") + john, err := cstore.Key("john") require.NoError(t, err) require.Equal(t, john.GetName(), "john") require.Equal(t, john.GetPubKey().Address(), addr) // Export the public key only - armor, err := cstore.ExportPubKey("john") + armor, err := cstore.ExportPubKeyArmor("john") + require.NoError(t, err) + err = cstore.Delete("john") require.NoError(t, err) + // Import it under a different name err = cstore.ImportPubKey("john-pubkey-only", armor) require.NoError(t, err) // Ensure consistency - john2, err := cstore.Get("john-pubkey-only") + john2, err := cstore.Key("john-pubkey-only") require.NoError(t, err) // Compare the public keys require.True(t, john.GetPubKey().Equals(john2.GetPubKey())) - // Ensure the original key hasn't changed - john, err = cstore.Get("john") - require.NoError(t, err) - require.Equal(t, john.GetPubKey().Address(), addr) - require.Equal(t, john.GetName(), "john") // Ensure keys cannot be overwritten err = cstore.ImportPubKey("john-pubkey-only", armor) require.NotNil(t, err) } -func TestInMemoryExportPrivateKeyObject(t *testing.T) { - kb := NewInMemory() - - info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1) - require.NoError(t, err) - require.Equal(t, info.GetName(), "john") - - // export private key object - _, err = kb.ExportPrivateKeyObject("john", "invalid") - require.NoError(t, err, "%+v", err) - exported, err := kb.ExportPrivateKeyObject("john", "secretcpw") - require.Nil(t, err, "%+v", err) - require.True(t, exported.PubKey().Equals(info.GetPubKey())) -} - // TestInMemoryAdvancedKeyManagement verifies update, import, export functionality func TestInMemoryAdvancedKeyManagement(t *testing.T) { // make the storage with reasonable defaults cstore := NewInMemory() - algo := Secp256k1 + algo := hd.Secp256k1 n1, n2 := "old-name", "new name" - p1 := nums // make sure key works with initial password - _, _, err := cstore.CreateMnemonic(n1, English, p1, algo) + _, _, err := cstore.NewMnemonic(n1, English, types.FullFundraiserPath, algo) require.Nil(t, err, "%+v", err) // exporting requires the proper name and passphrase - _, err = cstore.Export(n1 + ".notreal") + _, err = cstore.ExportPubKeyArmor(n1 + ".notreal") require.NotNil(t, err) - _, err = cstore.Export(" " + n1) + _, err = cstore.ExportPubKeyArmor(" " + n1) require.NotNil(t, err) - _, err = cstore.Export(n1 + " ") + _, err = cstore.ExportPubKeyArmor(n1 + " ") require.NotNil(t, err) - _, err = cstore.Export("") + _, err = cstore.ExportPubKeyArmor("") require.NotNil(t, err) - exported, err := cstore.Export(n1) + exported, err := cstore.ExportPubKeyArmor(n1) require.Nil(t, err, "%+v", err) + err = cstore.Delete(n1) + require.NoError(t, err) // import succeeds - err = cstore.Import(n2, exported) + err = cstore.ImportPubKey(n2, exported) require.NoError(t, err) // second import fails - err = cstore.Import(n2, exported) + err = cstore.ImportPubKey(n2, exported) require.NotNil(t, err) } // TestInMemorySeedPhrase verifies restoring from a seed phrase func TestInMemorySeedPhrase(t *testing.T) { - // make the storage with reasonable defaults cstore := NewInMemory() - algo := Secp256k1 + algo := hd.Secp256k1 n1, n2 := "lost-key", "found-again" - p1, p2 := nums, foobar // make sure key works with initial password - info, mnemonic, err := cstore.CreateMnemonic(n1, English, p1, algo) + info, mnemonic, err := cstore.NewMnemonic(n1, English, types.FullFundraiserPath, algo) require.Nil(t, err, "%+v", err) require.Equal(t, n1, info.GetName()) require.NotEmpty(t, mnemonic) // now, let us delete this key - err = cstore.Delete(n1, p1, false) + err = cstore.Delete(n1) require.Nil(t, err, "%+v", err) - _, err = cstore.Get(n1) + _, err = cstore.Key(n1) require.NotNil(t, err) // let us re-create it from the mnemonic-phrase params := *hd.NewFundraiserParams(0, sdk.CoinType, 0) hdPath := params.String() - newInfo, err := cstore.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, Secp256k1) + newInfo, err := cstore.NewAccount(n2, mnemonic, DefaultBIP39Passphrase, hdPath, algo) require.NoError(t, err) require.Equal(t, n2, newInfo.GetName()) require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) require.Equal(t, info.GetPubKey(), newInfo.GetPubKey()) } +func TestKeyChain_ShouldFailWhenAddingSameGeneratedAccount(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + kr, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + // Given we create a mnemonic + _, seed, err := kr.NewMnemonic("test", English, "", hd.Secp256k1) + require.NoError(t, err) + + require.NoError(t, kr.Delete("test")) + + path := hd.CreateHDPath(118, 0, 0).String() + _, err = kr.NewAccount("test1", seed, "", path, hd.Secp256k1) + require.NoError(t, err) + + // Creating another account with different uid but same seed should fail due to have same pub address + _, err = kr.NewAccount("test2", seed, "", path, hd.Secp256k1) + require.Error(t, err) +} + func ExampleNew() { // Select the encryption and storage for your cryptostore - customKeyGenFunc := func(bz []byte, algo SigningAlgo) (tmcrypto.PrivKey, error) { - var bzArr [32]byte - copy(bzArr[:], bz) - return secp256k1.PrivKeySecp256k1(bzArr), nil - } - cstore := NewInMemory(WithKeygenFunc(customKeyGenFunc)) + cstore := NewInMemory() - sec := Secp256k1 + sec := hd.Secp256k1 // Add keys and see they return in alphabetical order - bob, _, err := cstore.CreateMnemonic("Bob", English, "friend", sec) + bob, _, err := cstore.NewMnemonic("Bob", English, types.FullFundraiserPath, sec) if err != nil { // this should never happen fmt.Println(err) @@ -903,8 +766,8 @@ func ExampleNew() { // return info here just like in List fmt.Println(bob.GetName()) } - _, _, _ = cstore.CreateMnemonic("Alice", English, "secret", sec) - _, _, _ = cstore.CreateMnemonic("Carl", English, "mitm", sec) + _, _, _ = cstore.NewMnemonic("Alice", English, types.FullFundraiserPath, sec) + _, _, _ = cstore.NewMnemonic("Carl", English, types.FullFundraiserPath, sec) info, _ := cstore.List() for _, i := range info { fmt.Println(i.GetName()) @@ -912,13 +775,13 @@ func ExampleNew() { // We need to use passphrase to generate a signature tx := []byte("deadbeef") - sig, pub, err := cstore.Sign("Bob", "friend", tx) + sig, pub, err := cstore.Sign("Bob", tx) if err != nil { fmt.Println("don't accept real passphrase") } // and we can validate the signature with publicly available info - binfo, _ := cstore.Get("Bob") + binfo, _ := cstore.Key("Bob") if !binfo.GetPubKey().Equals(bob.GetPubKey()) { fmt.Println("Get and Create return different keys") } @@ -938,77 +801,394 @@ func ExampleNew() { // signed by Bob } -func accAddr(info Info) sdk.AccAddress { - return (sdk.AccAddress)(info.GetPubKey().Address()) +func TestAltKeyring_List(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + list, err := keyring.List() + require.NoError(t, err) + require.Empty(t, list) + + // Fails on creating unsupported pubKeyType + _, _, err = keyring.NewMnemonic("failing", English, types.FullFundraiserPath, notSupportedAlgo{}) + require.EqualError(t, err, ErrUnsupportedSigningAlgo.Error()) + + // Create 3 keys + uid1, uid2, uid3 := "Zkey", "Bkey", "Rkey" + _, _, err = keyring.NewMnemonic(uid1, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + _, _, err = keyring.NewMnemonic(uid2, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + _, _, err = keyring.NewMnemonic(uid3, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + list, err = keyring.List() + require.NoError(t, err) + require.Len(t, list, 3) + + // Check they are in alphabetical order + require.Equal(t, uid2, list[0].GetName()) + require.Equal(t, uid3, list[1].GetName()) + require.Equal(t, uid1, list[2].GetName()) } -var _ tmcrypto.PrivKey = testPriv{} -var _ tmcrypto.PubKey = testPub{} -var testCdc *amino.Codec +func TestAltKeyring_NewAccount(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) -type testPriv []byte + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) -func (privkey testPriv) PubKey() tmcrypto.PubKey { return testPub{} } -func (privkey testPriv) Bytes() []byte { - return testCdc.MustMarshalBinaryBare(privkey) + entropy, err := bip39.NewEntropy(defaultEntropySize) + require.NoError(t, err) + + mnemonic, err := bip39.NewMnemonic(entropy) + require.NoError(t, err) + + uid := "newUid" + + // Fails on creating unsupported pubKeyType + _, err = keyring.NewAccount(uid, mnemonic, DefaultBIP39Passphrase, sdk.FullFundraiserPath, notSupportedAlgo{}) + require.EqualError(t, err, ErrUnsupportedSigningAlgo.Error()) + + info, err := keyring.NewAccount(uid, mnemonic, DefaultBIP39Passphrase, sdk.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + require.Equal(t, uid, info.GetName()) + + list, err := keyring.List() + require.NoError(t, err) + require.Len(t, list, 1) } -func (privkey testPriv) Sign(msg []byte) ([]byte, error) { return []byte{}, nil } -func (privkey testPriv) Equals(other tmcrypto.PrivKey) bool { return true } -type testPub []byte +func TestAltKeyring_Get(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) -func (key testPub) Address() tmcrypto.Address { return tmcrypto.Address{} } -func (key testPub) Bytes() []byte { - return testCdc.MustMarshalBinaryBare(key) + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := someKey + mnemonic, _, err := keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + key, err := keyring.Key(uid) + require.NoError(t, err) + requireEqualInfo(t, mnemonic, key) } -func (key testPub) VerifyBytes(msg []byte, sig []byte) bool { return true } -func (key testPub) Equals(other tmcrypto.PubKey) bool { return true } -func TestInMemoryKeygenOverride(t *testing.T) { - // Save existing codec and reset after test - cryptoCdc := CryptoCdc - t.Cleanup(func() { - CryptoCdc = cryptoCdc - }) +func TestAltKeyring_KeyByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) - // Setup testCdc encoding and decoding new key type - testCdc = codec.New() - RegisterCodec(testCdc) - tmamino.RegisterAmino(testCdc) - - // Set up codecs for using new key types - privName, pubName := "test/priv_name", "test/pub_name" - tmamino.RegisterKeyType(testPriv{}, privName) - tmamino.RegisterKeyType(testPub{}, pubName) - testCdc.RegisterConcrete(testPriv{}, privName, nil) - testCdc.RegisterConcrete(testPub{}, pubName, nil) - CryptoCdc = testCdc - - overrideCalled := false - dummyFunc := func(bz []byte, algo SigningAlgo) (tmcrypto.PrivKey, error) { - overrideCalled = true - return testPriv(bz), nil - } + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := someKey + mnemonic, _, err := keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) - kb := NewInMemory(WithKeygenFunc(dummyFunc)) + key, err := keyring.KeyByAddress(mnemonic.GetAddress()) + require.NoError(t, err) + requireEqualInfo(t, key, mnemonic) +} - testName, pw := "name", "testPassword" +func TestAltKeyring_Delete(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) - // create new key which will generate with - info, _, err := kb.CreateMnemonic(testName, English, pw, Secp256k1) + keyring, err := New(t.Name(), BackendTest, dir, nil) require.NoError(t, err) - require.Equal(t, info.GetName(), testName) - // Assert overridden function was called - require.True(t, overrideCalled) + uid := someKey + _, _, err = keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) - // export private key object - exported, err := kb.ExportPrivateKeyObject(testName, pw) - require.Nil(t, err, "%+v", err) + list, err := keyring.List() + require.NoError(t, err) + require.Len(t, list, 1) + + err = keyring.Delete(uid) + require.NoError(t, err) + + list, err = keyring.List() + require.NoError(t, err) + require.Empty(t, list) +} + +func TestAltKeyring_DeleteByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := someKey + mnemonic, _, err := keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + list, err := keyring.List() + require.NoError(t, err) + require.Len(t, list, 1) + + err = keyring.DeleteByAddress(mnemonic.GetAddress()) + require.NoError(t, err) + + list, err = keyring.List() + require.NoError(t, err) + require.Empty(t, list) +} + +func TestAltKeyring_SavePubKey(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + list, err := keyring.List() + require.NoError(t, err) + require.Empty(t, list) + + key := someKey + priv := ed25519.GenPrivKey() + pub := priv.PubKey() + + info, err := keyring.SavePubKey(key, pub, hd.Secp256k1.Name()) + require.Nil(t, err) + require.Equal(t, pub, info.GetPubKey()) + require.Equal(t, key, info.GetName()) + require.Equal(t, hd.Secp256k1.Name(), info.GetAlgo()) + + list, err = keyring.List() + require.NoError(t, err) + require.Equal(t, 1, len(list)) +} + +func TestAltKeyring_SaveMultisig(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + mnemonic1, _, err := keyring.NewMnemonic("key1", English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + mnemonic2, _, err := keyring.NewMnemonic("key2", English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + key := "multi" + pub := multisig.NewPubKeyMultisigThreshold(2, []tmcrypto.PubKey{mnemonic1.GetPubKey(), mnemonic2.GetPubKey()}) + + info, err := keyring.SaveMultisig(key, pub) + require.Nil(t, err) + require.Equal(t, pub, info.GetPubKey()) + require.Equal(t, key, info.GetName()) + + list, err := keyring.List() + require.NoError(t, err) + require.Len(t, list, 3) +} + +func TestAltKeyring_Sign(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := "jack" + _, _, err = keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + msg := []byte("some message") + + sign, key, err := keyring.Sign(uid, msg) + require.NoError(t, err) + + require.True(t, key.VerifyBytes(msg, sign)) +} + +func TestAltKeyring_SignByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) - // require that the key type is the new key - _, ok := exported.(testPriv) - require.True(t, ok) + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := "jack" + mnemonic, _, err := keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + msg := []byte("some message") + + sign, key, err := keyring.SignByAddress(mnemonic.GetAddress(), msg) + require.NoError(t, err) + + require.True(t, key.VerifyBytes(msg, sign)) +} + +func TestAltKeyring_ImportExportPrivKey(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := theID + _, _, err = keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + passphrase := "somePass" + armor, err := keyring.ExportPrivKeyArmor(uid, passphrase) + require.NoError(t, err) + err = keyring.Delete(uid) + require.NoError(t, err) + newUID := otherID + // Should fail importing with wrong password + err = keyring.ImportPrivKey(newUID, armor, "wrongPass") + require.EqualError(t, err, "failed to decrypt private key: ciphertext decryption failed") + + err = keyring.ImportPrivKey(newUID, armor, passphrase) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = keyring.ImportPrivKey(newUID, armor, passphrase) + require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", newUID)) +} + +func TestAltKeyring_ImportExportPrivKey_ByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := theID + mnemonic, _, err := keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + passphrase := "somePass" + armor, err := keyring.ExportPrivKeyArmorByAddress(mnemonic.GetAddress(), passphrase) + require.NoError(t, err) + err = keyring.Delete(uid) + require.NoError(t, err) + + newUID := otherID + // Should fail importing with wrong password + err = keyring.ImportPrivKey(newUID, armor, "wrongPass") + require.EqualError(t, err, "failed to decrypt private key: ciphertext decryption failed") - require.True(t, exported.PubKey().Equals(info.GetPubKey())) + err = keyring.ImportPrivKey(newUID, armor, passphrase) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = keyring.ImportPrivKey(newUID, armor, passphrase) + require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", newUID)) } + +func TestAltKeyring_ImportExportPubKey(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := theID + _, _, err = keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + armor, err := keyring.ExportPubKeyArmor(uid) + require.NoError(t, err) + err = keyring.Delete(uid) + require.NoError(t, err) + + newUID := otherID + err = keyring.ImportPubKey(newUID, armor) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = keyring.ImportPubKey(newUID, armor) + require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", newUID)) +} + +func TestAltKeyring_ImportExportPubKey_ByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := theID + mnemonic, _, err := keyring.NewMnemonic(uid, English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + armor, err := keyring.ExportPubKeyArmorByAddress(mnemonic.GetAddress()) + require.NoError(t, err) + err = keyring.Delete(uid) + require.NoError(t, err) + + newUID := otherID + err = keyring.ImportPubKey(newUID, armor) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = keyring.ImportPubKey(newUID, armor) + require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", newUID)) +} + +func TestAltKeyring_ConstructorSupportedAlgos(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := New(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + // should fail when using unsupported signing algorythm. + _, _, err = keyring.NewMnemonic("test", English, types.FullFundraiserPath, notSupportedAlgo{}) + require.EqualError(t, err, "unsupported signing algo") + + // but works with default signing algo. + _, _, err = keyring.NewMnemonic("test", English, types.FullFundraiserPath, hd.Secp256k1) + require.NoError(t, err) + + // but we can create a new keybase with our provided algos. + dir2, clean2 := tests.NewTestCaseDir(t) + t.Cleanup(clean2) + + keyring2, err := New(t.Name(), BackendTest, dir2, nil, func(options *Options) { + options.SupportedAlgos = SigningAlgoList{ + notSupportedAlgo{}, + } + }) + require.NoError(t, err) + + // now this new keyring does not fail when signing with provided algo + _, _, err = keyring2.NewMnemonic("test", English, types.FullFundraiserPath, notSupportedAlgo{}) + require.NoError(t, err) +} + +func TestBackendConfigConstructors(t *testing.T) { + backend := newKWalletBackendKeyringConfig("test", "", nil) + require.Equal(t, []keyring.BackendType{keyring.KWalletBackend}, backend.AllowedBackends) + require.Equal(t, "kdewallet", backend.ServiceName) + require.Equal(t, "test", backend.KWalletAppID) + + backend = newPassBackendKeyringConfig("test", "directory", nil) + require.Equal(t, []keyring.BackendType{keyring.PassBackend}, backend.AllowedBackends) + require.Equal(t, "test", backend.ServiceName) + require.Equal(t, "keyring-test", backend.PassPrefix) +} + +func requireEqualInfo(t *testing.T, key Info, mnemonic Info) { + require.Equal(t, key.GetName(), mnemonic.GetName()) + require.Equal(t, key.GetAddress(), mnemonic.GetAddress()) + require.Equal(t, key.GetPubKey(), mnemonic.GetPubKey()) + require.Equal(t, key.GetAlgo(), mnemonic.GetAlgo()) + require.Equal(t, key.GetType(), mnemonic.GetType()) +} + +func accAddr(info Info) sdk.AccAddress { return info.GetAddress() } diff --git a/crypto/keyring/legacy.go b/crypto/keyring/legacy.go index 2d61f315541..2aa76fdcb5f 100644 --- a/crypto/keyring/legacy.go +++ b/crypto/keyring/legacy.go @@ -2,6 +2,7 @@ package keyring import ( "fmt" + "io" "strings" "github.com/pkg/errors" @@ -179,10 +180,53 @@ func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string, } // Close the underlying storage. -func (kb dbKeybase) Close() error { - return kb.db.Close() +func (kb dbKeybase) Close() error { return kb.db.Close() } + +func infoKey(name string) []byte { return []byte(fmt.Sprintf("%s.%s", name, infoSuffix)) } + +// InfoImporter is implemented by those types that want to provide functions necessary +// to migrate keys from LegacyKeybase types to Keyring types. +type InfoImporter interface { + // Import imports ASCII-armored private keys. + Import(uid string, armor string) error +} + +type keyringMigrator struct { + kr keystore +} + +func NewInfoImporter( + appName, backend, rootDir string, userInput io.Reader, opts ...Option, +) (InfoImporter, error) { + keyring, err := New(appName, backend, rootDir, userInput, opts...) + if err != nil { + return keyringMigrator{}, err + } + kr := keyring.(keystore) + return keyringMigrator{kr}, nil } -func infoKey(name string) []byte { - return []byte(fmt.Sprintf("%s.%s", name, infoSuffix)) +func (m keyringMigrator) Import(uid string, armor string) error { + _, err := m.kr.Key(uid) + if err == nil { + return fmt.Errorf("cannot overwrite key %q", uid) + } + + infoBytes, err := crypto.UnarmorInfoBytes(armor) + if err != nil { + return err + } + + info, err := unmarshalInfo(infoBytes) + if err != nil { + return err + } + + return m.kr.writeInfo(info) +} + +// KeybaseOption overrides options for the db. +type KeybaseOption func(*kbOptions) + +type kbOptions struct { } diff --git a/crypto/keyring/legacy_test.go b/crypto/keyring/legacy_test.go index 1e2b475660b..11ea30bf05e 100644 --- a/crypto/keyring/legacy_test.go +++ b/crypto/keyring/legacy_test.go @@ -1,6 +1,7 @@ package keyring_test import ( + "io" "path/filepath" "testing" @@ -38,7 +39,21 @@ func TestLegacyKeybase(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, armor) + _, err = kb.ExportPrivKey(keys[0].GetName(), "12345678", "12345678") + require.Error(t, err) + armoredInfo, err := kb.Export(keys[0].GetName()) require.NoError(t, err) require.NotEmpty(t, armoredInfo) + + importer, err := keyring.NewInfoImporter("cosmos", "memory", "", nil) + require.NoError(t, err) + err = importer.Import("test", "") + require.Error(t, err) + require.Equal(t, io.EOF, err) + require.NoError(t, importer.Import("test", armoredInfo)) + + err = importer.Import("test", armoredInfo) + require.Error(t, err) + require.Equal(t, `public key already exist in keybase`, err.Error()) } diff --git a/crypto/keyring/options.go b/crypto/keyring/options.go deleted file mode 100644 index 21e69398f00..00000000000 --- a/crypto/keyring/options.go +++ /dev/null @@ -1,39 +0,0 @@ -package keyring - -// KeybaseOption overrides options for the db -type KeybaseOption func(*kbOptions) - -type kbOptions struct { - keygenFunc PrivKeyGenFunc - deriveFunc DeriveKeyFunc - supportedAlgos []SigningAlgo - supportedAlgosLedger []SigningAlgo -} - -// WithKeygenFunc applies an overridden key generation function to generate the private key. -func WithKeygenFunc(f PrivKeyGenFunc) KeybaseOption { - return func(o *kbOptions) { - o.keygenFunc = f - } -} - -// WithDeriveFunc applies an overridden key derivation function to generate the private key. -func WithDeriveFunc(f DeriveKeyFunc) KeybaseOption { - return func(o *kbOptions) { - o.deriveFunc = f - } -} - -// WithSupportedAlgos defines the list of accepted SigningAlgos. -func WithSupportedAlgos(algos []SigningAlgo) KeybaseOption { - return func(o *kbOptions) { - o.supportedAlgos = algos - } -} - -// WithSupportedAlgosLedger defines the list of accepted SigningAlgos compatible with Ledger. -func WithSupportedAlgosLedger(algos []SigningAlgo) KeybaseOption { - return func(o *kbOptions) { - o.supportedAlgosLedger = algos - } -} diff --git a/crypto/keyring/signing_algorithms.go b/crypto/keyring/signing_algorithms.go index 12e000aeb9d..425f49eb859 100644 --- a/crypto/keyring/signing_algorithms.go +++ b/crypto/keyring/signing_algorithms.go @@ -1,26 +1,33 @@ package keyring -// SigningAlgo defines an algorithm to derive key-pairs which can be used for cryptographic signing. -type SigningAlgo string - -const ( - // MultiAlgo implies that a pubkey is a multisignature - MultiAlgo = SigningAlgo("multi") - // Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters. - Secp256k1 = SigningAlgo("secp256k1") - // Ed25519 represents the Ed25519 signature system. - // It is currently not supported for end-user keys (wallets/ledgers). - Ed25519 = SigningAlgo("ed25519") - // Sr25519 represents the Sr25519 signature system. - Sr25519 = SigningAlgo("sr25519") +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/crypto/hd" ) -// IsSupportedAlgorithm returns whether the signing algorithm is in the passed-in list of supported algorithms. -func IsSupportedAlgorithm(supported []SigningAlgo, algo SigningAlgo) bool { - for _, supportedAlgo := range supported { - if algo == supportedAlgo { +type SignatureAlgo interface { + Name() hd.PubKeyType + Derive() hd.DeriveFn + Generate() hd.GenerateFn +} + +func NewSigningAlgoFromString(str string) (SignatureAlgo, error) { + if str != string(hd.Secp256k1.Name()) { + return nil, fmt.Errorf("provided algorithm `%s` is not supported", str) + } + + return hd.Secp256k1, nil +} + +type SigningAlgoList []SignatureAlgo + +func (l SigningAlgoList) Contains(algo SignatureAlgo) bool { + for _, cAlgo := range l { + if cAlgo.Name() == algo.Name() { return true } } + return false } diff --git a/crypto/keyring/signing_algorithms_test.go b/crypto/keyring/signing_algorithms_test.go new file mode 100644 index 00000000000..1293249b09d --- /dev/null +++ b/crypto/keyring/signing_algorithms_test.go @@ -0,0 +1,71 @@ +package keyring + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/hd" +) + +func TestNewSigningAlgoByString(t *testing.T) { + tests := []struct { + name string + algoStr string + isSupported bool + expectedAlgo SignatureAlgo + expectedErr error + }{ + { + "supported algorithm", + "secp256k1", + true, + hd.Secp256k1, + nil, + }, + { + "not supported", + "notsupportedalgo", + false, + nil, + fmt.Errorf("provided algorithm `notsupportedalgo` is not supported"), + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + algorithm, err := NewSigningAlgoFromString(tt.algoStr) + if tt.isSupported { + require.Equal(t, hd.Secp256k1, algorithm) + } else { + require.EqualError(t, err, tt.expectedErr.Error()) + } + }) + } +} + +func TestAltSigningAlgoList_Contains(t *testing.T) { + list := SigningAlgoList{ + hd.Secp256k1, + } + + assert.True(t, list.Contains(hd.Secp256k1)) + assert.False(t, list.Contains(notSupportedAlgo{})) +} + +type notSupportedAlgo struct { +} + +func (n notSupportedAlgo) Name() hd.PubKeyType { + return "notSupported" +} + +func (n notSupportedAlgo) Derive() hd.DeriveFn { + return hd.Secp256k1.Derive() +} + +func (n notSupportedAlgo) Generate() hd.GenerateFn { + return hd.Secp256k1.Generate() +} diff --git a/crypto/keyring/types.go b/crypto/keyring/types.go index fadf47ad588..443896d785b 100644 --- a/crypto/keyring/types.go +++ b/crypto/keyring/types.go @@ -1,6 +1,10 @@ package keyring -import "github.com/tendermint/tendermint/crypto" +import ( + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/crypto/hd" +) // Language is a language to create the BIP 39 mnemonic in. // Currently, only english is supported though. @@ -62,7 +66,7 @@ func (kt KeyType) String() string { type ( // DeriveKeyFunc defines the function to derive a new key from a seed and hd path - DeriveKeyFunc func(mnemonic string, bip39Passphrase, hdPath string, algo SigningAlgo) ([]byte, error) + DeriveKeyFunc func(mnemonic string, bip39Passphrase, hdPath string, algo hd.PubKeyType) ([]byte, error) // PrivKeyGenFunc defines the function to convert derived key bytes to a tendermint private key - PrivKeyGenFunc func(bz []byte, algo SigningAlgo) (crypto.PrivKey, error) + PrivKeyGenFunc func(bz []byte, algo hd.PubKeyType) (crypto.PrivKey, error) ) diff --git a/crypto/keyring/types_test.go b/crypto/keyring/types_test.go index c2cb9723c2c..cf935d906c8 100644 --- a/crypto/keyring/types_test.go +++ b/crypto/keyring/types_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/hd" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -16,7 +16,7 @@ func Test_writeReadLedgerInfo(t *testing.T) { bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A") copy(tmpKey[:], bz) - lInfo := newLedgerInfo("some_name", tmpKey, *hd.NewFundraiserParams(5, sdk.CoinType, 1), Secp256k1) + lInfo := newLedgerInfo("some_name", tmpKey, *hd.NewFundraiserParams(5, sdk.CoinType, 1), hd.Secp256k1Type) assert.Equal(t, TypeLedger, lInfo.GetType()) path, err := lInfo.GetPath() diff --git a/crypto/ledger_mock.go b/crypto/ledger_mock.go index 21fc8a2b0f4..20a45c158bb 100644 --- a/crypto/ledger_mock.go +++ b/crypto/ledger_mock.go @@ -14,7 +14,7 @@ import ( bip39 "github.com/cosmos/go-bip39" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/crypto/ledger_secp256k1.go b/crypto/ledger_secp256k1.go index 2c346d4355a..809512e47d3 100644 --- a/crypto/ledger_secp256k1.go +++ b/crypto/ledger_secp256k1.go @@ -11,7 +11,7 @@ import ( tmcrypto "github.com/tendermint/tendermint/crypto" tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/hd" ) var ( diff --git a/crypto/ledger_test.go b/crypto/ledger_test.go index fbcf9239b49..7d73a36c980 100644 --- a/crypto/ledger_test.go +++ b/crypto/ledger_test.go @@ -9,7 +9,7 @@ import ( tmcrypto "github.com/tendermint/tendermint/crypto" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/server/init.go b/server/init.go index 054cdb46086..cf433aa1184 100644 --- a/server/init.go +++ b/server/init.go @@ -3,7 +3,9 @@ package server import ( "fmt" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -11,31 +13,39 @@ import ( // GenerateCoinKey returns the address of a public key, along with the secret // phrase to recover the private key. func GenerateCoinKey() (sdk.AccAddress, string, error) { - // generate a private key, with recovery phrase - info, secret, err := keyring.NewInMemory().CreateMnemonic( - "name", keyring.English, "pass", keyring.Secp256k1) + info, secret, err := keyring.NewInMemory().NewMnemonic("name", keyring.English, types.FullFundraiserPath, hd.Secp256k1) if err != nil { return sdk.AccAddress([]byte{}), "", err } - addr := info.GetPubKey().Address() - return sdk.AccAddress(addr), secret, nil + return sdk.AccAddress(info.GetPubKey().Address()), secret, nil } // GenerateSaveCoinKey returns the address of a public key, along with the secret // phrase to recover the private key. -func GenerateSaveCoinKey(keybase keyring.Keybase, keyName, keyPass string, overwrite bool) (sdk.AccAddress, string, error) { +func GenerateSaveCoinKey(keybase keyring.Keyring, keyName, keyPass string, overwrite bool) (sdk.AccAddress, string, error) { + exists := false + _, err := keybase.Key(keyName) + if err == nil { + exists = true + } + // ensure no overwrite - if !overwrite { - _, err := keybase.Get(keyName) - if err == nil { + if !overwrite && exists { + return sdk.AccAddress([]byte{}), "", fmt.Errorf( + "key already exists, overwrite is disabled") + } + + // generate a private key, with recovery phrase + if exists { + err = keybase.Delete(keyName) + if err != nil { return sdk.AccAddress([]byte{}), "", fmt.Errorf( - "key already exists, overwrite is disabled") + "failed to overwrite key") } } - // generate a private key, with recovery phrase - info, secret, err := keybase.CreateMnemonic(keyName, keyring.English, keyPass, keyring.Secp256k1) + info, secret, err := keybase.NewMnemonic(keyName, keyring.English, types.FullFundraiserPath, hd.Secp256k1) if err != nil { return sdk.AccAddress([]byte{}), "", err } diff --git a/server/init_test.go b/server/init_test.go index e51b88effde..c2b9b469d17 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -5,9 +5,11 @@ import ( "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/types" ) func TestGenerateCoinKey(t *testing.T) { @@ -16,7 +18,7 @@ func TestGenerateCoinKey(t *testing.T) { require.NoError(t, err) // Test creation - info, err := keyring.NewInMemory().CreateAccount("xxx", mnemonic, "", "012345678", keyring.CreateHDPath(0, 0).String(), keyring.Secp256k1) + info, err := keyring.NewInMemory().NewAccount("xxx", mnemonic, "", hd.NewFundraiserParams(0, types.GetConfig().GetCoinType(), 0).String(), hd.Secp256k1) require.NoError(t, err) require.Equal(t, addr, info.GetAddress()) } @@ -26,19 +28,19 @@ func TestGenerateSaveCoinKey(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := keyring.NewKeyring(t.Name(), "test", dir, nil) + kb, err := keyring.New(t.Name(), "test", dir, nil) require.NoError(t, err) addr, mnemonic, err := server.GenerateSaveCoinKey(kb, "keyname", "012345678", false) require.NoError(t, err) // Test key was actually saved - info, err := kb.Get("keyname") + info, err := kb.Key("keyname") require.NoError(t, err) require.Equal(t, addr, info.GetAddress()) // Test in-memory recovery - info, err = keyring.NewInMemory().CreateAccount("xxx", mnemonic, "", "012345678", keyring.CreateHDPath(0, 0).String(), keyring.Secp256k1) + info, err = keyring.NewInMemory().NewAccount("xxx", mnemonic, "", hd.NewFundraiserParams(0, types.GetConfig().GetCoinType(), 0).String(), hd.Secp256k1) require.NoError(t, err) require.Equal(t, addr, info.GetAddress()) } @@ -48,7 +50,7 @@ func TestGenerateSaveCoinKeyOverwriteFlag(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) - kb, err := keyring.NewKeyring(t.Name(), "test", dir, nil) + kb, err := keyring.New(t.Name(), "test", dir, nil) require.NoError(t, err) keyname := "justakey" diff --git a/x/auth/client/cli/tx_multisign.go b/x/auth/client/cli/tx_multisign.go index 83c068db182..e0d502a47f1 100644 --- a/x/auth/client/cli/tx_multisign.go +++ b/x/auth/client/cli/tx_multisign.go @@ -65,13 +65,13 @@ func makeMultiSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) } inBuf := bufio.NewReader(cmd.InOrStdin()) - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), inBuf) if err != nil { return } - multisigInfo, err := kb.Get(args[1]) + multisigInfo, err := kb.Key(args[1]) if err != nil { return } diff --git a/x/auth/client/tx.go b/x/auth/client/tx.go index ba19d1be598..cc04cd73084 100644 --- a/x/auth/client/tx.go +++ b/x/auth/client/tx.go @@ -97,7 +97,7 @@ func CompleteAndBroadcastTxCLI(txBldr authtypes.TxBuilder, cliCtx context.CLICon _, _ = fmt.Fprintf(os.Stderr, "%s\n\n", json) buf := bufio.NewReader(os.Stdin) - ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf) + ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr) if err != nil || !ok { _, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction") return err @@ -184,7 +184,7 @@ func SignStdTx( var signedStdTx authtypes.StdTx - info, err := txBldr.Keybase().Get(name) + info, err := txBldr.Keybase().Key(name) if err != nil { return signedStdTx, err } diff --git a/x/auth/types/params_test.go b/x/auth/types/params_test.go index f25a4491495..fcec36cb833 100644 --- a/x/auth/types/params_test.go +++ b/x/auth/types/params_test.go @@ -4,8 +4,9 @@ import ( "fmt" "testing" - "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/auth/types" ) func TestParamsEqual(t *testing.T) { diff --git a/x/auth/types/txbuilder.go b/x/auth/types/txbuilder.go index 2c2576841c4..41f33684be6 100644 --- a/x/auth/types/txbuilder.go +++ b/x/auth/types/txbuilder.go @@ -17,7 +17,7 @@ import ( // TxBuilder implements a transaction context created in SDK modules. type TxBuilder struct { txEncoder sdk.TxEncoder - keybase keyring.Keybase + keybase keyring.Keyring accountNumber uint64 sequence uint64 gas uint64 @@ -53,7 +53,7 @@ func NewTxBuilder( // NewTxBuilderFromCLI returns a new initialized TxBuilder with parameters from // the command line using Viper. func NewTxBuilderFromCLI(input io.Reader) TxBuilder { - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), input) + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), input) if err != nil { panic(err) } @@ -90,7 +90,7 @@ func (bldr TxBuilder) Gas() uint64 { return bldr.gas } func (bldr TxBuilder) GasAdjustment() float64 { return bldr.gasAdjustment } // Keybase returns the keybase -func (bldr TxBuilder) Keybase() keyring.Keybase { return bldr.keybase } +func (bldr TxBuilder) Keybase() keyring.Keyring { return bldr.keybase } // SimulateAndExecute returns the option to simulate and then execute the transaction // using the gas from the simulation results @@ -149,7 +149,7 @@ func (bldr TxBuilder) WithGasPrices(gasPrices string) TxBuilder { } // WithKeybase returns a copy of the context with updated keybase. -func (bldr TxBuilder) WithKeybase(keybase keyring.Keybase) TxBuilder { +func (bldr TxBuilder) WithKeybase(keybase keyring.Keyring) TxBuilder { bldr.keybase = keybase return bldr } @@ -272,17 +272,17 @@ func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx StdTx, appendSig } // MakeSignature builds a StdSignature given keybase, key name, passphrase, and a StdSignMsg. -func MakeSignature(keybase keyring.Keybase, name, passphrase string, +func MakeSignature(keybase keyring.Keyring, name, passphrase string, msg StdSignMsg) (sig StdSignature, err error) { if keybase == nil { - keybase, err = keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), os.Stdin) + keybase, err = keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), os.Stdin) if err != nil { return } } - sigBytes, pubkey, err := keybase.Sign(name, passphrase, msg.Bytes()) + sigBytes, pubkey, err := keybase.Sign(name, msg.Bytes()) if err != nil { return } diff --git a/x/genutil/client/cli/gentx.go b/x/genutil/client/cli/gentx.go index 6e2d7f73b60..1e138820de8 100644 --- a/x/genutil/client/cli/gentx.go +++ b/x/genutil/client/cli/gentx.go @@ -93,14 +93,14 @@ func GenTxCmd(ctx *server.Context, cdc *codec.Codec, mbm module.BasicManager, sm } inBuf := bufio.NewReader(cmd.InOrStdin()) - kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), + kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flagClientHome), inBuf) if err != nil { return errors.Wrap(err, "failed to initialize keybase") } name := viper.GetString(flags.FlagName) - key, err := kb.Get(name) + key, err := kb.Key(name) if err != nil { return errors.Wrap(err, "failed to read from keybase") }