diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e2b582c61..4d2142e26a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/client/keys/add.go b/client/keys/add.go index 47c6d57417..60f4dfe200 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -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 } diff --git a/client/keys/delete_test.go b/client/keys/delete_test.go index 7593b7f010..eeafb40baa 100644 --- a/client/keys/delete_test.go +++ b/client/keys/delete_test.go @@ -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 { diff --git a/client/keys/export_test.go b/client/keys/export_test.go index 1279ae537a..579cd829d1 100644 --- a/client/keys/export_test.go +++ b/client/keys/export_test.go @@ -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 diff --git a/client/keys/list_test.go b/client/keys/list_test.go index 129255fd54..7ea8b3fb69 100644 --- a/client/keys/list_test.go +++ b/client/keys/list_test.go @@ -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() { diff --git a/client/keys/show_test.go b/client/keys/show_test.go index 0483e06d1d..6e261bd1e7 100644 --- a/client/keys/show_test.go +++ b/client/keys/show_test.go @@ -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 diff --git a/client/keys/update_test.go b/client/keys/update_test.go index 6cfd46d92f..9d7b46c4aa 100644 --- a/client/keys/update_test.go +++ b/client/keys/update_test.go @@ -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 diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index d1b67e9142..d7fdcea400 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -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 diff --git a/crypto/keys/keybase_base.go b/crypto/keys/keybase_base.go index ea3a60bb9b..3003b08828 100644 --- a/crypto/keys/keybase_base.go +++ b/crypto/keys/keybase_base.go @@ -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 +} diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index a1dfa1c7e7..0c2396afbf 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -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 diff --git a/crypto/keys/keyring.go b/crypto/keys/keyring.go index 842db588c3..8b82df3770 100644 --- a/crypto/keys/keyring.go +++ b/crypto/keys/keyring.go @@ -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 diff --git a/crypto/keys/keyring_test.go b/crypto/keys/keyring_test.go index 84cfe3816f..1149a2691c 100644 --- a/crypto/keys/keyring_test.go +++ b/crypto/keys/keyring_test.go @@ -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()) diff --git a/crypto/keys/keys.go b/crypto/keys/keys.go index 58a58a95be..af5590814b 100644 --- a/crypto/keys/keys.go +++ b/crypto/keys/keys.go @@ -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") ) diff --git a/crypto/keys/lazy_keybase.go b/crypto/keys/lazy_keybase.go index 0c9b61c858..db0042732a 100644 --- a/crypto/keys/lazy_keybase.go +++ b/crypto/keys/lazy_keybase.go @@ -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() {} diff --git a/crypto/keys/lazy_keybase_test.go b/crypto/keys/lazy_keybase_test.go index 31a9cf242f..1b5991dae7 100644 --- a/crypto/keys/lazy_keybase_test.go +++ b/crypto/keys/lazy_keybase_test.go @@ -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)) diff --git a/crypto/keys/mintkey/mintkey.go b/crypto/keys/mintkey/mintkey.go index 6b3a3af8ad..585d148c92 100644 --- a/crypto/keys/mintkey/mintkey.go +++ b/crypto/keys/mintkey/mintkey.go @@ -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) { diff --git a/crypto/keys/mintkey/mintkey_test.go b/crypto/keys/mintkey/mintkey_test.go index b4ce4d6a0f..ef5f77d337 100644 --- a/crypto/keys/mintkey/mintkey_test.go +++ b/crypto/keys/mintkey/mintkey_test.go @@ -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())) } diff --git a/crypto/keys/types.go b/crypto/keys/types.go index 739250f98e..72fc7ecbc7 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -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) diff --git a/crypto/keys/types_test.go b/crypto/keys/types_test.go index 089eee44ce..1376c3bbb7 100644 --- a/crypto/keys/types_test.go +++ b/crypto/keys/types_test.go @@ -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() diff --git a/server/init_test.go b/server/init_test.go index 752540825a..2a447eef4f 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -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()) } diff --git a/x/auth/ante/sigverify.go b/x/auth/ante/sigverify.go index 5a6eb74746..4498b432d7 100644 --- a/x/auth/ante/sigverify.go +++ b/x/auth/ante/sigverify.go @@ -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()