diff --git a/Gopkg.lock b/Gopkg.lock index 7ea599db9273..430d72894279 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -513,12 +513,12 @@ version = "v0.9.0" [[projects]] - digest = "1:fca24169988a61ea725d1326de30910d8049fe68bcbc194d28803f9a76dda380" + digest = "1:a2b34d1436ac24a0db176f84c015d80438602eeec12f2f8358bb72749711ab52" name = "github.com/zondax/ledger-cosmos-go" packages = ["."] pruneopts = "UT" - revision = "69fdb8ce5e5b9d9c3b22b9248e117b231d4f06dd" - version = "v0.9.7" + revision = "e2f595b3b7b222e1cbe9daf89e73e4dcab02d54c" + version = "v0.9.8" [[projects]] digest = "1:f8e4c0b959174a1fa5946b12f1f2ac7ea5651bef20a9e4a8dac55dbffcaa6cd6" diff --git a/Gopkg.toml b/Gopkg.toml index e24077be58dd..e32c9ee7f3de 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -44,7 +44,7 @@ [[constraint]] name = "github.com/zondax/ledger-cosmos-go" - version = "=v0.9.7" + version = "=v0.9.8" ## deps without releases: diff --git a/PENDING.md b/PENDING.md index f92c1e594bbc..ad4f173c55c4 100644 --- a/PENDING.md +++ b/PENDING.md @@ -44,6 +44,7 @@ decoded automatically. ### Gaia CLI +* [\#3670] CLI support for showing bech32 addresses in Ledger devices * [\#3711] Update `tx sign` to use `--from` instead of the deprecated `--name` CLI flag. diff --git a/client/keys/show.go b/client/keys/show.go index 04981fd10cbb..1dbd8c34a7e6 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -1,20 +1,20 @@ package keys import ( + "errors" "fmt" - "github.com/tendermint/tendermint/crypto" - + "github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto/keys" - - "errors" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" "github.com/spf13/viper" + + tmcrypto "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/libs/cli" - - sdk "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -24,6 +24,8 @@ const ( FlagPublicKey = "pubkey" // FlagBechPrefix defines a desired Bech32 prefix encoding for a key. FlagBechPrefix = "bech" + // FlagBechPrefix defines a desired Bech32 prefix encoding for a key. + FlagDevice = "device" flagMultiSigThreshold = "multisig-threshold" defaultMultiSigKeyName = "multi" @@ -33,13 +35,16 @@ var _ keys.Info = (*multiSigKey)(nil) type multiSigKey struct { name string - key crypto.PubKey + key tmcrypto.PubKey } func (m multiSigKey) GetName() string { return m.name } func (m multiSigKey) GetType() keys.KeyType { return keys.TypeLocal } -func (m multiSigKey) GetPubKey() crypto.PubKey { return m.key } +func (m multiSigKey) GetPubKey() tmcrypto.PubKey { return m.key } func (m multiSigKey) GetAddress() sdk.AccAddress { return sdk.AccAddress(m.key.Address()) } +func (m multiSigKey) GetPath() (*hd.BIP44Params, error) { + return nil, fmt.Errorf("BIP44 Paths are not available for this type") +} func showKeysCmd() *cobra.Command { cmd := &cobra.Command{ @@ -53,6 +58,7 @@ func showKeysCmd() *cobra.Command { cmd.Flags().String(FlagBechPrefix, sdk.PrefixAccount, "The Bech32 prefix encoding for a key (acc|val|cons)") cmd.Flags().BoolP(FlagAddress, "a", false, "output the address only (overrides --output)") cmd.Flags().BoolP(FlagPublicKey, "p", false, "output the public key only (overrides --output)") + cmd.Flags().BoolP(FlagDevice, "d", false, "output the address in the device") cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures") return cmd @@ -67,7 +73,7 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { return err } } else { - pks := make([]crypto.PubKey, len(args)) + pks := make([]tmcrypto.PubKey, len(args)) for i, keyName := range args { info, err := GetKeyInfo(keyName) if err != nil { @@ -90,6 +96,7 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { isShowAddr := viper.GetBool(FlagAddress) isShowPubKey := viper.GetBool(FlagPublicKey) + isShowDevice := viper.GetBool(FlagDevice) isOutputSet := false tmp := cmd.Flag(cli.OutputFlag) @@ -119,6 +126,26 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { printKeyInfo(info, bechKeyOut) } + if isShowDevice { + if isShowPubKey { + return fmt.Errorf("the device flag (-d) can only be used for addresses not pubkeys") + } + if viper.GetString(FlagBechPrefix) != "acc" { + return fmt.Errorf("the device flag (-d) can only be used for accounts") + } + // Override and show in the device + if info.GetType() != keys.TypeLedger { + return fmt.Errorf("the device flag (-d) can only be used for accounts stored in devices") + } + + hdpath, err := info.GetPath() + if err != nil { + return nil + } + + return crypto.LedgerShowAddress(*hdpath, info.GetPubKey()) + } + return nil } diff --git a/client/keys/show_test.go b/client/keys/show_test.go index 8f67d1d6ef1f..bb44fd47107b 100644 --- a/client/keys/show_test.go +++ b/client/keys/show_test.go @@ -79,6 +79,21 @@ func Test_runShowCmd(t *testing.T) { err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) assert.NoError(t, err) + // Now try multisig key - set bech to acc + threshold=2 + viper.Set(FlagBechPrefix, "acc") + viper.Set(FlagDevice, true) + viper.Set(flagMultiSigThreshold, 2) + err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) + assert.EqualError(t, err, "the device flag (-d) can only be used for accounts stored in devices") + + viper.Set(FlagBechPrefix, "val") + err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) + assert.EqualError(t, err, "the device flag (-d) can only be used for accounts") + + viper.Set(FlagPublicKey, true) + err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) + assert.EqualError(t, err, "the device flag (-d) can only be used for addresses not pubkeys") + // TODO: Capture stdout and compare } diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 6374eaafa330..09829e1dd833 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -74,9 +74,9 @@ func TestCreateLedger(t *testing.T) { pk, err = sdk.Bech32ifyAccPub(pubKey) assert.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk) - linfo := restoredKey.(ledgerInfo) - assert.Equal(t, "44'/118'/3'/0/1", linfo.GetPath().String()) - + path, err := restoredKey.GetPath() + assert.NoError(t, err) + assert.Equal(t, "44'/118'/3'/0/1", path.String()) } // TestKeyManagement makes sure we can manipulate these keys well diff --git a/crypto/keys/types.go b/crypto/keys/types.go index 8a4ff7fc26cd..09e732aa177e 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -1,10 +1,12 @@ package keys import ( - "github.com/tendermint/tendermint/crypto" + "fmt" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/types" + + "github.com/tendermint/tendermint/crypto" ) // Keybase exposes operations on a generic keystore @@ -82,6 +84,8 @@ type Info interface { GetPubKey() crypto.PubKey // Address GetAddress() types.AccAddress + // Bip44 Path + GetPath() (*hd.BIP44Params, error) } var _ Info = &localInfo{} @@ -119,6 +123,10 @@ func (i localInfo) GetAddress() types.AccAddress { return i.PubKey.Address().Bytes() } +func (i localInfo) GetPath() (*hd.BIP44Params, error) { + return nil, fmt.Errorf("BIP44 Paths are not available for this type") +} + // ledgerInfo is the public information about a Ledger key type ledgerInfo struct { Name string `json:"name"` @@ -150,8 +158,9 @@ func (i ledgerInfo) GetAddress() types.AccAddress { return i.PubKey.Address().Bytes() } -func (i ledgerInfo) GetPath() hd.BIP44Params { - return i.Path +func (i ledgerInfo) GetPath() (*hd.BIP44Params, error) { + tmp := i.Path + return &tmp, nil } // offlineInfo is the public information about an offline key @@ -183,6 +192,10 @@ func (i offlineInfo) GetAddress() types.AccAddress { return i.PubKey.Address().Bytes() } +func (i offlineInfo) GetPath() (*hd.BIP44Params, error) { + return nil, fmt.Errorf("BIP44 Paths are not available for this type") +} + // encoding info func writeInfo(i Info) []byte { return cdc.MustMarshalBinaryLengthPrefixed(i) diff --git a/crypto/keys/types_test.go b/crypto/keys/types_test.go index 621395b0c236..3124e484cb85 100644 --- a/crypto/keys/types_test.go +++ b/crypto/keys/types_test.go @@ -20,7 +20,10 @@ func Test_writeReadLedgerInfo(t *testing.T) { tmpKey, *hd.NewFundraiserParams(5, 1)} assert.Equal(t, TypeLedger, lInfo.GetType()) - assert.Equal(t, "44'/118'/5'/0/1", lInfo.GetPath().String()) + + path, err := lInfo.GetPath() + assert.NoError(t, err) + assert.Equal(t, "44'/118'/5'/0/1", path.String()) assert.Equal(t, "cosmospub1addwnpepqddddqg2glc8x4fl7vxjlnr7p5a3czm5kcdp4239sg6yqdc4rc2r5wmxv8p", types.MustBech32ifyAccPub(lInfo.GetPubKey())) @@ -36,5 +39,8 @@ func Test_writeReadLedgerInfo(t *testing.T) { assert.Equal(t, lInfo.GetType(), restoredInfo.GetType()) assert.Equal(t, lInfo.GetPubKey(), restoredInfo.GetPubKey()) - assert.Equal(t, lInfo.GetPath(), restoredInfo.(ledgerInfo).GetPath()) + restoredPath, err := restoredInfo.GetPath() + assert.NoError(t, err) + + assert.Equal(t, path, restoredPath) } diff --git a/crypto/ledger_mock.go b/crypto/ledger_mock.go index df7cf6d4e1f4..75e6941da12a 100644 --- a/crypto/ledger_mock.go +++ b/crypto/ledger_mock.go @@ -3,10 +3,12 @@ package crypto import ( + "fmt" + "github.com/btcsuite/btcd/btcec" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/tests" - "github.com/cosmos/go-bip39" + bip39 "github.com/cosmos/go-bip39" "github.com/pkg/errors" secp256k1 "github.com/tendermint/btcd/btcec" "github.com/tendermint/tendermint/crypto" @@ -77,3 +79,9 @@ func (mock LedgerSECP256K1Mock) SignSECP256K1(derivationPath []uint32, message [ sig2 := btcec.Signature{R: sig.R, S: sig.S} return sig2.Serialize(), nil } + +// ShowAddressSECP256K1 shows the address for the corresponding bip32 derivation path +func (mock LedgerSECP256K1Mock) ShowAddressSECP256K1(bip32Path []uint32, hrp string) error { + fmt.Printf("Request to show address for %v at %v", hrp, bip32Path) + return nil +} diff --git a/crypto/ledger_secp256k1.go b/crypto/ledger_secp256k1.go index c19c82fd8aa6..fae0c1569b96 100644 --- a/crypto/ledger_secp256k1.go +++ b/crypto/ledger_secp256k1.go @@ -5,12 +5,14 @@ import ( "os" "github.com/btcsuite/btcd/btcec" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" + tmbtcec "github.com/tendermint/btcd/btcec" tmcrypto "github.com/tendermint/tendermint/crypto" tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1" - - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" ) var ( @@ -31,6 +33,7 @@ type ( Close() error GetPublicKeySECP256K1([]uint32) ([]byte, error) SignSECP256K1([]uint32, []byte) ([]byte, error) + ShowAddressSECP256K1([]uint32, string) error } // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we @@ -61,6 +64,26 @@ func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params) (tmcrypto.PrivKey, error) { return PrivKeyLedgerSecp256k1{pubKey, path}, nil } +// LedgerShowAddress triggers a ledger device to show the corresponding address. +func LedgerShowAddress(path hd.BIP44Params, expectedPubKey tmcrypto.PubKey) error { + device, err := getLedgerDevice() + if err != nil { + return err + } + defer warnIfErrors(device.Close) + + pubKey, err := getPubKey(device, path) + if err != nil { + return err + } + + if pubKey != expectedPubKey { + return fmt.Errorf("pubkey does not match, Check this is the same device") + } + + return device.ShowAddressSECP256K1(path.DerivationPath(), types.Bech32PrefixAccAddr) +} + // PubKey returns the cached public key. func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey { return pkl.CachedPubKey