Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: CLI support for showing bech32 addresses in Ledger devices #3670

Merged
merged 8 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

[[constraint]]
name = "github.com/zondax/ledger-cosmos-go"
version = "=v0.9.7"
version = "=v0.9.8"
jleni marked this conversation as resolved.
Show resolved Hide resolved

## deps without releases:

Expand Down
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
45 changes: 36 additions & 9 deletions client/keys/show.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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"
Expand All @@ -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{
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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" {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("the device flag (-d) can only be used for accounts")
}
// Override and show in the device
if info.GetType() != keys.TypeLedger {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
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
}

Expand Down
15 changes: 15 additions & 0 deletions client/keys/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
6 changes: 3 additions & 3 deletions crypto/keys/keybase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 16 additions & 3 deletions crypto/keys/types.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -82,6 +84,8 @@ type Info interface {
GetPubKey() crypto.PubKey
// Address
GetAddress() types.AccAddress
// Bip44 Path
GetPath() (*hd.BIP44Params, error)
}

var _ Info = &localInfo{}
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 8 additions & 2 deletions crypto/keys/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand All @@ -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)
}
10 changes: 9 additions & 1 deletion crypto/ledger_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
27 changes: 25 additions & 2 deletions crypto/ledger_secp256k1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down