From 1306a25e42a3dd9e0642b8dbb47205e44b7ce227 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 30 Apr 2019 10:58:21 +0200 Subject: [PATCH] Adding support for Ledger Cosmos App v1.5 (#4227) This PR adds support for the latest version of the Cosmos App (v.1.5). The app is not been released yet by Ledger but the PR is backwards compatible. We can later remove backwards compatibility and enforce v1.5 only. When creating a new account, `gaiacli` now shows the account/index and address in the device and requires user confirmation. Related PRs: https://github.com/cosmos/ledger-cosmos-go/pull/3 https://github.com/cosmos/ledger-cosmos-go/pull/4 https://github.com/cosmos/ledger-cosmos-go/pull/5 https://github.com/cosmos/ledger-cosmos-go/pull/6 Changes in the app can be found here: https://github.com/LedgerHQ/ledger-app-cosmos/pull/5 --- .../gaiacli/4227-Support-for-Ledger-App-1.5 | 1 + client/keys/add.go | 5 +- client/keys/show.go | 4 +- crypto/keys/keybase.go | 7 +- crypto/keys/keybase_test.go | 4 +- crypto/keys/lazy_keybase.go | 4 +- crypto/keys/types.go | 2 +- crypto/ledger_mock.go | 34 ++++- crypto/ledger_secp256k1.go | 103 +++++++++++---- crypto/ledger_test.go | 122 ++++++++++++++++-- go.mod | 8 +- go.sum | 10 +- x/mint/client/module_client.go | 11 +- 13 files changed, 254 insertions(+), 61 deletions(-) create mode 100644 .pending/improvements/gaiacli/4227-Support-for-Ledger-App-1.5 diff --git a/.pending/improvements/gaiacli/4227-Support-for-Ledger-App-1.5 b/.pending/improvements/gaiacli/4227-Support-for-Ledger-App-1.5 new file mode 100644 index 000000000..97bc1e81b --- /dev/null +++ b/.pending/improvements/gaiacli/4227-Support-for-Ledger-App-1.5 @@ -0,0 +1 @@ +#4227 Support for Ledger App v1.5 diff --git a/client/keys/add.go b/client/keys/add.go index fd3c1b747..983636e14 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -171,9 +171,10 @@ func runAddCmd(_ *cobra.Command, args []string) error { account := uint32(viper.GetInt(flagAccount)) index := uint32(viper.GetInt(flagIndex)) - // If we're using ledger, only thing we need is the path. So generate key and we're done. + // If we're using ledger, only thing we need is the path and the bech32 prefix. if viper.GetBool(client.FlagUseLedger) { - info, err := kb.CreateLedger(name, keys.Secp256k1, account, index) + bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix() + info, err := kb.CreateLedger(name, keys.Secp256k1, bech32PrefixAccAddr, account, index) if err != nil { return err } diff --git a/client/keys/show.go b/client/keys/show.go index 131b45f5e..b5316dc5f 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -25,7 +25,7 @@ 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 indicates that the information should be shown in the device FlagDevice = "device" flagMultiSigThreshold = "multisig-threshold" @@ -48,7 +48,7 @@ consisting of all the keys provided by name and multisig threshold.`, 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().BoolP(FlagDevice, "d", false, "Output the address in a ledger device") cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures") cmd.Flags().BoolP(flagShowMultiSig, "m", false, "Output multisig pubkey constituents, threshold, and weights") cmd.Flags().Bool(client.FlagIndentResponse, false, "Add indent to JSON response") diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index e4ff5651c..a72b43238 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -137,18 +137,19 @@ func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string // CreateLedger creates a new locally-stored reference to a Ledger keypair // It returns the created key info and an error if the Ledger could not be queried -func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (Info, error) { +func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (Info, error) { if algo != Secp256k1 { return nil, ErrUnsupportedSigningAlgo } hdPath := hd.NewFundraiserParams(account, index) - priv, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath) + priv, _, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath, hrp) if err != nil { return nil, err } pub := priv.PubKey() + // Note: Once Cosmos App v1.3.1 is compulsory, it could be possible to check that pubkey and addr match return kb.writeLedgerKey(name, pub, *hdPath), nil } @@ -246,7 +247,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t case ledgerInfo: linfo := info.(ledgerInfo) - priv, err = crypto.NewPrivKeyLedgerSecp256k1(linfo.Path) + priv, err = crypto.NewPrivKeyLedgerSecp256k1Unsafe(linfo.Path) if err != nil { return } diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index e5613e302..67b1f1c0a 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -38,7 +38,7 @@ func TestCreateAccountInvalidMnemonic(t *testing.T) { func TestCreateLedgerUnsupportedAlgo(t *testing.T) { kb := NewInMemory() - _, err := kb.CreateLedger("some_account", Ed25519, 0, 1) + _, err := kb.CreateLedger("some_account", Ed25519, "cosmos", 0, 1) assert.Error(t, err) assert.Equal(t, "unsupported signing algo: only secp256k1 is supported", err.Error()) } @@ -50,7 +50,7 @@ func TestCreateLedger(t *testing.T) { // test_cover does not compile some dependencies so ledger is disabled // test_unit may add a ledger mock // both cases are acceptable - ledger, err := kb.CreateLedger("some_account", Secp256k1, 3, 1) + ledger, err := kb.CreateLedger("some_account", Secp256k1, "cosmos", 3, 1) if err != nil { assert.Error(t, err) diff --git a/crypto/keys/lazy_keybase.go b/crypto/keys/lazy_keybase.go index 6f0b68523..6922bd152 100644 --- a/crypto/keys/lazy_keybase.go +++ b/crypto/keys/lazy_keybase.go @@ -106,14 +106,14 @@ func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, return newDbKeybase(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params) } -func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (info Info, err error) { +func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() - return newDbKeybase(db).CreateLedger(name, algo, account, index) + return newDbKeybase(db).CreateLedger(name, algo, hrp, account, index) } func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) { diff --git a/crypto/keys/types.go b/crypto/keys/types.go index 1459e1ae0..5389f9368 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -35,7 +35,7 @@ type Keybase interface { Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) // CreateLedger creates, stores, and returns a new Ledger key reference - CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (info Info, err error) + CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) // CreateOffline creates, stores, and returns a new offline key reference CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) diff --git a/crypto/ledger_mock.go b/crypto/ledger_mock.go index b9f1048cf..159d9c2d3 100644 --- a/crypto/ledger_mock.go +++ b/crypto/ledger_mock.go @@ -4,15 +4,17 @@ package crypto import ( "fmt" - "github.com/btcsuite/btcd/btcec" - bip39 "github.com/cosmos/go-bip39" "github.com/pkg/errors" - secp256k1 "github.com/tendermint/btcd/btcec" - "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/go-bip39" + + secp256k1 "github.com/tendermint/btcd/btcec" + "github.com/tendermint/tendermint/crypto" + tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1" ) // If ledger support (build tag) has been enabled, which implies a CGO dependency, @@ -31,6 +33,8 @@ func (mock LedgerSECP256K1Mock) Close() error { return nil } +// GetPublicKeySECP256K1 mocks a ledger device +// as per the original API, it returns an uncompressed key func (mock LedgerSECP256K1Mock) GetPublicKeySECP256K1(derivationPath []uint32) ([]byte, error) { if derivationPath[0] != 44 { return nil, errors.New("Invalid derivation path") @@ -56,6 +60,28 @@ func (mock LedgerSECP256K1Mock) GetPublicKeySECP256K1(derivationPath []uint32) ( return pubkeyObject.SerializeUncompressed(), nil } +// GetAddressPubKeySECP256K1 mocks a ledger device +// as per the original API, it returns a compressed key and a bech32 address +func (mock LedgerSECP256K1Mock) GetAddressPubKeySECP256K1(derivationPath []uint32, hrp string) ([]byte, string, error) { + pk, err := mock.GetPublicKeySECP256K1(derivationPath) + if err != nil { + return nil, "", err + } + + // re-serialize in the 33-byte compressed format + cmp, err := btcec.ParsePubKey(pk[:], btcec.S256()) + if err != nil { + return nil, "", fmt.Errorf("error parsing public key: %v", err) + } + + var compressedPublicKey tmsecp256k1.PubKeySecp256k1 + copy(compressedPublicKey[:], cmp.SerializeCompressed()) + + // Generate the bech32 addr using existing tmcrypto/etc. + addr := types.AccAddress(compressedPublicKey.Address()).String() + return pk, addr, err +} + func (mock LedgerSECP256K1Mock) SignSECP256K1(derivationPath []uint32, message []byte) ([]byte, error) { path := hd.NewParams(derivationPath[0], derivationPath[1], derivationPath[2], derivationPath[3] != 0, derivationPath[4]) seed, err := bip39.NewSeedWithErrorChecking(tests.TestMnemonic, "") diff --git a/crypto/ledger_secp256k1.go b/crypto/ledger_secp256k1.go index 84ddf4657..b0c573f11 100644 --- a/crypto/ledger_secp256k1.go +++ b/crypto/ledger_secp256k1.go @@ -5,12 +5,11 @@ import ( "os" "github.com/btcsuite/btcd/btcec" + "github.com/pkg/errors" "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" @@ -28,13 +27,15 @@ type ( // dependencies when Ledger support is potentially not enabled. discoverLedgerFn func() (LedgerSECP256K1, error) - // LedgerSECP256K1 reflects an interface a Ledger API must implement for - // the SECP256K1 scheme. + // LedgerSECP256K1 reflects an interface a Ledger API must implement for SECP256K1 LedgerSECP256K1 interface { Close() error + // Returns an uncompressed pubkey GetPublicKeySECP256K1([]uint32) ([]byte, error) + // Returns a compressed pubkey and bech32 address (requires user confirmation) + GetAddressPubKeySECP256K1([]uint32, string) ([]byte, string, error) + // Signs a message (requires user confirmation) SignSECP256K1([]uint32, []byte) ([]byte, error) - ShowAddressSECP256K1([]uint32, string) error } // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we @@ -48,16 +49,19 @@ type ( } ) -// NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key -// for later use. -func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params) (tmcrypto.PrivKey, error) { +// NewPrivKeyLedgerSecp256k1Unsafe will generate a new key and store the public key for later use. +// +// This function is marked as unsafe as it will retrieve a pubkey without user verification. +// It can only be used to verify a pubkey but never to create new accounts/keys. In that case, +// please refer to NewPrivKeyLedgerSecp256k1 +func NewPrivKeyLedgerSecp256k1Unsafe(path hd.BIP44Params) (tmcrypto.PrivKey, error) { device, err := getLedgerDevice() if err != nil { return nil, err } defer warnIfErrors(device.Close) - pubKey, err := getPubKey(device, path) + pubKey, err := getPubKeyUnsafe(device, path) if err != nil { return nil, err } @@ -65,24 +69,21 @@ 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 { +// NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key for later use. +// The request will require user confirmation and will show account and index in the device +func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params, hrp string) (tmcrypto.PrivKey, string, error) { device, err := getLedgerDevice() if err != nil { - return err + return nil, "", err } defer warnIfErrors(device.Close) - pubKey, err := getPubKey(device, path) + pubKey, addr, err := getPubKeyAddrSafe(device, path, hrp) if err != nil { - return err + return nil, "", err } - if pubKey != expectedPubKey { - return fmt.Errorf("pubkey does not match, Check this is the same device") - } - - return device.ShowAddressSECP256K1(path.DerivationPath(), types.Bech32PrefixAccAddr) + return PrivKeyLedgerSecp256k1{pubKey, path}, addr, nil } // PubKey returns the cached public key. @@ -101,6 +102,35 @@ func (pkl PrivKeyLedgerSecp256k1) Sign(message []byte) ([]byte, error) { return sign(device, pkl, message) } +// 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 := getPubKeyUnsafe(device, path) + if err != nil { + return err + } + + if pubKey != expectedPubKey { + return fmt.Errorf("the key's pubkey does not match with the one retrieved from Ledger. Check that the HD path and device are the correct ones") + } + + pubKey2, _, err := getPubKeyAddrSafe(device, path, types.Bech32PrefixAccAddr) + if err != nil { + return err + } + + if pubKey2 != expectedPubKey { + return fmt.Errorf("the key's pubkey does not match with the one retrieved from Ledger. Check that the HD path and device are the correct ones") + } + + return nil +} + // ValidateKey allows us to verify the sanity of a public key after loading it // from disk. func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error { @@ -162,7 +192,7 @@ func getLedgerDevice() (LedgerSECP256K1, error) { } func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error { - pub, err := getPubKey(device, pkl.Path) + pub, err := getPubKeyUnsafe(device, pkl.Path) if err != nil { return err } @@ -194,10 +224,15 @@ func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byt return convertDERtoBER(sig) } -// getPubKey reads the pubkey the ledger itself +// getPubKeyUnsafe reads the pubkey from a ledger device +// +// This function is marked as unsafe as it will retrieve a pubkey without user verification +// It can only be used to verify a pubkey but never to create new accounts/keys. In that case, +// please refer to getPubKeyAddrSafe +// // since this involves IO, it may return an error, which is not exposed // in the PubKey interface, so this function allows better error handling -func getPubKey(device LedgerSECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, error) { +func getPubKeyUnsafe(device LedgerSECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, error) { publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath()) if err != nil { return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err) @@ -214,3 +249,27 @@ func getPubKey(device LedgerSECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, er return compressedPublicKey, nil } + +// getPubKeyAddr reads the pubkey and the address from a ledger device. +// This function is marked as Safe as it will require user confirmation and +// account and index will be shown in the device. +// +// Since this involves IO, it may return an error, which is not exposed +// in the PubKey interface, so this function allows better error handling. +func getPubKeyAddrSafe(device LedgerSECP256K1, path hd.BIP44Params, hrp string) (tmcrypto.PubKey, string, error) { + publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp) + if err != nil { + return nil, "", fmt.Errorf("address %s rejected", addr) + } + + // re-serialize in the 33-byte compressed format + cmp, err := btcec.ParsePubKey(publicKey[:], btcec.S256()) + if err != nil { + return nil, "", fmt.Errorf("error parsing public key: %v", err) + } + + var compressedPublicKey tmsecp256k1.PubKeySecp256k1 + copy(compressedPublicKey[:], cmp.SerializeCompressed()) + + return compressedPublicKey, addr, nil +} diff --git a/crypto/ledger_test.go b/crypto/ledger_test.go index faa3fd679..d7f30eefc 100644 --- a/crypto/ledger_test.go +++ b/crypto/ledger_test.go @@ -19,27 +19,31 @@ func TestLedgerErrorHandling(t *testing.T) { // first, try to generate a key, must return an error // (no panic) path := *hd.NewParams(44, 555, 0, false, 0) - _, err := NewPrivKeyLedgerSecp256k1(path) + _, err := NewPrivKeyLedgerSecp256k1Unsafe(path) require.Error(t, err) } -func TestPublicKey(t *testing.T) { +func TestPublicKeyUnsafe(t *testing.T) { path := *hd.NewFundraiserParams(0, 0) - priv, err := NewPrivKeyLedgerSecp256k1(path) + priv, err := NewPrivKeyLedgerSecp256k1Unsafe(path) require.Nil(t, err, "%s", err) require.NotNil(t, priv) + require.Equal(t, "eb5ae98721034fef9cd7c4c63588d3b03feb5281b9d232cba34d6f3d71aee59211ffbfe1fe87", + fmt.Sprintf("%x", priv.PubKey().Bytes()), + "Is your device using test mnemonic: %s ?", tests.TestMnemonic) + pubKeyAddr, err := sdk.Bech32ifyAccPub(priv.PubKey()) require.NoError(t, err) require.Equal(t, "cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0", pubKeyAddr, "Is your device using test mnemonic: %s ?", tests.TestMnemonic) - require.Equal(t, "5075624b6579536563703235366b317b303334464546394344374334433633353838443342303"+ - "3464542353238314239443233324342413334443646334437314145453539323131464642464531464538377d", - fmt.Sprintf("%x", priv.PubKey())) + addr := sdk.AccAddress(priv.PubKey().Address()).String() + require.Equal(t, "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", + addr, "Is your device using test mnemonic: %s ?", tests.TestMnemonic) } -func TestPublicKeyHDPath(t *testing.T) { +func TestPublicKeyUnsafeHDPath(t *testing.T) { expectedAnswers := []string{ "cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0", "cosmospub1addwnpepqfsdqjr68h7wjg5wacksmqaypasnra232fkgu5sxdlnlu8j22ztxvlqvd65", @@ -62,7 +66,7 @@ func TestPublicKeyHDPath(t *testing.T) { path := *hd.NewFundraiserParams(0, i) fmt.Printf("Checking keys at %v\n", path) - priv, err := NewPrivKeyLedgerSecp256k1(path) + priv, err := NewPrivKeyLedgerSecp256k1Unsafe(path) require.Nil(t, err, "%s", err) require.NotNil(t, priv) @@ -94,6 +98,104 @@ func TestPublicKeyHDPath(t *testing.T) { } } +func TestPublicKeySafe(t *testing.T) { + path := *hd.NewFundraiserParams(0, 0) + priv, addr, err := NewPrivKeyLedgerSecp256k1(path, "cosmos") + + require.Nil(t, err, "%s", err) + require.NotNil(t, priv) + + require.Equal(t, "eb5ae98721034fef9cd7c4c63588d3b03feb5281b9d232cba34d6f3d71aee59211ffbfe1fe87", + fmt.Sprintf("%x", priv.PubKey().Bytes()), + "Is your device using test mnemonic: %s ?", tests.TestMnemonic) + + pubKeyAddr, err := sdk.Bech32ifyAccPub(priv.PubKey()) + require.NoError(t, err) + require.Equal(t, "cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0", + pubKeyAddr, "Is your device using test mnemonic: %s ?", tests.TestMnemonic) + + require.Equal(t, "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", + addr, "Is your device using test mnemonic: %s ?", tests.TestMnemonic) + + addr2 := sdk.AccAddress(priv.PubKey().Address()).String() + require.Equal(t, addr, addr2) +} + +func TestPublicKeyHDPath(t *testing.T) { + expectedPubKeys := []string{ + "cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0", + "cosmospub1addwnpepqfsdqjr68h7wjg5wacksmqaypasnra232fkgu5sxdlnlu8j22ztxvlqvd65", + "cosmospub1addwnpepqw3xwqun6q43vtgw6p4qspq7srvxhcmvq4jrx5j5ma6xy3r7k6dtxmrkh3d", + "cosmospub1addwnpepqvez9lrp09g8w7gkv42y4yr5p6826cu28ydrhrujv862yf4njmqyyjr4pjs", + "cosmospub1addwnpepq06hw3enfrtmq8n67teytcmtnrgcr0yntmyt25kdukfjkerdc7lqg32rcz7", + "cosmospub1addwnpepqg3trf2gd0s2940nckrxherwqhgmm6xd5h4pcnrh4x7y35h6yafmcpk5qns", + "cosmospub1addwnpepqdm6rjpx6wsref8wjn7ym6ntejet430j4szpngfgc20caz83lu545vuv8hp", + "cosmospub1addwnpepqvdhtjzy2wf44dm03jxsketxc07vzqwvt3vawqqtljgsr9s7jvydjmt66ew", + "cosmospub1addwnpepqwystfpyxwcava7v3t7ndps5xzu6s553wxcxzmmnxevlzvwrlqpzz695nw9", + "cosmospub1addwnpepqw970u6gjqkccg9u3rfj99857wupj2z9fqfzy2w7e5dd7xn7kzzgkgqch0r", + } + + expectedAddrs := []string{ + "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", + "cosmos19ewxwemt6uahejvwf44u7dh6tq859tkyvarh2q", + "cosmos1a07dzdjgjsntxpp75zg7cgatgq0udh3pcdcxm3", + "cosmos1qvw52lmn9gpvem8welghrkc52m3zczyhlqjsl7", + "cosmos17m78ka80fqkkw2c4ww0v4xm5nsu2drgrlm8mn2", + "cosmos1ferh9ll9c452d2p8k2v7heq084guygkn43up9e", + "cosmos10vf3sxmjg96rqq36axcphzfsl74dsntuehjlw5", + "cosmos1cq83av8cmnar79h0rg7duh9gnr7wkh228a7fxg", + "cosmos1dszhfrt226jy5rsre7e48vw9tgwe90uerfyefa", + "cosmos1734d7qsylzrdt05muhqqtpd90j8mp4y6rzch8l", + } + + const numIters = 10 + + privKeys := make([]tmcrypto.PrivKey, numIters) + + // Check with device + for i := uint32(0); i < 10; i++ { + path := *hd.NewFundraiserParams(0, i) + fmt.Printf("Checking keys at %v\n", path) + + priv, addr, err := NewPrivKeyLedgerSecp256k1(path, "cosmos") + require.Nil(t, err, "%s", err) + require.NotNil(t, addr) + require.NotNil(t, priv) + + addr2 := sdk.AccAddress(priv.PubKey().Address()).String() + require.Equal(t, addr2, addr) + require.Equal(t, + expectedAddrs[i], addr, + "Is your device using test mnemonic: %s ?", tests.TestMnemonic) + + // Check other methods + require.NoError(t, priv.(PrivKeyLedgerSecp256k1).ValidateKey()) + tmp := priv.(PrivKeyLedgerSecp256k1) + (&tmp).AssertIsPrivKeyInner() + + pubKeyAddr, err := sdk.Bech32ifyAccPub(priv.PubKey()) + require.NoError(t, err) + require.Equal(t, + expectedPubKeys[i], pubKeyAddr, + "Is your device using test mnemonic: %s ?", tests.TestMnemonic) + + // Store and restore + serializedPk := priv.Bytes() + require.NotNil(t, serializedPk) + require.True(t, len(serializedPk) >= 50) + + privKeys[i] = priv + } + + // Now check equality + for i := 0; i < 10; i++ { + for j := 0; j < 10; j++ { + require.Equal(t, i == j, privKeys[i].Equals(privKeys[j])) + require.Equal(t, i == j, privKeys[j].Equals(privKeys[i])) + } + } +} + func getFakeTx(accountNumber uint32) []byte { tmp := fmt.Sprintf( `{"account_number":"%d","chain_id":"1234","fee":{"amount":[{"amount":"150","denom":"atom"}],"gas":"5000"},"memo":"memo","msgs":[[""]],"sequence":"6"}`, @@ -109,7 +211,7 @@ func TestSignaturesHD(t *testing.T) { path := *hd.NewFundraiserParams(account, account/5) fmt.Printf("Checking signature at %v --- PLEASE REVIEW AND ACCEPT IN THE DEVICE\n", path) - priv, err := NewPrivKeyLedgerSecp256k1(path) + priv, err := NewPrivKeyLedgerSecp256k1Unsafe(path) require.Nil(t, err, "%s", err) pub := priv.PubKey() @@ -124,7 +226,7 @@ func TestSignaturesHD(t *testing.T) { func TestRealLedgerSecp256k1(t *testing.T) { msg := getFakeTx(50) path := *hd.NewFundraiserParams(0, 0) - priv, err := NewPrivKeyLedgerSecp256k1(path) + priv, err := NewPrivKeyLedgerSecp256k1Unsafe(path) require.Nil(t, err, "%s", err) pub := priv.PubKey() diff --git a/go.mod b/go.mod index cc60848dd..961c12d1a 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,7 @@ require ( github.com/bgentry/speakeasy v0.1.0 github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 - github.com/cosmos/ledger-cosmos-go v0.9.11 - github.com/cosmos/ledger-go v0.9.1 // indirect + github.com/cosmos/ledger-cosmos-go v0.10.2 github.com/fortytw2/leaktest v1.3.0 // indirect github.com/go-logfmt/logfmt v0.4.0 // indirect github.com/gogo/protobuf v1.1.1 @@ -27,7 +26,7 @@ require ( github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 // indirect github.com/otiai10/mint v1.2.3 // indirect github.com/pelletier/go-toml v1.2.0 - github.com/pkg/errors v0.8.0 + github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v0.9.2 // indirect github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect github.com/prometheus/common v0.2.0 // indirect @@ -41,13 +40,12 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.0.3 - github.com/stretchr/testify v1.2.2 + github.com/stretchr/testify v1.3.0 github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3 // indirect github.com/tendermint/btcd v0.1.1 github.com/tendermint/go-amino v0.14.1 github.com/tendermint/iavl v0.12.1 github.com/tendermint/tendermint v0.31.5 - github.com/zondax/hid v0.9.0 // indirect golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 google.golang.org/grpc v1.19.0 // indirect gopkg.in/yaml.v2 v2.2.2 // indirect diff --git a/go.sum b/go.sum index 3ca7b29b0..1e79b896f 100644 --- a/go.sum +++ b/go.sum @@ -28,11 +28,12 @@ github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= -github.com/cosmos/ledger-cosmos-go v0.9.11 h1:bkcIKqaM6evicjkSP+Le8HDLXt9P+MqGRnGiMUC20m4= -github.com/cosmos/ledger-cosmos-go v0.9.11/go.mod h1:RWldjvUf4Hfi46ti/8etBH3eQ2rOqqz2hstdzROQSHo= +github.com/cosmos/ledger-cosmos-go v0.10.2 h1:B8JlCtl6otXi5PxY3Y7OF+HLhjLYvsemo4nl23wPQ9Y= +github.com/cosmos/ledger-cosmos-go v0.10.2/go.mod h1:TOLCJf4/WyTm7uw3OKurqANyQ66yoJEH9D14o0cpRlU= github.com/cosmos/ledger-go v0.9.1 h1:bRIamtlWShVk1THw52NdCPHxtBxKnauglSB23mH1/w8= github.com/cosmos/ledger-go v0.9.1/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -100,6 +101,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -134,9 +137,12 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.0.3 h1:z5LPUc2iz8VLT5Cw1UyrESG6FUUnOGecYGY08BLKSuc= github.com/spf13/viper v1.0.3/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3 h1:sAlSBRDl4psFR3ysKXRSE8ss6Mt90+ma1zRTroTNBJA= github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= diff --git a/x/mint/client/module_client.go b/x/mint/client/module_client.go index 1561e8c98..e77cd9205 100644 --- a/x/mint/client/module_client.go +++ b/x/mint/client/module_client.go @@ -1,12 +1,11 @@ package client import ( - "github.com/spf13/cobra" - "github.com/tendermint/go-amino" - - sdkclient "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/x/mint" - "github.com/cosmos/cosmos-sdk/x/mint/client/cli" + sdkclient "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/mint/client/cli" + "github.com/spf13/cobra" + "github.com/tendermint/go-amino" ) type ModuleClient struct {