Merge PR #5439: Keybase: Multiple Signature Algorithms

This commit is contained in:
Sunny Aggarwal 2020-01-14 10:40:10 -05:00 committed by Alexander Bezobchuk
parent d452e9398b
commit f367087731
21 changed files with 419 additions and 201 deletions

View File

@ -101,7 +101,16 @@ if the provided arguments are invalid.
* (modules) [\#5299](https://github.com/cosmos/cosmos-sdk/pull/5299) `HandleDoubleSign` along with params `MaxEvidenceAge`
and `DoubleSignJailEndTime` have moved from the `x/slashing` module to the `x/evidence` module.
* (keys) [\#4941](https://github.com/cosmos/cosmos-sdk/issues/4941) Initializing a new keybase through `NewKeyringFromHomeFlag`, `NewKeyringFromDir`, `NewKeyBaseFromHomeFlag`, `NewKeyBaseFromDir`, or `NewInMemory` functions now accept optional parameters of type `KeybaseOption`. These optional parameters are also added on the keys subcommands functions, which are now public, and allows these options to be set on the commands or ignored to default to previous behavior.
* The option introduced in this PR is `WithKeygenFunc` which allows a custom bytes to key implementation to be defined when keys are created.
* [\#5439](https://github.com/cosmos/cosmos-sdk/pull/5439) Further modularization was done to the `keybase`
package to make it more suitable for use with different key formats and algorithms:
* The `WithKeygenFunc` function added as a `KeybaseOption` which allows a custom bytes to key
implementation to be defined when keys are created.
* The `WithDeriveFunc` function added as a `KeybaseOption` allows custom logic for deriving a key
from a mnemonic, bip39 password, and HD Path.
* BIP44 is no longer build into `keybase.CreateAccount()`. It is however the default when using
the `client/keys` add command.
* `SupportedAlgos` and `SupportedAlgosLedger` functions return a slice of `SigningAlgo`s that are
supported by the keybase and the ledger integration respectively.
* (simapp) [\#5419](https://github.com/cosmos/cosmos-sdk/pull/5419) simapp/helpers.GenTx() now accepts a gas argument.
* (baseapp) [\#5455](https://github.com/cosmos/cosmos-sdk/issues/5455) An `sdk.Context` is passed into the `router.Route()`
function.
@ -177,10 +186,13 @@ that allows for arbitrary vesting periods.
* `IncrementSequenceDecorator`: Increments the account sequence for each signer to prevent replay attacks.
* (cli) [\#5223](https://github.com/cosmos/cosmos-sdk/issues/5223) Cosmos Ledger App v2.0.0 is now supported. The changes are backwards compatible and App v1.5.x is still supported.
* (x/staking) [\#5380](https://github.com/cosmos/cosmos-sdk/pull/5380) Introduced ability to store historical info entries in staking keeper, allows applications to introspect specified number of past headers and validator sets
* Introduces new parameter `HistoricalEntries` which allows applications to determine how many recent historical info entries they want to persist in store. Default value is 0.
* Introduces cli commands and rest routes to query historical information at a given height
* Introduces new parameter `HistoricalEntries` which allows applications to determine how many recent historical info entries they want to persist in store. Default value is 0.
* Introduces cli commands and rest routes to query historical information at a given height
* (modules) [\#5249](https://github.com/cosmos/cosmos-sdk/pull/5249) Funds are now allowed to be directly sent to the community pool (via the distribution module account).
* (keys) [\#4941](https://github.com/cosmos/cosmos-sdk/issues/4941) Introduce keybase option to allow overriding the default private key implementation of a key generated through the `keys add` cli command.
* (keys) [\#5439](https://github.com/cosmos/cosmos-sdk/pull/5439) Flags `--algo` and `--hd-path` are added to
`keys add` command in order to make use of keybase modularized. By default, it uses (0, 0) bip44
HD path and secp256k1 keys, so is non-breaking.
* (types) [\#5447](https://github.com/cosmos/cosmos-sdk/pull/5447) Added `ApproxRoot` function to sdk.Decimal type in order to get the nth root for a decimal number, where n is a positive integer.
* An `ApproxSqrt` function was also added for convenience around the common case of n=2.

View File

@ -32,6 +32,8 @@ const (
flagIndex = "index"
flagMultisig = "multisig"
flagNoSort = "nosort"
flagHDPath = "hd-path"
flagKeyAlgo = "algo"
// DefaultKeyPass contains the default key password for genesis transactions
DefaultKeyPass = "12345678"
@ -71,9 +73,11 @@ the flag --nosort is set.
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
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().String(flagHDPath, "", "Manual HD Path derivation (overrides BIP44 config)")
cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation")
cmd.Flags().Uint32(flagIndex, 0, "Address index number for HD derivation")
cmd.Flags().Bool(flags.FlagIndentResponse, false, "Add indent to JSON response")
cmd.Flags().String(flagKeyAlgo, string(keys.Secp256k1), "Key signing algorithm to generate keys for")
return cmd
}
@ -112,6 +116,14 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keys.Keybase, inBuf *bufio.
interactive := viper.GetBool(flagInteractive)
showMnemonic := !viper.GetBool(flagNoBackup)
algo := keys.SigningAlgo(viper.GetString(flagKeyAlgo))
if algo == keys.SigningAlgo("") {
algo = keys.Secp256k1
}
if !keys.IsSupportedAlgorithm(kb.SupportedAlgos(), algo) {
return keys.ErrUnsupportedSigningAlgo
}
if !viper.GetBool(flagDryRun) {
_, err = kb.Get(name)
if err == nil {
@ -164,7 +176,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keys.Keybase, inBuf *bufio.
if err != nil {
return err
}
_, err = kb.CreateOffline(name, pk)
_, err = kb.CreateOffline(name, pk, algo)
if err != nil {
return err
}
@ -174,8 +186,26 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keys.Keybase, inBuf *bufio.
account := uint32(viper.GetInt(flagAccount))
index := uint32(viper.GetInt(flagIndex))
useBIP44 := !viper.IsSet(flagHDPath)
var hdPath string
if useBIP44 {
hdPath = keys.CreateHDPath(account, index).String()
} else {
hdPath = viper.GetString(flagHDPath)
}
// If we're using ledger, only thing we need is the path and the bech32 prefix.
if viper.GetBool(flags.FlagUseLedger) {
if !useBIP44 {
return errors.New("cannot set custom bip32 path with ledger")
}
if !keys.IsSupportedAlgorithm(kb.SupportedAlgosLedger(), algo) {
return keys.ErrUnsupportedSigningAlgo
}
bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix()
info, err := kb.CreateLedger(name, keys.Secp256k1, bech32PrefixAccAddr, account, index)
if err != nil {
@ -240,7 +270,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keys.Keybase, inBuf *bufio.
}
}
info, err := kb.CreateAccount(name, mnemonic, bip39Passphrase, DefaultKeyPass, account, index)
info, err := kb.CreateAccount(name, mnemonic, bip39Passphrase, DefaultKeyPass, hdPath, algo)
if err != nil {
return err
}

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/tests"
)
@ -44,13 +45,13 @@ func Test_runDeleteCmd(t *testing.T) {
if runningUnattended {
mockIn.Reset("testpass1\ntestpass1\n")
}
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keys.Secp256k1)
require.NoError(t, err)
if runningUnattended {
mockIn.Reset("testpass1\ntestpass1\n")
}
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keys.Secp256k1)
require.NoError(t, err)
if runningUnattended {

View File

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/tests"
)
@ -32,7 +33,7 @@ func Test_runExportCmd(t *testing.T) {
if runningUnattended {
mockIn.Reset("testpass1\ntestpass1\n")
}
_, err = kb.CreateAccount("keyname1", tests.TestMnemonic, "", "123456789", 0, 0)
_, err = kb.CreateAccount("keyname1", tests.TestMnemonic, "", "123456789", "", keys.Secp256k1)
require.NoError(t, err)
// Now enter password

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/tests"
)
@ -36,7 +37,7 @@ func Test_runListCmd(t *testing.T) {
mockIn.Reset("testpass1\ntestpass1\n")
}
_, err = kb.CreateAccount("something", tests.TestMnemonic, "", "", 0, 0)
_, err = kb.CreateAccount("something", tests.TestMnemonic, "", "", "", keys.Secp256k1)
require.NoError(t, err)
defer func() {

View File

@ -58,13 +58,13 @@ func Test_runShowCmd(t *testing.T) {
if runningUnattended {
mockIn.Reset("testpass1\ntestpass1\n")
}
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keys.Secp256k1)
require.NoError(t, err)
if runningUnattended {
mockIn.Reset("testpass1\n")
}
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keys.Secp256k1)
require.NoError(t, err)
// Now try single key

View File

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/tests"
)
@ -38,9 +39,9 @@ func Test_runUpdateCmd(t *testing.T) {
kb, err := NewKeyBaseFromHomeFlag()
assert.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keys.Secp256k1)
assert.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keys.Secp256k1)
assert.NoError(t, err)
// Try again now that we have keys

View File

@ -10,7 +10,6 @@ import (
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/types"
@ -57,7 +56,7 @@ const (
var (
// ErrUnsupportedSigningAlgo is raised when the caller tries to use a
// different signing scheme than secp256k1.
ErrUnsupportedSigningAlgo = errors.New("unsupported signing algo: only secp256k1 is supported")
ErrUnsupportedSigningAlgo = errors.New("unsupported signing algo")
// ErrUnsupportedLanguage is raised when the caller tries to use a
// different language than english for creating a mnemonic sentence.
@ -101,18 +100,10 @@ func (kb dbKeybase) CreateMnemonic(
// CreateAccount converts a mnemonic to a private key and persists it, encrypted
// with the given password.
func (kb dbKeybase) CreateAccount(
name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32,
name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo,
) (Info, error) {
return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, account, index)
}
// Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
func (kb dbKeybase) Derive(
name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params,
) (Info, error) {
return kb.base.Derive(kb, name, mnemonic, bip39Passphrase, encryptPasswd, params)
return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo)
}
// CreateLedger creates a new locally-stored reference to a Ledger keypair.
@ -126,8 +117,8 @@ func (kb dbKeybase) CreateLedger(
// CreateOffline creates a new reference to an offline keypair. It returns the
// created key info.
func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.base.writeOfflineKey(kb, name, pub), nil
func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey, algo SigningAlgo) (Info, error) {
return kb.base.writeOfflineKey(kb, name, pub, algo), nil
}
// CreateMulti creates a new reference to a multisig (offline) keypair. It
@ -199,7 +190,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t
return
}
priv, err = mintkey.UnarmorDecryptPrivKey(i.PrivKeyArmor, passphrase)
priv, _, err = mintkey.UnarmorDecryptPrivKey(i.PrivKeyArmor, passphrase)
if err != nil {
return nil, nil, err
}
@ -238,7 +229,7 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr
return nil, err
}
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
priv, _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
if err != nil {
return nil, err
}
@ -272,7 +263,7 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
return
}
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes(), string(info.GetAlgo())), nil
}
// ExportPrivKey returns a private key in ASCII armored format.
@ -285,7 +276,12 @@ func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string,
return "", err
}
return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase), nil
info, err := kb.Get(name)
if err != nil {
return "", err
}
return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil
}
// ImportPrivKey imports a private key in ASCII armor format. It returns an
@ -296,12 +292,12 @@ func (kb dbKeybase) ImportPrivKey(name string, armor string, passphrase string)
return errors.New("Cannot overwrite key " + name)
}
privKey, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
privKey, algo, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
if err != nil {
return errors.Wrap(err, "couldn't import private key")
}
kb.writeLocalKey(name, privKey, passphrase)
kb.writeLocalKey(name, privKey, passphrase, SigningAlgo(algo))
return nil
}
@ -329,7 +325,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
return errors.New("Cannot overwrite data for name " + name)
}
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
pubBytes, algo, err := mintkey.UnarmorPubKeyBytes(armor)
if err != nil {
return
}
@ -339,7 +335,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
return
}
kb.base.writeOfflineKey(kb, name, pubKey)
kb.base.writeOfflineKey(kb, name, pubKey, SigningAlgo(algo))
return
}
@ -355,7 +351,7 @@ func (kb dbKeybase) Delete(name, passphrase string, skipPass bool) error {
}
if linfo, ok := info.(localInfo); ok && !skipPass {
if _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase); err != nil {
if _, _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase); err != nil {
return err
}
}
@ -382,7 +378,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
case localInfo:
linfo := i
key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
key, _, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
if err != nil {
return err
}
@ -392,7 +388,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
return err
}
kb.writeLocalKey(name, key, newpass)
kb.writeLocalKey(name, key, newpass, i.GetAlgo())
return nil
default:
@ -405,13 +401,23 @@ func (kb dbKeybase) CloseDB() {
kb.db.Close()
}
func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info {
// SupportedAlgos returns a list of supported signing algorithms.
func (kb dbKeybase) SupportedAlgos() []SigningAlgo {
return kb.base.SupportedAlgos()
}
// SupportedAlgosLedger returns a list of supported ledger signing algorithms.
func (kb dbKeybase) SupportedAlgosLedger() []SigningAlgo {
return kb.base.SupportedAlgosLedger()
}
func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string, algo SigningAlgo) Info {
// encrypt private key using passphrase
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase, string(algo))
// make Info
pub := priv.PubKey()
info := newLocalInfo(name, pub, privArmor)
info := newLocalInfo(name, pub, privArmor, algo)
kb.writeInfo(name, info)
return info

