diff --git a/CHANGELOG.md b/CHANGELOG.md index 1505a53da4bb..a0356406f9b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,13 +69,14 @@ 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/keys) [\#5735](https://github.com/cosmos/cosmos-sdk/pull/5735) Keyring's `Update()` function is now no-op. +* (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/keys) [\#5866](https://github.com/cosmos/cosmos-sdk/pull/5866) Move `Keyring` and `Keybase` implementations and their associated types from `crypto/keys/` to `crypto/keybase/`. +* (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. ### Features @@ -85,7 +86,7 @@ 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/keys) [\#5844](https://github.com/cosmos/cosmos-sdk/pull/5844) Keybase/Keyring `Sign()` methods no longer decode amino signatures +* (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. ### State Machine Breaking diff --git a/client/keys/add.go b/client/keys/add.go index d8f768cc4f14..530431eb808d 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -291,7 +291,7 @@ func printCreate(cmd *cobra.Command, info keyring.Info, showMnemonic bool, mnemo switch output { case OutputFormatText: cmd.PrintErrln() - printKeyInfo(info, keyring.Bech32KeyOutput) + printKeyInfo(cmd.OutOrStdout(), info, keyring.Bech32KeyOutput) // print mnemonic unless requested not to. if showMnemonic { diff --git a/client/keys/list.go b/client/keys/list.go index 8af7f97154d1..770bde2f1465 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -36,12 +36,12 @@ func runListCmd(cmd *cobra.Command, _ []string) error { return err } + cmd.SetOut(cmd.OutOrStdout()) if !viper.GetBool(flagListNames) { - printInfos(infos) + printInfos(cmd.OutOrStdout(), infos) return nil } - cmd.SetOut(cmd.OutOrStdout()) for _, info := range infos { cmd.Println(info.GetName()) } diff --git a/client/keys/parse.go b/client/keys/parse.go index 79ccca739f0a..5167cd810084 100644 --- a/client/keys/parse.go +++ b/client/keys/parse.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "errors" "fmt" + "io" "strings" "github.com/spf13/cobra" @@ -83,38 +84,39 @@ hexadecimal into bech32 cosmos prefixed format and vice versa. return cmd } -func parseKey(_ *cobra.Command, args []string) error { +func parseKey(cmd *cobra.Command, args []string) error { addr := strings.TrimSpace(args[0]) + outstream := cmd.OutOrStdout() if len(addr) == 0 { return errors.New("couldn't parse empty input") } - if !(runFromBech32(addr) || runFromHex(addr)) { + if !(runFromBech32(outstream, addr) || runFromHex(outstream, addr)) { return errors.New("couldn't find valid bech32 nor hex data") } return nil } // print info from bech32 -func runFromBech32(bech32str string) bool { +func runFromBech32(w io.Writer, bech32str string) bool { hrp, bz, err := bech32.DecodeAndConvert(bech32str) if err != nil { return false } - displayParseKeyInfo(newHexOutput(hrp, bz)) + displayParseKeyInfo(w, newHexOutput(hrp, bz)) return true } // print info from hex -func runFromHex(hexstr string) bool { +func runFromHex(w io.Writer, hexstr string) bool { bz, err := hex.DecodeString(hexstr) if err != nil { return false } - displayParseKeyInfo(newBech32Output(bz)) + displayParseKeyInfo(w, newBech32Output(bz)) return true } -func displayParseKeyInfo(stringer fmt.Stringer) { +func displayParseKeyInfo(w io.Writer, stringer fmt.Stringer) { var out []byte var err error @@ -136,5 +138,5 @@ func displayParseKeyInfo(stringer fmt.Stringer) { panic(err) } - fmt.Println(string(out)) + fmt.Fprintln(w, string(out)) } diff --git a/client/keys/parse_test.go b/client/keys/parse_test.go index 759deb386c75..2f2941b82f90 100644 --- a/client/keys/parse_test.go +++ b/client/keys/parse_test.go @@ -23,7 +23,7 @@ func TestParseKey(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.wantErr, parseKey(nil, tt.args) != nil) + require.Equal(t, tt.wantErr, parseKey(ParseKeyStringCommand(), tt.args) != nil) }) } } diff --git a/client/keys/show.go b/client/keys/show.go index f4e6cc1fbe5d..c423caa1aaea 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -35,10 +35,10 @@ const ( // ShowKeysCmd shows key information for a given key name. func ShowKeysCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "show [name [name...]]", - Short: "Show key info for the given name", - Long: `Return public details of a single local key. If multiple names are -provided, then an ephemeral multisig key will be created under the name "multi" + Use: "show [name_or_address [name_or_address...]]", + Short: "Retrieve key information by name or address", + Long: `Display keys details. If multiple names or addresses are provided, +then an ephemeral multisig key will be created under the name "multi" consisting of all the keys provided by name and multisig threshold.`, Args: cobra.MinimumNArgs(1), RunE: runShowCmd, @@ -62,16 +62,16 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { return err } if len(args) == 1 { - info, err = kb.Get(args[0]) + info, err = fetchKey(kb, args[0]) if err != nil { - return err + return fmt.Errorf("%s is not a valid name or address: %v", args[0], err) } } else { pks := make([]tmcrypto.PubKey, len(args)) - for i, keyName := range args { - info, err := kb.Get(keyName) + for i, keyref := range args { + info, err := fetchKey(kb, keyref) if err != nil { - return err + return fmt.Errorf("%s is not a valid name or address: %v", keyref, err) } pks[i] = info.GetPubKey() @@ -112,11 +112,11 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { switch { case isShowAddr: - printKeyAddress(info, bechKeyOut) + printKeyAddress(cmd.OutOrStdout(), info, bechKeyOut) case isShowPubKey: - printPubKey(info, bechKeyOut) + printPubKey(cmd.OutOrStdout(), info, bechKeyOut) default: - printKeyInfo(info, bechKeyOut) + printKeyInfo(cmd.OutOrStdout(), info, bechKeyOut) } if isShowDevice { @@ -142,6 +142,22 @@ 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) + if err != nil { + accAddr, err := sdk.AccAddressFromBech32(keyref) + if err != nil { + return info, err + } + + info, err = kb.GetByAddress(accAddr) + if err != nil { + return info, errors.New("key not found") + } + } + return info, nil +} + func validateMultisigThreshold(k, nKeys int) error { if k <= 0 { return fmt.Errorf("threshold must be a positive integer") diff --git a/client/keys/show_test.go b/client/keys/show_test.go index f7b35cb3d3e0..fe1070e1ffb7 100644 --- a/client/keys/show_test.go +++ b/client/keys/show_test.go @@ -38,8 +38,8 @@ func Test_runShowCmd(t *testing.T) { runningUnattended := isRunningUnattended() cmd := ShowKeysCmd() mockIn, _, _ := tests.ApplyMockIO(cmd) - require.EqualError(t, runShowCmd(cmd, []string{"invalid"}), "The specified item could not be found in the keyring") - require.EqualError(t, runShowCmd(cmd, []string{"invalid1", "invalid2"}), "The specified item could not be found in the keyring") + require.EqualError(t, runShowCmd(cmd, []string{"invalid"}), "invalid is not a valid name or address: decoding bech32 failed: invalid bech32 string length 7") + require.EqualError(t, runShowCmd(cmd, []string{"invalid1", "invalid2"}), "invalid1 is not a valid name or address: decoding bech32 failed: invalid index of 1") // Prepare a key base // Now add a temporary keybase @@ -78,7 +78,13 @@ func Test_runShowCmd(t *testing.T) { if runningUnattended { mockIn.Reset("testpass1\n") } + + // try fetch by name require.NoError(t, runShowCmd(cmd, []string{fakeKeyName1})) + // try fetch by addr + info, err := kb.Get(fakeKeyName1) + require.NoError(t, err) + require.NoError(t, runShowCmd(cmd, []string{info.GetAddress().String()})) // Now try multisig key - set bech to acc viper.Set(FlagBechPrefix, sdk.PrefixAccount) diff --git a/client/keys/utils.go b/client/keys/utils.go index a6dc78ecb183..4581d6861603 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -2,12 +2,13 @@ package keys import ( "fmt" + "io" "path/filepath" "github.com/99designs/keyring" "github.com/spf13/viper" "github.com/tendermint/tendermint/libs/cli" - yaml "gopkg.in/yaml.v2" + "gopkg.in/yaml.v2" "github.com/cosmos/cosmos-sdk/client/flags" cryptokeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -37,7 +38,7 @@ func getLazyKeyBaseFromDir(rootDir string, opts ...cryptokeyring.KeybaseOption) return cryptokeyring.New(defaultKeyDBName, filepath.Join(rootDir, "keys"), opts...), nil } -func printKeyInfo(keyInfo cryptokeyring.Info, bechKeyOut bechKeyOutFn) { +func printKeyInfo(w io.Writer, keyInfo cryptokeyring.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(keyInfo) if err != nil { panic(err) @@ -45,7 +46,7 @@ func printKeyInfo(keyInfo cryptokeyring.Info, bechKeyOut bechKeyOutFn) { switch viper.Get(cli.OutputFlag) { case OutputFormatText: - printTextInfos([]cryptokeyring.KeyOutput{ko}) + printTextInfos(w, []cryptokeyring.KeyOutput{ko}) case OutputFormatJSON: var out []byte @@ -59,11 +60,11 @@ func printKeyInfo(keyInfo cryptokeyring.Info, bechKeyOut bechKeyOutFn) { panic(err) } - fmt.Println(string(out)) + fmt.Fprintln(w, string(out)) } } -func printInfos(infos []cryptokeyring.Info) { +func printInfos(w io.Writer, infos []cryptokeyring.Info) { kos, err := cryptokeyring.Bech32KeysOutput(infos) if err != nil { panic(err) @@ -71,7 +72,7 @@ func printInfos(infos []cryptokeyring.Info) { switch viper.Get(cli.OutputFlag) { case OutputFormatText: - printTextInfos(kos) + printTextInfos(w, kos) case OutputFormatJSON: var out []byte @@ -86,34 +87,34 @@ func printInfos(infos []cryptokeyring.Info) { if err != nil { panic(err) } - fmt.Printf("%s", out) + fmt.Fprintf(w, "%s", out) } } -func printTextInfos(kos []cryptokeyring.KeyOutput) { +func printTextInfos(w io.Writer, kos []cryptokeyring.KeyOutput) { out, err := yaml.Marshal(&kos) if err != nil { panic(err) } - fmt.Println(string(out)) + fmt.Fprintln(w, string(out)) } -func printKeyAddress(info cryptokeyring.Info, bechKeyOut bechKeyOutFn) { +func printKeyAddress(w io.Writer, info cryptokeyring.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(info) if err != nil { panic(err) } - fmt.Println(ko.Address) + fmt.Fprintln(w, ko.Address) } -func printPubKey(info cryptokeyring.Info, bechKeyOut bechKeyOutFn) { +func printPubKey(w io.Writer, info cryptokeyring.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(info) if err != nil { panic(err) } - fmt.Println(ko.PubKey) + fmt.Fprintln(w, ko.PubKey) } func isRunningUnattended() bool { diff --git a/crypto/keyring/db_keybase.go b/crypto/keyring/db_keybase.go index 4a09cc4afb24..0ed7cbfd927a 100644 --- a/crypto/keyring/db_keybase.go +++ b/crypto/keyring/db_keybase.go @@ -1,6 +1,7 @@ package keyring import ( + "encoding/hex" "fmt" "reflect" "strings" @@ -126,7 +127,7 @@ func (kb dbKeybase) Get(name string) (Info, error) { // GetByAddress returns Info based on a provided AccAddress. An error is returned // if the address does not exist. func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) { - ik, err := kb.db.Get(addrKey(address)) + ik, err := kb.db.Get(addrStringKey(address)) if err != nil { return nil, err } @@ -344,7 +345,7 @@ func (kb dbKeybase) Delete(name, passphrase string, skipPass bool) error { batch := kb.db.NewBatch() defer batch.Close() - batch.Delete(addrKey(info.GetAddress())) + batch.Delete(addrStringKey(info.GetAddress())) batch.Delete(infoKey(name)) return batch.WriteSync() @@ -414,13 +415,18 @@ func (kb dbKeybase) writeInfo(name string, info Info) { kb.db.SetSync(key, serializedInfo) // store a pointer to the infokey by address for fast lookup - kb.db.SetSync(addrKey(info.GetAddress()), key) + kb.db.SetSync(addrStringKey(info.GetAddress()), key) } -func addrKey(address types.AccAddress) []byte { +// this is to be removed together with dbKeybase and the old Keybase interface +func addrStringKey(address types.AccAddress) []byte { return []byte(fmt.Sprintf("%s.%s", address.String(), addressSuffix)) } +func addrHexKey(address types.AccAddress) []byte { + return []byte(fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix)) +} + func infoKey(name string) []byte { return []byte(fmt.Sprintf("%s.%s", name, infoSuffix)) } diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index 1752c099ba16..c48e0ba4a9c7 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -178,7 +178,7 @@ func (kb keyringKeybase) Get(name string) (Info, error) { // 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(addrKey(address))) + ik, err := kb.db.Get(string(addrHexKey(address))) if err != nil { return nil, err } @@ -313,7 +313,7 @@ func (kb keyringKeybase) Import(name string, armor string) error { kb.writeInfo(name, info) err = kb.db.Set(keyring.Item{ - Key: string(addrKey(info.GetAddress())), + Key: string(addrHexKey(info.GetAddress())), Data: infoKey(name), }) if err != nil { @@ -401,7 +401,7 @@ func (kb keyringKeybase) Delete(name, _ string, _ bool) error { return err } - err = kb.db.Remove(string(addrKey(info.GetAddress()))) + err = kb.db.Remove(string(addrHexKey(info.GetAddress()))) if err != nil { return err } @@ -454,7 +454,7 @@ func (kb keyringKeybase) writeInfo(name string, info Info) { } err = kb.db.Set(keyring.Item{ - Key: string(addrKey(info.GetAddress())), + Key: string(addrHexKey(info.GetAddress())), Data: key, }) if err != nil {