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 0000000000..97bc1e81b1 --- /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 fd3c1b747d..983636e142 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 131b45f5e7..b5316dc5f9 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 e4ff5651c6..a72b43238a 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 e5613e302e..67b1f1c0ae 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 6f0b685234..6922bd152f 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 1459e1ae02..5389f93686 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 b9f1048cf5..159d9c2d37 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 84ddf46578..b0c573f113 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 faa3fd6797..d7f30eefcc 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 cc60848dd9..961c12d1a5 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 3ca7b29b0c..1e79b896f2 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 1561e8c989..e77cd92050 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 {