View File

@ -17,7 +17,10 @@ import (
type (
kbOptions struct {
keygenFunc PrivKeyGenFunc
keygenFunc PrivKeyGenFunc
deriveFunc DeriveKeyFunc
supportedAlgos []SigningAlgo
supportedAlgosLedger []SigningAlgo
}
// baseKeybase is an auxiliary type that groups Keybase storage agnostic features
@ -32,7 +35,7 @@ type (
}
writeLocalKeyer interface {
writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info
writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string, algo SigningAlgo) Info
}
infoWriter interface {
@ -47,10 +50,36 @@ func WithKeygenFunc(f PrivKeyGenFunc) KeybaseOption {
}
}
// WithDeriveFunc applies an overridden key derivation function to generate the private key.
func WithDeriveFunc(f DeriveKeyFunc) KeybaseOption {
return func(o *kbOptions) {
o.deriveFunc = f
}
}
// WithSupportedAlgos defines the list of accepted SigningAlgos.
func WithSupportedAlgos(algos []SigningAlgo) KeybaseOption {
return func(o *kbOptions) {
o.supportedAlgos = algos
}
}
// WithSupportedAlgosLedger defines the list of accepted SigningAlgos compatible with Ledger.
func WithSupportedAlgosLedger(algos []SigningAlgo) KeybaseOption {
return func(o *kbOptions) {
o.supportedAlgosLedger = algos
}
}
// newBaseKeybase generates the base keybase with defaulting to tendermint SECP256K1 key type
func newBaseKeybase(optionsFns ...KeybaseOption) baseKeybase {
// Default options for keybase
options := kbOptions{keygenFunc: baseSecpPrivKeyGen}
options := kbOptions{
keygenFunc: StdPrivKeyGen,
deriveFunc: StdDeriveKey,
supportedAlgos: []SigningAlgo{Secp256k1},
supportedAlgosLedger: []SigningAlgo{Secp256k1},
}
for _, optionFn := range optionsFns {
optionFn(&options)
@ -59,9 +88,20 @@ func newBaseKeybase(optionsFns ...KeybaseOption) baseKeybase {
return baseKeybase{options: options}
}
// baseSecpPrivKeyGen generates a secp256k1 private key from the given bytes
func baseSecpPrivKeyGen(bz [32]byte) tmcrypto.PrivKey {
return secp256k1.PrivKeySecp256k1(bz)
// StdPrivKeyGen is the default PrivKeyGen function in the keybase.
// For now, it only supports Secp256k1
func StdPrivKeyGen(bz []byte, algo SigningAlgo) (tmcrypto.PrivKey, error) {
if algo == Secp256k1 {
return SecpPrivKeyGen(bz), nil
}
return nil, ErrUnsupportedSigningAlgo
}
// SecpPrivKeyGen generates a secp256k1 private key from the given bytes
func SecpPrivKeyGen(bz []byte) tmcrypto.PrivKey {
var bzArr [32]byte
copy(bzArr[:], bz)
return secp256k1.PrivKeySecp256k1(bzArr)
}
// SignWithLedger signs a binary message with the ledger device referenced by an Info object
@ -111,29 +151,26 @@ func (kb baseKeybase) DecodeSignature(info Info, msg []byte) (sig []byte, pub tm
// CreateAccount creates an account Info object.
func (kb baseKeybase) CreateAccount(
keyWriter keyWriter, name, mnemonic, bip39Passwd, encryptPasswd string, account, index uint32,
) (Info, error) {
hdPath := CreateHDPath(account, index)
return kb.Derive(keyWriter, name, mnemonic, bip39Passwd, encryptPasswd, *hdPath)
}
func (kb baseKeybase) persistDerivedKey(
keyWriter keyWriter, seed []byte, passwd, name, fullHdPath string,
keyWriter keyWriter, name, mnemonic, bip39Passphrase, encryptPasswd, hdPath string, algo SigningAlgo,
) (Info, error) {
// create master key and derive first key for keyring
derivedPriv, err := ComputeDerivedKey(seed, fullHdPath)
derivedPriv, err := kb.options.deriveFunc(mnemonic, bip39Passphrase, hdPath, algo)
if err != nil {
return nil, err
}
privKey, err := kb.options.keygenFunc(derivedPriv, algo)
if err != nil {
return nil, err
}
var info Info
if passwd != "" {
info = keyWriter.writeLocalKey(name, kb.options.keygenFunc(derivedPriv), passwd)
if encryptPasswd != "" {
info = keyWriter.writeLocalKey(name, privKey, encryptPasswd, algo)
} else {
info = kb.writeOfflineKey(keyWriter, name, kb.options.keygenFunc(derivedPriv).PubKey())
info = kb.writeOfflineKey(keyWriter, name, privKey.PubKey(), algo)
}
return info, nil
@ -145,7 +182,7 @@ func (kb baseKeybase) CreateLedger(
w infoWriter, name string, algo SigningAlgo, hrp string, account, index uint32,
) (Info, error) {
if !IsAlgoSupported(algo) {
if !IsSupportedAlgorithm(kb.SupportedAlgosLedger(), algo) {
return nil, ErrUnsupportedSigningAlgo
}
@ -157,7 +194,7 @@ func (kb baseKeybase) CreateLedger(
return nil, err
}
return kb.writeLedgerKey(w, name, priv.PubKey(), *hdPath), nil
return kb.writeLedgerKey(w, name, priv.PubKey(), *hdPath, algo), nil
}
// CreateMnemonic generates a new key with the given algorithm and language pair.
@ -169,7 +206,7 @@ func (kb baseKeybase) CreateMnemonic(
return nil, "", ErrUnsupportedLanguage
}
if !IsAlgoSupported(algo) {
if !IsSupportedAlgorithm(kb.SupportedAlgos(), algo) {
return nil, "", ErrUnsupportedSigningAlgo
}
@ -185,37 +222,22 @@ func (kb baseKeybase) CreateMnemonic(
return nil, "", err
}
info, err = kb.persistDerivedKey(
keyWriter,
bip39.NewSeed(mnemonic, DefaultBIP39Passphrase), passwd,
name, types.GetConfig().GetFullFundraiserPath(),
)
info, err = kb.CreateAccount(keyWriter, name, mnemonic, DefaultBIP39Passphrase, passwd, types.GetConfig().GetFullFundraiserPath(), algo)
if err != nil {
return nil, "", err
}
return info, mnemonic, err
}
// Derive computes a BIP39 seed from the mnemonic and bip39Passphrase. It creates
// a private key from the seed using the BIP44 params.
func (kb baseKeybase) Derive(
keyWriter keyWriter, name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params, // nolint:interfacer
) (Info, error) {
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
if err != nil {
return nil, err
}
return kb.persistDerivedKey(keyWriter, seed, encryptPasswd, name, params.String())
}
func (kb baseKeybase) writeLedgerKey(w infoWriter, name string, pub tmcrypto.PubKey, path hd.BIP44Params) Info {
info := newLedgerInfo(name, pub, path)
func (kb baseKeybase) writeLedgerKey(w infoWriter, name string, pub tmcrypto.PubKey, path hd.BIP44Params, algo SigningAlgo) Info {
info := newLedgerInfo(name, pub, path, algo)
w.writeInfo(name, info)
return info
}
func (kb baseKeybase) writeOfflineKey(w infoWriter, name string, pub tmcrypto.PubKey) Info {
info := newOfflineInfo(name, pub)
func (kb baseKeybase) writeOfflineKey(w infoWriter, name string, pub tmcrypto.PubKey, algo SigningAlgo) Info {
info := newOfflineInfo(name, pub, algo)
w.writeInfo(name, info)
return info
}
@ -226,10 +248,28 @@ func (kb baseKeybase) writeMultisigKey(w infoWriter, name string, pub tmcrypto.P
return info
}
// ComputeDerivedKey derives and returns the private key for the given seed and HD path.
func ComputeDerivedKey(seed []byte, fullHdPath string) ([32]byte, error) {
// StdDeriveKey is the default DeriveKey function in the keybase.
// For now, it only supports Secp256k1
func StdDeriveKey(mnemonic string, bip39Passphrase, hdPath string, algo SigningAlgo) ([]byte, error) {
if algo == Secp256k1 {
return SecpDeriveKey(mnemonic, bip39Passphrase, hdPath)
}
return nil, ErrUnsupportedSigningAlgo
}
// SecpDeriveKey derives and returns the secp256k1 private key for the given seed and HD path.
func SecpDeriveKey(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) {
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
if err != nil {
return nil, err
}
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
return hd.DerivePrivateKeyForPath(masterPriv, ch, fullHdPath)
if len(hdPath) == 0 {
return masterPriv[:], nil
}
derivedKey, err := hd.DerivePrivateKeyForPath(masterPriv, ch, hdPath)
return derivedKey[:], err
}
// CreateHDPath returns BIP 44 object from account and index parameters.
@ -237,9 +277,22 @@ func CreateHDPath(account uint32, index uint32) *hd.BIP44Params {
return hd.NewFundraiserParams(account, types.GetConfig().GetCoinType(), index)
}
// IsAlgoSupported returns whether the signing algorithm is supported.
//
// TODO: Refactor this to be configurable to support interchangeable key signing
// and addressing.
// Ref: https://github.com/cosmos/cosmos-sdk/issues/4941
func IsAlgoSupported(algo SigningAlgo) bool { return algo == Secp256k1 }
// SupportedAlgos returns a list of supported signing algorithms.
func (kb baseKeybase) SupportedAlgos() []SigningAlgo {
return kb.options.supportedAlgos
}
// SupportedAlgosLedger returns a list of supported ledger signing algorithms.
func (kb baseKeybase) SupportedAlgosLedger() []SigningAlgo {
return kb.options.supportedAlgosLedger
}
// IsSupportedAlgorithm returns whether the signing algorithm is in the passed-in list of supported algorithms.
func IsSupportedAlgorithm(supported []SigningAlgo, algo SigningAlgo) bool {
for _, supportedAlgo := range supported {
if algo == supportedAlgo {
return true
}
}
return false
}

View File

@ -38,25 +38,43 @@ func TestCreateAccountInvalidMnemonic(t *testing.T) {
_, err := kb.CreateAccount(
"some_account",
"malarkey pair crucial catch public canyon evil outer stage ten gym tornado",
"", "", 0, 1)
"", "", CreateHDPath(0, 0).String(), Secp256k1)
assert.Error(t, err)
assert.Equal(t, "Invalid mnemonic", err.Error())
}
func TestCreateLedgerUnsupportedAlgo(t *testing.T) {
kb := NewInMemory()
supportedLedgerAlgos := kb.SupportedAlgosLedger()
for _, supportedAlgo := range supportedLedgerAlgos {
if Ed25519 == supportedAlgo {
assert.FailNow(t, "Was not an unsupported algorithm")
}
}
_, 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())
assert.Equal(t, "unsupported signing algo", err.Error())
}
func TestCreateLedger(t *testing.T) {
kb := NewInMemory()
kb := NewInMemory(WithSupportedAlgosLedger([]SigningAlgo{Secp256k1, Ed25519}))
// test_cover and test_unit will result in different answers
// test_cover does not compile some dependencies so ledger is disabled
// test_unit may add a ledger mock
// both cases are acceptable
supportedLedgerAlgos := kb.SupportedAlgosLedger()
secpSupported := false
edSupported := false
for _, supportedAlgo := range supportedLedgerAlgos {
secpSupported = secpSupported || (supportedAlgo == Secp256k1)
edSupported = edSupported || (supportedAlgo == Ed25519)
}
assert.True(t, secpSupported)
assert.True(t, edSupported)
ledger, err := kb.CreateLedger("some_account", Secp256k1, "cosmos", 3, 1)
if err != nil {
@ -92,7 +110,21 @@ func TestCreateLedger(t *testing.T) {
// TestKeyManagement makes sure we can manipulate these keys well
func TestKeyManagement(t *testing.T) {
// make the storage with reasonable defaults
cstore := NewInMemory()
cstore := NewInMemory(WithSupportedAlgos([]SigningAlgo{Secp256k1, Sr25519}))
// Test modified supported algos
supportedAlgos := cstore.SupportedAlgos()
secpSupported := false
edSupported := false
srSupported := false
for _, supportedAlgo := range supportedAlgos {
secpSupported = secpSupported || (supportedAlgo == Secp256k1)
edSupported = edSupported || (supportedAlgo == Ed25519)
srSupported = srSupported || (supportedAlgo == Sr25519)
}
assert.True(t, secpSupported)
assert.False(t, edSupported)
assert.True(t, srSupported)
algo := Secp256k1
n1, n2, n3 := "personal", "business", "other"
@ -152,10 +184,12 @@ func TestKeyManagement(t *testing.T) {
o1 := "offline"
priv1 := ed25519.GenPrivKey()
pub1 := priv1.PubKey()
i, err = cstore.CreateOffline(o1, pub1)
i, err = cstore.CreateOffline(o1, pub1, algo)
require.Nil(t, err)
require.Equal(t, pub1, i.GetPubKey())
require.Equal(t, o1, i.GetName())
iOffline := i.(*offlineInfo)
require.Equal(t, algo, iOffline.GetAlgo())
keyS, err = cstore.List()
require.NoError(t, err)
require.Equal(t, 2, len(keyS))
@ -393,7 +427,8 @@ func TestSeedPhrase(t *testing.T) {
// let us re-create it from the mnemonic-phrase
params := *hd.NewFundraiserParams(0, sdk.CoinType, 0)
newInfo, err := cstore.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params)
hdPath := params.String()
newInfo, err := cstore.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, Secp256k1)
require.NoError(t, err)
require.Equal(t, n2, newInfo.GetName())
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
@ -402,7 +437,11 @@ func TestSeedPhrase(t *testing.T) {
func ExampleNew() {
// Select the encryption and storage for your cryptostore
customKeyGenFunc := func(bz [32]byte) crypto.PrivKey { return secp256k1.PrivKeySecp256k1(bz) }
customKeyGenFunc := func(bz []byte, algo SigningAlgo) (crypto.PrivKey, error) {
var bzArr [32]byte
copy(bzArr[:], bz)
return secp256k1.PrivKeySecp256k1(bzArr), nil
}
cstore := NewInMemory(WithKeygenFunc(customKeyGenFunc))
sec := Secp256k1

View File

@ -20,7 +20,6 @@ import (
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/types"
@ -90,19 +89,10 @@ func (kb keyringKeybase) CreateMnemonic(
// CreateAccount converts a mnemonic to a private key and persists it, encrypted
// with the given password.
func (kb keyringKeybase) CreateAccount(
name, mnemonic, bip39Passwd, encryptPasswd string, account, index uint32,
name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo,
) (Info, error) {
return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, account, index)
}
// Derive computes a BIP39 seed from th mnemonic and bip39Passphrase. It creates
// a private key from the seed using the BIP44 params.
func (kb keyringKeybase) Derive(
name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params,
) (info Info, err error) {
return kb.base.Derive(kb, name, mnemonic, bip39Passphrase, encryptPasswd, params)
return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo)
}
// CreateLedger creates a new locally-stored reference to a Ledger keypair.
@ -116,8 +106,8 @@ func (kb keyringKeybase) CreateLedger(
// CreateOffline creates a new reference to an offline keypair. It returns the
// created key info.
func (kb keyringKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.base.writeOfflineKey(kb, name, pub), nil
func (kb keyringKeybase) CreateOffline(name string, pub tmcrypto.PubKey, algo SigningAlgo) (Info, error) {
return kb.base.writeOfflineKey(kb, name, pub, algo), nil
}
// CreateMulti creates a new reference to a multisig (offline) keypair. It
@ -284,7 +274,7 @@ func (kb keyringKeybase) ExportPubKey(name string) (armor string, err error) {
return "", fmt.Errorf("no key to export with name: %s", name)
}
return mintkey.ArmorPubKeyBytes(bz.GetPubKey().Bytes()), nil
return mintkey.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil
}
// Import imports armored private key.
@ -330,7 +320,12 @@ func (kb keyringKeybase) ExportPrivKey(name, decryptPassphrase, encryptPassphras
return "", err
}
return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase), nil
info, err := kb.Get(name)
if err != nil {
return "", err
}
return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil
}
// ImportPrivKey imports a private key in ASCII armor format. An error is returned
@ -341,13 +336,13 @@ func (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error {
return fmt.Errorf("cannot overwrite key: %s", name)
}
privKey, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
privKey, algo, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
if err != nil {
return errors.Wrap(err, "failed to decrypt private key")
}
// NOTE: The keyring keystore has no need for a passphrase.
kb.writeLocalKey(name, privKey, "")
kb.writeLocalKey(name, privKey, "", SigningAlgo(algo))
return nil
}
@ -370,7 +365,7 @@ func (kb keyringKeybase) ImportPubKey(name string, armor string) error {
}
}
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
pubBytes, algo, err := mintkey.UnarmorPubKeyBytes(armor)
if err != nil {
return err
}
@ -380,7 +375,7 @@ func (kb keyringKeybase) ImportPubKey(name string, armor string) error {
return err
}
kb.base.writeOfflineKey(kb, name, pubKey)
kb.base.writeOfflineKey(kb, name, pubKey, SigningAlgo(algo))
return nil
}
@ -419,7 +414,7 @@ func (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string,
switch linfo := info.(type) {
case localInfo:
key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
key, _, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
if err != nil {
return err
}
@ -429,7 +424,7 @@ func (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string,
return err
}
kb.writeLocalKey(name, key, newpass)
kb.writeLocalKey(name, key, newpass, linfo.GetAlgo())
return nil
default:
@ -437,13 +432,23 @@ func (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string,
}
}
// SupportedAlgos returns a list of supported signing algorithms.
func (kb keyringKeybase) SupportedAlgos() []SigningAlgo {
return kb.base.SupportedAlgos()
}
// SupportedAlgosLedger returns a list of supported ledger signing algorithms.
func (kb keyringKeybase) SupportedAlgosLedger() []SigningAlgo {
return kb.base.SupportedAlgosLedger()
}
// CloseDB releases the lock and closes the storage backend.
func (kb keyringKeybase) CloseDB() {}
func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, _ string) Info {
func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, _ string, algo SigningAlgo) Info {
// encrypt private key using keyring
pub := priv.PubKey()
info := newLocalInfo(name, pub, string(priv.Bytes()))
info := newLocalInfo(name, pub, string(priv.Bytes()), algo)
kb.writeInfo(name, info)
return info

View File

@ -79,7 +79,7 @@ func TestLazyKeyManagementKeyRing(t *testing.T) {
o1 := "offline"
priv1 := ed25519.GenPrivKey()
pub1 := priv1.PubKey()
i, err = kb.CreateOffline(o1, pub1)
i, err = kb.CreateOffline(o1, pub1, Ed25519)
require.Nil(t, err)
require.Equal(t, pub1, i.GetPubKey())
require.Equal(t, o1, i.GetName())
@ -209,10 +209,11 @@ func TestLazyExportImportPubKeyKeyRing(t *testing.T) {
defer cleanup()
kb, err := NewTestKeyring("keybasename", dir)
require.NoError(t, err)
algo := Secp256k1
// CreateMnemonic a private-public key pair and ensure consistency
notPasswd := "n9y25ah7"
info, _, err := kb.CreateMnemonic("john", English, notPasswd, Secp256k1)
info, _, err := kb.CreateMnemonic("john", English, notPasswd, algo)
require.Nil(t, err)
require.NotEqual(t, info, "")
require.Equal(t, info.GetName(), "john")
@ -318,7 +319,8 @@ func TestLazySeedPhraseKeyRing(t *testing.T) {
// let us re-create it from the mnemonic-phrase
params := *hd.NewFundraiserParams(0, sdk.CoinType, 0)
newInfo, err := kb.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params)
hdPath := params.String()
newInfo, err := kb.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, Secp256k1)
require.NoError(t, err)
require.Equal(t, n2, newInfo.GetName())
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())

View File

@ -4,9 +4,13 @@ package keys
type SigningAlgo string
const (
// MultiAlgo implies that a pubkey is a multisignature
MultiAlgo = SigningAlgo("multi")
// Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters.
Secp256k1 = SigningAlgo("secp256k1")
// Ed25519 represents the Ed25519 signature system.
// It is currently not supported for end-user keys (wallets/ledgers).
Ed25519 = SigningAlgo("ed25519")
// Sr25519 represents the Sr25519 signature system.
Sr25519 = SigningAlgo("sr25519")
)

View File

@ -6,7 +6,6 @@ import (
"github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -88,7 +87,7 @@ func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd str
return newDBKeybase(db, lkb.options...).CreateMnemonic(name, language, passwd, algo)
}
func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo) (Info, error) {
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
@ -96,17 +95,7 @@ func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd
defer db.Close()
return newDBKeybase(db,
lkb.options...).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index)
}
func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) {
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return newDBKeybase(db, lkb.options...).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params)
lkb.options...).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo)
}
func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) {
@ -119,14 +108,14 @@ func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, a
return newDBKeybase(db, lkb.options...).CreateLedger(name, algo, hrp, account, index)
}
func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) {
func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey, algo SigningAlgo) (info Info, err error) {
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
if err != nil {
return nil, err
}
defer db.Close()
return newDBKeybase(db, lkb.options...).CreateOffline(name, pubkey)
return newDBKeybase(db, lkb.options...).CreateOffline(name, pubkey, algo)
}
func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) {
@ -221,4 +210,14 @@ func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
return newDBKeybase(db, lkb.options...).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
}
// SupportedAlgos returns a list of supported signing algorithms.
func (lkb lazyKeybase) SupportedAlgos() []SigningAlgo {
return newBaseKeybase(lkb.options...).SupportedAlgos()
}
// SupportedAlgosLedger returns a list of supported ledger signing algorithms.
func (lkb lazyKeybase) SupportedAlgosLedger() []SigningAlgo {
return newBaseKeybase(lkb.options...).SupportedAlgosLedger()
}
func (lkb lazyKeybase) CloseDB() {}

View File

@ -89,7 +89,7 @@ func TestLazyKeyManagement(t *testing.T) {
o1 := "offline"
priv1 := ed25519.GenPrivKey()
pub1 := priv1.PubKey()
i, err = kb.CreateOffline(o1, pub1)
i, err = kb.CreateOffline(o1, pub1, algo)
require.Nil(t, err)
require.Equal(t, pub1, i.GetPubKey())
require.Equal(t, o1, i.GetName())
@ -245,10 +245,11 @@ func TestLazyExportImportPubKey(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := New("keybasename", dir)
algo := Secp256k1
// CreateMnemonic a private-public key pair and ensure consistency
notPasswd := "n9y25ah7"
info, _, err := kb.CreateMnemonic("john", English, notPasswd, Secp256k1)
info, _, err := kb.CreateMnemonic("john", English, notPasswd, algo)
require.Nil(t, err)
require.NotEqual(t, info, "")
require.Equal(t, info.GetName(), "john")
@ -368,7 +369,8 @@ func TestLazySeedPhrase(t *testing.T) {
// let us re-create it from the mnemonic-phrase
params := *hd.NewFundraiserParams(0, sdk.CoinType, 0)
newInfo, err := kb.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params)
hdPath := params.String()
newInfo, err := kb.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, algo)
require.NoError(t, err)
require.Equal(t, n2, newInfo.GetName())
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
@ -421,9 +423,9 @@ func TestKeygenOverride(t *testing.T) {
CryptoCdc = testCdc
overrideCalled := false
dummyFunc := func(bz [32]byte) crypto.PrivKey {
dummyFunc := func(bz []byte, algo SigningAlgo) (crypto.PrivKey, error) {
overrideCalled = true
return testPriv(bz[:])
return testPriv(bz[:]), nil
}
kb := New("keybasename", dir, WithKeygenFunc(dummyFunc))

View File

@ -20,6 +20,11 @@ const (
blockTypePrivKey = "TENDERMINT PRIVATE KEY"
blockTypeKeyInfo = "TENDERMINT KEY INFO"
blockTypePubKey = "TENDERMINT PUBLIC KEY"
defaultAlgo = "secp256k1"
headerVersion = "version"
headerType = "type"
)
// Make bcrypt security parameter var, so it can be changed within the lcd test
@ -42,36 +47,58 @@ var BcryptSecurityParameter = 12
// Armor the InfoBytes
func ArmorInfoBytes(bz []byte) string {
return armorBytes(bz, blockTypeKeyInfo)
header := map[string]string{
headerType: "Info",
headerVersion: "0.0.0",
}
return armor.EncodeArmor(blockTypeKeyInfo, header, bz)
}
// Armor the PubKeyBytes
func ArmorPubKeyBytes(bz []byte) string {
return armorBytes(bz, blockTypePubKey)
}
func armorBytes(bz []byte, blockType string) string {
func ArmorPubKeyBytes(bz []byte, algo string) string {
header := map[string]string{
"type": "Info",
"version": "0.0.0",
headerVersion: "0.0.1",
}
return armor.EncodeArmor(blockType, header, bz)
if algo != "" {
header[headerType] = algo
}
return armor.EncodeArmor(blockTypePubKey, header, bz)
}
//-----------------------------------------------------------------
// remove armor
// Unarmor the InfoBytes
func UnarmorInfoBytes(armorStr string) (bz []byte, err error) {
return unarmorBytes(armorStr, blockTypeKeyInfo)
func UnarmorInfoBytes(armorStr string) ([]byte, error) {
bz, header, err := unarmorBytes(armorStr, blockTypeKeyInfo)
if err != nil {
return nil, err
}
if header[headerVersion] != "0.0.0" {
return nil, fmt.Errorf("unrecognized version: %v", header[headerVersion])
}
return bz, nil
}
// Unarmor the PubKeyBytes
func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
return unarmorBytes(armorStr, blockTypePubKey)
// UnarmorPubKeyBytes returns the pubkey byte slice, a string of the algo type, and an error
func UnarmorPubKeyBytes(armorStr string) (bz []byte, algo string, err error) {
bz, header, err := unarmorBytes(armorStr, blockTypePubKey)
switch header[headerVersion] {
case "0.0.0":
return bz, defaultAlgo, err
case "0.0.1":
if header[headerType] == "" {
header[headerType] = defaultAlgo
}
return bz, header[headerType], err
default:
err = fmt.Errorf("unrecognized version: %v", header[headerVersion])
return nil, "", err
}
}
func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
func unarmorBytes(armorStr, blockType string) (bz []byte, header map[string]string, err error) {
bType, header, bz, err := armor.DecodeArmor(armorStr)
if err != nil {
return
@ -80,10 +107,6 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
err = fmt.Errorf("unrecognized armor type %q, expected: %q", bType, blockType)
return
}
if header["version"] != "0.0.0" {
err = fmt.Errorf("unrecognized version: %v", header["version"])
return
}
return
}
@ -91,12 +114,15 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
// encrypt/decrypt with armor
// Encrypt and armor the private key.
func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string, algo string) string {
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
header := map[string]string{
"kdf": "bcrypt",
"salt": fmt.Sprintf("%X", saltBytes),
}
if algo != "" {
header[headerType] = algo
}
armorStr := armor.EncodeArmor(blockTypePrivKey, header, encBytes)
return armorStr
}
@ -115,28 +141,31 @@ func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte
return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
}
// Unarmor and decrypt the private key.
func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
var privKey crypto.PrivKey
// UnarmorDecryptPrivKey returns the privkey byte slice, a string of the algo type, and an error
func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey crypto.PrivKey, algo string, err error) {
blockType, header, encBytes, err := armor.DecodeArmor(armorStr)
if err != nil {
return privKey, err
return privKey, "", err
}
if blockType != blockTypePrivKey {
return privKey, fmt.Errorf("unrecognized armor type: %v", blockType)
return privKey, "", fmt.Errorf("unrecognized armor type: %v", blockType)
}
if header["kdf"] != "bcrypt" {
return privKey, fmt.Errorf("unrecognized KDF type: %v", header["KDF"])
return privKey, "", fmt.Errorf("unrecognized KDF type: %v", header["KDF"])
}
if header["salt"] == "" {
return privKey, fmt.Errorf("missing salt bytes")
return privKey, "", fmt.Errorf("missing salt bytes")
}
saltBytes, err := hex.DecodeString(header["salt"])
if err != nil {
return privKey, fmt.Errorf("error decoding salt: %v", err.Error())
return privKey, "", fmt.Errorf("error decoding salt: %v", err.Error())
}
privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase)
return privKey, err
if header[headerType] == "" {
header[headerType] = defaultAlgo
}
return privKey, header[headerType], err
}
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {

View File

@ -13,11 +13,12 @@ import (
func TestArmorUnarmorPrivKey(t *testing.T) {
priv := secp256k1.GenPrivKey()
armor := mintkey.EncryptArmorPrivKey(priv, "passphrase")
_, err := mintkey.UnarmorDecryptPrivKey(armor, "wrongpassphrase")
armor := mintkey.EncryptArmorPrivKey(priv, "passphrase", "")
_, _, err := mintkey.UnarmorDecryptPrivKey(armor, "wrongpassphrase")
require.Error(t, err)
decrypted, err := mintkey.UnarmorDecryptPrivKey(armor, "passphrase")
decrypted, algo, err := mintkey.UnarmorDecryptPrivKey(armor, "passphrase")
require.NoError(t, err)
require.Equal(t, string(keys.Secp256k1), algo)
require.True(t, priv.Equals(decrypted))
}
@ -28,10 +29,11 @@ func TestArmorUnarmorPubKey(t *testing.T) {
// Add keys and see they return in alphabetical order
info, _, err := cstore.CreateMnemonic("Bob", keys.English, "passphrase", keys.Secp256k1)
require.NoError(t, err)
armor := mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes())
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
armor := mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes(), "")
pubBytes, algo, err := mintkey.UnarmorPubKeyBytes(armor)
require.NoError(t, err)
pub, err := cryptoAmino.PubKeyFromBytes(pubBytes)
require.NoError(t, err)
require.Equal(t, string(keys.Secp256k1), algo)
require.True(t, pub.Equals(info.GetPubKey()))
}

View File

@ -31,21 +31,15 @@ type Keybase interface {
// same name.
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error)
// CreateAccount converts a mnemonic to a private key using a BIP44 path 44'/118'/{account}'/0/{index}
// CreateAccount converts a mnemonic to a private key and BIP 32 HD Path
// and persists it, encrypted with the given password.
CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error)
// Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
// Derive private key from the seed using the BIP44 params.
// Encrypt the key to disk using encryptPasswd.
// See https://github.com/cosmos/cosmos-sdk/issues/2095
Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error)
CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo) (Info, error)
// CreateLedger creates, stores, and returns a new Ledger key reference
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)
CreateOffline(name string, pubkey crypto.PubKey, algo SigningAlgo) (info Info, err error)
// CreateMulti creates, stores, and returns a new multsig (offline) key reference
CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error)
@ -81,6 +75,12 @@ type Keybase interface {
// ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
// SupportedAlgos returns a list of signing algorithms supported by the keybase
SupportedAlgos() []SigningAlgo
// SupportedAlgosLedger returns a list of signing algorithms supported by the keybase's ledger integration
SupportedAlgosLedger() []SigningAlgo
// CloseDB closes the database.
CloseDB()
}
@ -118,6 +118,8 @@ type Info interface {
GetPubKey() crypto.PubKey
// Address
GetAddress() types.AccAddress
// Algo
GetAlgo() SigningAlgo
// Bip44 Path
GetPath() (*hd.BIP44Params, error)
}
@ -132,15 +134,17 @@ var (
// localInfo is the public information about a locally stored key
type localInfo struct {
Name string `json:"name"`
Algo SigningAlgo `json:"algo"`
PubKey crypto.PubKey `json:"pubkey"`
PrivKeyArmor string `json:"privkey.armor"`
}
func newLocalInfo(name string, pub crypto.PubKey, privArmor string) Info {
func newLocalInfo(name string, pub crypto.PubKey, privArmor string, algo SigningAlgo) Info {
return &localInfo{
Name: name,
PubKey: pub,
PrivKeyArmor: privArmor,
Algo: algo,
}
}
@ -164,6 +168,11 @@ func (i localInfo) GetAddress() types.AccAddress {
return i.PubKey.Address().Bytes()
}
// GetType implements Info interface
func (i localInfo) GetAlgo() SigningAlgo {
return i.Algo
}
// GetType implements Info interface
func (i localInfo) GetPath() (*hd.BIP44Params, error) {
return nil, fmt.Errorf("BIP44 Paths are not available for this type")
@ -174,13 +183,15 @@ type ledgerInfo struct {
Name string `json:"name"`
PubKey crypto.PubKey `json:"pubkey"`
Path hd.BIP44Params `json:"path"`
Algo SigningAlgo `json:"algo"`
}
func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params) Info {
func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params, algo SigningAlgo) Info {
return &ledgerInfo{
Name: name,
PubKey: pub,
Path: path,
Algo: algo,
}
}
@ -204,6 +215,11 @@ func (i ledgerInfo) GetAddress() types.AccAddress {
return i.PubKey.Address().Bytes()
}
// GetPath implements Info interface
func (i ledgerInfo) GetAlgo() SigningAlgo {
return i.Algo
}
// GetPath implements Info interface
func (i ledgerInfo) GetPath() (*hd.BIP44Params, error) {
tmp := i.Path
@ -213,13 +229,15 @@ func (i ledgerInfo) GetPath() (*hd.BIP44Params, error) {
// offlineInfo is the public information about an offline key
type offlineInfo struct {
Name string `json:"name"`
Algo SigningAlgo `json:"algo"`
PubKey crypto.PubKey `json:"pubkey"`
}
func newOfflineInfo(name string, pub crypto.PubKey) Info {
func newOfflineInfo(name string, pub crypto.PubKey, algo SigningAlgo) Info {
return &offlineInfo{
Name: name,
PubKey: pub,
Algo: algo,
}
}
@ -238,6 +256,11 @@ func (i offlineInfo) GetPubKey() crypto.PubKey {
return i.PubKey
}
// GetAlgo returns the signing algorithm for the key
func (i offlineInfo) GetAlgo() SigningAlgo {
return i.Algo
}
// GetAddress implements Info interface
func (i offlineInfo) GetAddress() types.AccAddress {
return i.PubKey.Address().Bytes()
@ -299,6 +322,11 @@ func (i multiInfo) GetAddress() types.AccAddress {
return i.PubKey.Address().Bytes()
}
// GetPath implements Info interface
func (i multiInfo) GetAlgo() SigningAlgo {
return MultiAlgo
}
// GetPath implements Info interface
func (i multiInfo) GetPath() (*hd.BIP44Params, error) {
return nil, fmt.Errorf("BIP44 Paths are not available for this type")
@ -316,8 +344,10 @@ func unmarshalInfo(bz []byte) (info Info, err error) {
}
type (
// DeriveKeyFunc defines the function to derive a new key from a seed and hd path
DeriveKeyFunc func(mnemonic string, bip39Passphrase, hdPath string, algo SigningAlgo) ([]byte, error)
// PrivKeyGenFunc defines the function to convert derived key bytes to a tendermint private key
PrivKeyGenFunc func(bz [32]byte) crypto.PrivKey
PrivKeyGenFunc func(bz []byte, algo SigningAlgo) (crypto.PrivKey, error)
// KeybaseOption overrides options for the db
KeybaseOption func(*kbOptions)

View File

@ -17,7 +17,7 @@ func Test_writeReadLedgerInfo(t *testing.T) {
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
copy(tmpKey[:], bz)
lInfo := newLedgerInfo("some_name", tmpKey, *hd.NewFundraiserParams(5, types.CoinType, 1))
lInfo := newLedgerInfo("some_name", tmpKey, *hd.NewFundraiserParams(5, types.CoinType, 1), Secp256k1)
assert.Equal(t, TypeLedger, lInfo.GetType())
path, err := lInfo.GetPath()

View File

@ -17,7 +17,7 @@ func TestGenerateCoinKey(t *testing.T) {
require.NoError(t, err)
// Test creation
info, err := keys.NewInMemoryKeyBase().CreateAccount("xxx", mnemonic, "", "012345678", 0, 0)
info, err := keys.NewInMemoryKeyBase().CreateAccount("xxx", mnemonic, "", "012345678", crkeys.CreateHDPath(0, 0).String(), crkeys.Secp256k1)
require.NoError(t, err)
require.Equal(t, addr, info.GetAddress())
}
@ -39,7 +39,7 @@ func TestGenerateSaveCoinKey(t *testing.T) {
require.Equal(t, addr, info.GetAddress())
// Test in-memory recovery
info, err = keys.NewInMemoryKeyBase().CreateAccount("xxx", mnemonic, "", "012345678", 0, 0)
info, err = keys.NewInMemoryKeyBase().CreateAccount("xxx", mnemonic, "", "012345678", crkeys.CreateHDPath(0, 0).String(), crkeys.Secp256k1)
require.NoError(t, err)
require.Equal(t, addr, info.GetAddress())
}

View File

@ -312,7 +312,7 @@ func DefaultSigVerificationGasConsumer(
var multisignature multisig.Multisignature
codec.Cdc.MustUnmarshalBinaryBare(sig, &multisignature)
consumeMultisignatureVerificationGas(meter, multisignature, pubkey, params)
ConsumeMultisignatureVerificationGas(meter, multisignature, pubkey, params)
return nil
default:
@ -320,7 +320,8 @@ func DefaultSigVerificationGasConsumer(
}
}
func consumeMultisignatureVerificationGas(meter sdk.GasMeter,
// ConsumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature
func ConsumeMultisignatureVerificationGas(meter sdk.GasMeter,
sig multisig.Multisignature, pubkey multisig.PubKeyMultisigThreshold,
params types.Params) {
size := sig.BitArray.Size()