Merge PR #3586: Fix keybase storage of ledger accounts
This commit is contained in:
parent
69eddb748c
commit
94dccf3842
|
@ -43,6 +43,7 @@ BUG FIXES
|
||||||
* Gaia REST API
|
* Gaia REST API
|
||||||
|
|
||||||
* Gaia CLI
|
* Gaia CLI
|
||||||
|
* [\#3586](https://github.com/cosmos/cosmos-sdk/pull/3586) Incomplete ledger derivation paths in keybase
|
||||||
|
|
||||||
* Gaia
|
* Gaia
|
||||||
* [\#3585] Fix setting the tx hash in `NewResponseFormatBroadcastTxCommit`.
|
* [\#3585] Fix setting the tx hash in `NewResponseFormatBroadcastTxCommit`.
|
||||||
|
@ -50,4 +51,4 @@ BUG FIXES
|
||||||
* SDK
|
* SDK
|
||||||
* [\#3582](https://github.com/cosmos/cosmos-sdk/pull/3582) Running `make test_unit was failing due to a missing tag
|
* [\#3582](https://github.com/cosmos/cosmos-sdk/pull/3582) Running `make test_unit was failing due to a missing tag
|
||||||
|
|
||||||
* Tendermint
|
* Tendermint
|
||||||
|
|
|
@ -77,7 +77,7 @@ the flag --nosort is set.
|
||||||
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
||||||
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
|
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
|
||||||
cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation")
|
cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation")
|
||||||
cmd.Flags().Uint32(flagIndex, 0, "Index number for HD derivation")
|
cmd.Flags().Uint32(flagIndex, 0, "Address index number for HD derivation")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
//+build ledger,test_ledger_mock
|
||||||
|
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_runAddCmdLedger(t *testing.T) {
|
||||||
|
cmd := addKeyCommand()
|
||||||
|
assert.NotNil(t, cmd)
|
||||||
|
|
||||||
|
// Prepare a keybase
|
||||||
|
kbHome, kbCleanUp := tests.NewTestCaseDir(t)
|
||||||
|
assert.NotNil(t, kbHome)
|
||||||
|
defer kbCleanUp()
|
||||||
|
viper.Set(cli.HomeFlag, kbHome)
|
||||||
|
viper.Set(client.FlagUseLedger, true)
|
||||||
|
|
||||||
|
/// Test Text
|
||||||
|
viper.Set(cli.OutputFlag, OutputFormatText)
|
||||||
|
// Now enter password
|
||||||
|
cleanUp1 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
|
||||||
|
defer cleanUp1()
|
||||||
|
err := runAddCmd(cmd, []string{"keyname1"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Now check that it has been stored properly
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, kb)
|
||||||
|
key1, err := kb.Get("keyname1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, key1)
|
||||||
|
|
||||||
|
assert.Equal(t, "keyname1", key1.GetName())
|
||||||
|
assert.Equal(t, keys.TypeLedger, key1.GetType())
|
||||||
|
assert.Equal(t,
|
||||||
|
"cosmospub1addwnpepqd87l8xhcnrrtzxnkql7k55ph8fr9jarf4hn6udwukfprlalu8lgw0urza0",
|
||||||
|
sdk.MustBech32ifyAccPub(key1.GetPubKey()))
|
||||||
|
}
|
|
@ -23,10 +23,6 @@ func Test_runAddCmdBasic(t *testing.T) {
|
||||||
cmd := addKeyCommand()
|
cmd := addKeyCommand()
|
||||||
assert.NotNil(t, cmd)
|
assert.NotNil(t, cmd)
|
||||||
|
|
||||||
// Missing input (enter password)
|
|
||||||
err := runAddCmd(cmd, []string{"keyname"})
|
|
||||||
assert.EqualError(t, err, "EOF")
|
|
||||||
|
|
||||||
// Prepare a keybase
|
// Prepare a keybase
|
||||||
kbHome, kbCleanUp := tests.NewTestCaseDir(t)
|
kbHome, kbCleanUp := tests.NewTestCaseDir(t)
|
||||||
assert.NotNil(t, kbHome)
|
assert.NotNil(t, kbHome)
|
||||||
|
@ -38,7 +34,7 @@ func Test_runAddCmdBasic(t *testing.T) {
|
||||||
// Now enter password
|
// Now enter password
|
||||||
cleanUp1 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
|
cleanUp1 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
|
||||||
defer cleanUp1()
|
defer cleanUp1()
|
||||||
err = runAddCmd(cmd, []string{"keyname1"})
|
err := runAddCmd(cmd, []string{"keyname1"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
/// Test Text - Replace? >> FAIL
|
/// Test Text - Replace? >> FAIL
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package keys
|
package keys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
amino "github.com/tendermint/go-amino"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||||
|
|
||||||
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var cdc = amino.NewCodec()
|
var cdc = amino.NewCodec()
|
||||||
|
@ -12,8 +11,7 @@ var cdc = amino.NewCodec()
|
||||||
func init() {
|
func init() {
|
||||||
cryptoAmino.RegisterAmino(cdc)
|
cryptoAmino.RegisterAmino(cdc)
|
||||||
cdc.RegisterInterface((*Info)(nil), nil)
|
cdc.RegisterInterface((*Info)(nil), nil)
|
||||||
cdc.RegisterConcrete(ccrypto.PrivKeyLedgerSecp256k1{},
|
cdc.RegisterConcrete(hd.BIP44Params{}, "crypto/keys/hd/BIP44Params", nil)
|
||||||
"tendermint/PrivKeyLedgerSecp256k1", nil)
|
|
||||||
cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil)
|
cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil)
|
||||||
cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil)
|
cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil)
|
||||||
cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil)
|
cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil)
|
||||||
|
|
|
@ -33,25 +33,25 @@ const (
|
||||||
|
|
||||||
// BIP44Params wraps BIP 44 params (5 level BIP 32 path).
|
// BIP44Params wraps BIP 44 params (5 level BIP 32 path).
|
||||||
// To receive a canonical string representation ala
|
// To receive a canonical string representation ala
|
||||||
// m / purpose' / coin_type' / account' / change / address_index
|
// m / purpose' / coinType' / account' / change / addressIndex
|
||||||
// call String() on a BIP44Params instance.
|
// call String() on a BIP44Params instance.
|
||||||
type BIP44Params struct {
|
type BIP44Params struct {
|
||||||
purpose uint32
|
Purpose uint32 `json:"purpose"`
|
||||||
coinType uint32
|
CoinType uint32 `json:"coinType"`
|
||||||
account uint32
|
Account uint32 `json:"account"`
|
||||||
change bool
|
Change bool `json:"change"`
|
||||||
addressIdx uint32
|
AddressIndex uint32 `json:"addressIndex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParams creates a BIP 44 parameter object from the params:
|
// NewParams creates a BIP 44 parameter object from the params:
|
||||||
// m / purpose' / coin_type' / account' / change / address_index
|
// m / purpose' / coinType' / account' / change / addressIndex
|
||||||
func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32) *BIP44Params {
|
func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32) *BIP44Params {
|
||||||
return &BIP44Params{
|
return &BIP44Params{
|
||||||
purpose: purpose,
|
Purpose: purpose,
|
||||||
coinType: coinType,
|
CoinType: coinType,
|
||||||
account: account,
|
Account: account,
|
||||||
change: change,
|
Change: change,
|
||||||
addressIdx: addressIdx,
|
AddressIndex: addressIdx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,11 +105,11 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BIP44Params{
|
return &BIP44Params{
|
||||||
purpose: purpose,
|
Purpose: purpose,
|
||||||
coinType: coinType,
|
CoinType: coinType,
|
||||||
account: account,
|
Account: account,
|
||||||
change: change > 0,
|
Change: change > 0,
|
||||||
addressIdx: addressIdx,
|
AddressIndex: addressIdx,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,32 +139,32 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
|
||||||
// DerivationPath returns the BIP44 fields as an array.
|
// DerivationPath returns the BIP44 fields as an array.
|
||||||
func (p BIP44Params) DerivationPath() []uint32 {
|
func (p BIP44Params) DerivationPath() []uint32 {
|
||||||
change := uint32(0)
|
change := uint32(0)
|
||||||
if p.change {
|
if p.Change {
|
||||||
change = 1
|
change = 1
|
||||||
}
|
}
|
||||||
return []uint32{
|
return []uint32{
|
||||||
p.purpose,
|
p.Purpose,
|
||||||
p.coinType,
|
p.CoinType,
|
||||||
p.account,
|
p.Account,
|
||||||
change,
|
change,
|
||||||
p.addressIdx,
|
p.AddressIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p BIP44Params) String() string {
|
func (p BIP44Params) String() string {
|
||||||
var changeStr string
|
var changeStr string
|
||||||
if p.change {
|
if p.Change {
|
||||||
changeStr = "1"
|
changeStr = "1"
|
||||||
} else {
|
} else {
|
||||||
changeStr = "0"
|
changeStr = "0"
|
||||||
}
|
}
|
||||||
// m / purpose' / coin_type' / account' / change / address_index
|
// m / Purpose' / coin_type' / Account' / Change / address_index
|
||||||
return fmt.Sprintf("%d'/%d'/%d'/%s/%d",
|
return fmt.Sprintf("%d'/%d'/%d'/%s/%d",
|
||||||
p.purpose,
|
p.Purpose,
|
||||||
p.coinType,
|
p.CoinType,
|
||||||
p.account,
|
p.Account,
|
||||||
changeStr,
|
changeStr,
|
||||||
p.addressIdx)
|
p.AddressIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex.
|
// ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex.
|
||||||
|
|
|
@ -284,9 +284,9 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case ledgerInfo:
|
case ledgerInfo:
|
||||||
return nil, errors.New("Only works on local private keys")
|
return nil, errors.New("only works on local private keys")
|
||||||
case offlineInfo:
|
case offlineInfo:
|
||||||
return nil, errors.New("Only works on local private keys")
|
return nil, errors.New("only works on local private keys")
|
||||||
}
|
}
|
||||||
return priv, nil
|
return priv, nil
|
||||||
}
|
}
|
||||||
|
@ -428,7 +428,8 @@ func (kb dbKeybase) writeOfflineKey(name string, pub tmcrypto.PubKey) Info {
|
||||||
func (kb dbKeybase) writeInfo(name string, info Info) {
|
func (kb dbKeybase) writeInfo(name string, info Info) {
|
||||||
// write the info by key
|
// write the info by key
|
||||||
key := infoKey(name)
|
key := infoKey(name)
|
||||||
kb.db.SetSync(key, writeInfo(info))
|
serializedInfo := writeInfo(info)
|
||||||
|
kb.db.SetSync(key, serializedInfo)
|
||||||
// store a pointer to the infokey by address for fast lookup
|
// store a pointer to the infokey by address for fast lookup
|
||||||
kb.db.SetSync(addrKey(info.GetAddress()), key)
|
kb.db.SetSync(addrKey(info.GetAddress()), key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,19 +68,34 @@ func TestCreateLedger(t *testing.T) {
|
||||||
// test_cover does not compile some dependencies so ledger is disabled
|
// test_cover does not compile some dependencies so ledger is disabled
|
||||||
// test_unit may add a ledger mock
|
// test_unit may add a ledger mock
|
||||||
// both cases are acceptable
|
// both cases are acceptable
|
||||||
ledger, err := kb.CreateLedger("some_account", Secp256k1, 0, 1)
|
ledger, err := kb.CreateLedger("some_account", Secp256k1, 3, 1)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
|
assert.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
|
||||||
assert.Nil(t, ledger)
|
assert.Nil(t, ledger)
|
||||||
} else {
|
t.Skip("ledger nano S: support for ledger devices is not available in this executable")
|
||||||
// The mock is available, check that the address is correct
|
return
|
||||||
pubKey := ledger.GetPubKey()
|
|
||||||
addr, err := sdk.Bech32ifyAccPub(pubKey)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "cosmospub1addwnpepqfsdqjr68h7wjg5wacksmqaypasnra232fkgu5sxdlnlu8j22ztxvlqvd65", addr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The mock is available, check that the address is correct
|
||||||
|
pubKey := ledger.GetPubKey()
|
||||||
|
pk, err := sdk.Bech32ifyAccPub(pubKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
|
||||||
|
|
||||||
|
// Check that restoring the key gets the same results
|
||||||
|
restoredKey, err := kb.Get("some_account")
|
||||||
|
assert.NotNil(t, restoredKey)
|
||||||
|
assert.Equal(t, "some_account", restoredKey.GetName())
|
||||||
|
assert.Equal(t, TypeLedger, restoredKey.GetType())
|
||||||
|
pubKey = restoredKey.GetPubKey()
|
||||||
|
pk, err = sdk.Bech32ifyAccPub(pubKey)
|
||||||
|
assert.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
|
||||||
|
|
||||||
|
linfo := restoredKey.(ledgerInfo)
|
||||||
|
assert.Equal(t, "44'/118'/3'/0/1", linfo.GetPath().String())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestKeyManagement makes sure we can manipulate these keys well
|
// TestKeyManagement makes sure we can manipulate these keys well
|
||||||
|
|
|
@ -150,6 +150,10 @@ func (i ledgerInfo) GetAddress() types.AccAddress {
|
||||||
return i.PubKey.Address().Bytes()
|
return i.PubKey.Address().Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i ledgerInfo) GetPath() hd.BIP44Params {
|
||||||
|
return i.Path
|
||||||
|
}
|
||||||
|
|
||||||
// offlineInfo is the public information about an offline key
|
// offlineInfo is the public information about an offline key
|
||||||
type offlineInfo struct {
|
type offlineInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_writeReadLedgerInfo(t *testing.T) {
|
||||||
|
var tmpKey secp256k1.PubKeySecp256k1
|
||||||
|
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
|
||||||
|
copy(tmpKey[:], bz)
|
||||||
|
|
||||||
|
lInfo := ledgerInfo{
|
||||||
|
"some_name",
|
||||||
|
tmpKey,
|
||||||
|
*hd.NewFundraiserParams(5, 1)}
|
||||||
|
assert.Equal(t, TypeLedger, lInfo.GetType())
|
||||||
|
assert.Equal(t, "44'/118'/5'/0/1", lInfo.GetPath().String())
|
||||||
|
assert.Equal(t,
|
||||||
|
"cosmospub1addwnpepqddddqg2glc8x4fl7vxjlnr7p5a3czm5kcdp4239sg6yqdc4rc2r5wmxv8p",
|
||||||
|
types.MustBech32ifyAccPub(lInfo.GetPubKey()))
|
||||||
|
|
||||||
|
// Serialize and restore
|
||||||
|
serialized := writeInfo(lInfo)
|
||||||
|
restoredInfo, err := readInfo(serialized)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, restoredInfo)
|
||||||
|
|
||||||
|
// Check both keys match
|
||||||
|
assert.Equal(t, lInfo.GetName(), restoredInfo.GetName())
|
||||||
|
assert.Equal(t, lInfo.GetType(), restoredInfo.GetType())
|
||||||
|
assert.Equal(t, lInfo.GetPubKey(), restoredInfo.GetPubKey())
|
||||||
|
|
||||||
|
assert.Equal(t, lInfo.GetPath(), restoredInfo.(ledgerInfo).GetPath())
|
||||||
|
}
|
|
@ -79,7 +79,7 @@ func TestPublicKeyHDPath(t *testing.T) {
|
||||||
// Store and restore
|
// Store and restore
|
||||||
serializedPk := priv.Bytes()
|
serializedPk := priv.Bytes()
|
||||||
require.NotNil(t, serializedPk)
|
require.NotNil(t, serializedPk)
|
||||||
require.Equal(t, 44, len(serializedPk))
|
require.True(t, len(serializedPk) >= 50)
|
||||||
|
|
||||||
privKeys[i] = priv
|
privKeys[i] = priv
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue