github.com/Finschia/finschia-sdk@v0.48.1/client/keys/show.go (about)

     1  package keys
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/Finschia/ostracon/libs/cli"
     8  	"github.com/spf13/cobra"
     9  
    10  	"github.com/Finschia/finschia-sdk/client"
    11  	"github.com/Finschia/finschia-sdk/crypto/keyring"
    12  	"github.com/Finschia/finschia-sdk/crypto/keys/multisig"
    13  	"github.com/Finschia/finschia-sdk/crypto/ledger"
    14  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    15  	sdk "github.com/Finschia/finschia-sdk/types"
    16  	sdkerr "github.com/Finschia/finschia-sdk/types/errors"
    17  )
    18  
    19  const (
    20  	// FlagAddress is the flag for the user's address on the command line.
    21  	FlagAddress = "address"
    22  	// FlagPublicKey represents the user's public key on the command line.
    23  	FlagPublicKey = "pubkey"
    24  	// FlagBechPrefix defines a desired Bech32 prefix encoding for a key.
    25  	FlagBechPrefix = "bech"
    26  	// FlagDevice indicates that the information should be shown in the device
    27  	FlagDevice = "device"
    28  
    29  	flagMultiSigThreshold = "multisig-threshold"
    30  
    31  	defaultMultiSigKeyName = "multi"
    32  )
    33  
    34  // ShowKeysCmd shows key information for a given key name.
    35  func ShowKeysCmd() *cobra.Command {
    36  	cmd := &cobra.Command{
    37  		Use:   "show [name_or_address [name_or_address...]]",
    38  		Short: "Retrieve key information by name or address",
    39  		Long: `Display keys details. If multiple names or addresses are provided,
    40  then an ephemeral multisig key will be created under the name "multi"
    41  consisting of all the keys provided by name and multisig threshold.`,
    42  		Args: cobra.MinimumNArgs(1),
    43  		RunE: runShowCmd,
    44  	}
    45  	f := cmd.Flags()
    46  	f.String(FlagBechPrefix, sdk.PrefixAccount, "The Bech32 prefix encoding for a key (acc|val|cons)")
    47  	f.BoolP(FlagAddress, "a", false, "Output the address only (overrides --output)")
    48  	f.BoolP(FlagPublicKey, "p", false, "Output the public key only (overrides --output)")
    49  	f.BoolP(FlagDevice, "d", false, "Output the address in a ledger device")
    50  	f.Int(flagMultiSigThreshold, 1, "K out of N required signatures")
    51  
    52  	return cmd
    53  }
    54  
    55  func runShowCmd(cmd *cobra.Command, args []string) (err error) {
    56  	var info keyring.Info
    57  	clientCtx, err := client.GetClientQueryContext(cmd)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	if len(args) == 1 {
    63  		info, err = fetchKey(clientCtx.Keyring, args[0])
    64  		if err != nil {
    65  			return fmt.Errorf("%s is not a valid name or address: %v", args[0], err)
    66  		}
    67  		if info.GetType() == keyring.TypeMulti {
    68  			info, err = keyring.NewMultiInfo(info.GetName(), info.GetPubKey())
    69  			if err != nil {
    70  				return err
    71  			}
    72  		}
    73  	} else {
    74  		pks := make([]cryptotypes.PubKey, len(args))
    75  		for i, keyref := range args {
    76  			info, err := fetchKey(clientCtx.Keyring, keyref)
    77  			if err != nil {
    78  				return fmt.Errorf("%s is not a valid name or address: %v", keyref, err)
    79  			}
    80  
    81  			pks[i] = info.GetPubKey()
    82  		}
    83  
    84  		multisigThreshold, _ := cmd.Flags().GetInt(flagMultiSigThreshold)
    85  		err = validateMultisigThreshold(multisigThreshold, len(args))
    86  		if err != nil {
    87  			return err
    88  		}
    89  
    90  		multikey := multisig.NewLegacyAminoPubKey(multisigThreshold, pks)
    91  		info, err = keyring.NewMultiInfo(defaultMultiSigKeyName, multikey)
    92  		if err != nil {
    93  			return err
    94  		}
    95  	}
    96  
    97  	isShowAddr, _ := cmd.Flags().GetBool(FlagAddress)
    98  	isShowPubKey, _ := cmd.Flags().GetBool(FlagPublicKey)
    99  	isShowDevice, _ := cmd.Flags().GetBool(FlagDevice)
   100  
   101  	isOutputSet := false
   102  	tmp := cmd.Flag(cli.OutputFlag)
   103  	if tmp != nil {
   104  		isOutputSet = tmp.Changed
   105  	}
   106  
   107  	if isShowAddr && isShowPubKey {
   108  		return errors.New("cannot use both --address and --pubkey at once")
   109  	}
   110  
   111  	if isOutputSet && (isShowAddr || isShowPubKey) {
   112  		return errors.New("cannot use --output with --address or --pubkey")
   113  	}
   114  
   115  	bechPrefix, _ := cmd.Flags().GetString(FlagBechPrefix)
   116  	bechKeyOut, err := getBechKeyOut(bechPrefix)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	if isOutputSet {
   122  		clientCtx.OutputFormat, _ = cmd.Flags().GetString(cli.OutputFlag)
   123  	}
   124  
   125  	switch {
   126  	case isShowAddr, isShowPubKey:
   127  		ko, err := bechKeyOut(info)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		out := ko.Address
   132  		if isShowPubKey {
   133  			out = ko.PubKey
   134  		}
   135  		fmt.Fprintln(cmd.OutOrStdout(), out)
   136  	default:
   137  		printKeyInfo(cmd.OutOrStdout(), info, bechKeyOut, clientCtx.OutputFormat)
   138  	}
   139  
   140  	if isShowDevice {
   141  		if isShowPubKey {
   142  			return fmt.Errorf("the device flag (-d) can only be used for addresses not pubkeys")
   143  		}
   144  		if bechPrefix != "acc" {
   145  			return fmt.Errorf("the device flag (-d) can only be used for accounts")
   146  		}
   147  
   148  		// Override and show in the device
   149  		if info.GetType() != keyring.TypeLedger {
   150  			return fmt.Errorf("the device flag (-d) can only be used for accounts stored in devices")
   151  		}
   152  
   153  		hdpath, err := info.GetPath()
   154  		if err != nil {
   155  			return nil
   156  		}
   157  
   158  		return ledger.ShowAddress(*hdpath, info.GetPubKey(), sdk.GetConfig().GetBech32AccountAddrPrefix())
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  func fetchKey(kb keyring.Keyring, keyref string) (keyring.Info, error) {
   165  	// firstly check if the keyref is a key name of a key registered in a keyring.
   166  	info, err := kb.Key(keyref)
   167  	// if the key is not there or if we have a problem with a keyring itself then we move to a
   168  	// fallback: searching for key by address.
   169  	if err == nil || !sdkerr.IsOf(err, sdkerr.ErrIO, sdkerr.ErrKeyNotFound) {
   170  		return info, err
   171  	}
   172  	accAddr, err := sdk.AccAddressFromBech32(keyref)
   173  	if err != nil {
   174  		return info, err
   175  	}
   176  
   177  	info, err = kb.KeyByAddress(accAddr)
   178  	return info, sdkerr.Wrap(err, "Invalid key")
   179  }
   180  
   181  func validateMultisigThreshold(k, nKeys int) error {
   182  	if k <= 0 {
   183  		return fmt.Errorf("threshold must be a positive integer")
   184  	}
   185  	if nKeys < k {
   186  		return fmt.Errorf(
   187  			"threshold k of n multisignature: %d < %d", nKeys, k)
   188  	}
   189  	return nil
   190  }
   191  
   192  func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) {
   193  	switch bechPrefix {
   194  	case sdk.PrefixAccount:
   195  		return keyring.MkAccKeyOutput, nil
   196  	case sdk.PrefixValidator:
   197  		return keyring.MkValKeyOutput, nil
   198  	case sdk.PrefixConsensus:
   199  		return keyring.MkConsKeyOutput, nil
   200  	}
   201  
   202  	return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix)
   203  }