Skip to content

Commit

Permalink
Merge PR #3670: CLI support for showing bech32 addresses in Ledger de…
Browse files Browse the repository at this point in the history
…vices
  • Loading branch information
jleni authored and cwgoes committed Feb 25, 2019
1 parent c96d8f3 commit 3eb0acd
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 24 deletions.
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"

## 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" {
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
}

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

0 comments on commit 3eb0acd

Please sign in to comment.