Enter the new keyring interface (#5904)

crypto/keyring:

`Keybase` interface gives way to its successor: `Keyring`. `LegacyKeybase`
interface is added in order to guarantee limited backward compatibility with
the old `Keybase` interface for the sole purpose of migrating keys across
the new keyring backends.

The package no longer depends on the `github.com/types.Config`
singleton.

`SupportedAlgos` and `SupportedLedgerAlgos` methods have been removed.
The keyring just fails when trying to perform an action with an unsupported
algorithm.

crypto/ subdirs reorganization:

`crypto/keys/hd` was moved to `crypto/hd`, which now groups together
all HD wallets related types and utilities.

client/input:

* Removal of unnecessary `GetCheckPassword`, `PrintPrefixed` functions.
* `GetConfirmation`'s signature changed to take in a io.Writer for better integration
  with `cobra.Command` types.

client/context:

* In-memory keyring is allocated in the context when `--gen-only` flag is passed
  in. `GetFromFields` does no longer silently allocate a keyring, it takes one as
  argument.

Co-authored with @jgimeno

Co-authored-by: Jonathan Gimeno <jgimeno@gmail.com>
This commit is contained in:
Alessio Treglia 2020-04-08 11:38:28 +02:00 committed by GitHub
parent 7325692550
commit a1feca39c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 1829 additions and 1473 deletions

View File

@ -70,19 +70,22 @@ and provided directly the IAVL store.
* (modules) [\#5572](https://github.com/cosmos/cosmos-sdk/pull/5572) Move account balance logic and APIs from `x/auth` to `x/bank`.
* (types) [\#5533](https://github.com/cosmos/cosmos-sdk/pull/5533) Refactored `AppModuleBasic` and `AppModuleGenesis`
to now accept a `codec.JSONMarshaler` for modular serialization of genesis state.
* (crypto/keyring) [\#5735](https://github.com/cosmos/cosmos-sdk/pull/5735) Keyring's `Update()` function is now no-op.
* (types/rest) [\#5779](https://github.com/cosmos/cosmos-sdk/pull/5779) Drop unused Parse{Int64OrReturnBadRequest,QueryParamBool}() functions.
* (keys) [\#5820](https://github.com/cosmos/cosmos-sdk/pull/5820/) Removed method CloseDB from Keybase interface.
* (baseapp) [\#5837](https://github.com/cosmos/cosmos-sdk/issues/5837) Transaction simulation now returns a `SimulationResponse` which contains the `GasInfo` and
`Result` from the execution.
* (crypto/keyring) [\#5866](https://github.com/cosmos/cosmos-sdk/pull/5866) Move `Keyring` and `Keybase` implementations and their associated types from `crypto/keys/` to `crypto/keyring/`.
* (crypto) [\#5880](https://github.com/cosmos/cosmos-sdk/pull/5880) Merge `crypto/keys/mintkey` into `crypto`.
* (crypto/keyring) [\#5858](https://github.com/cosmos/cosmos-sdk/pull/5858) Make Keyring store keys by name and address's hexbytes representation.
* (crypto/keyring) [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) Deprecate old keybase implementation:
- Remove `Update` from the `Keybase` interface.
- `NewKeyring()` now accepts a new backend: `MemoryBackend`.
- `New()` has been renamed to`NewLegacy()`, which now returns a `LegacyKeybase` type that only allows migration of keys from the legacy keybase to a new keyring.
* (client/input) [\#5904](https://github.com/cosmos/cosmos-sdk/pull/5904) Removal of unnecessary `GetCheckPassword`, `PrintPrefixed` functions.
* (client/keys) [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) Rename `NewKeyBaseFromDir()` -> `NewLegacyKeyBaseFromDir()`.
* (crypto) [\#5880](https://github.com/cosmos/cosmos-sdk/pull/5880) Merge `crypto/keys/mintkey` into `crypto`.
* (crypto/hd) [\#5904](https://github.com/cosmos/cosmos-sdk/pull/5904) `crypto/keys/hd` moved to `crypto/hd`.
* (crypto/keyring):
- [\#5866](https://github.com/cosmos/cosmos-sdk/pull/5866) Rename `crypto/keys/` to `crypto/keyring/`.
- [\#5904](https://github.com/cosmos/cosmos-sdk/pull/5904) `Keybase` -> `Keyring` interfaces migration. `LegacyKeybase` interface is added in order
to guarantee limited backward compatibility with the old Keybase interface for the sole purpose of migrating keys across the new keyring backends. `NewLegacy`
constructor is provided [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) to allow for smooth migration of keys from the legacy LevelDB based implementation
to new keyring backends. Plus, the package and the new keyring no longer depends on the sdk.Config singleton. Please consult the package documentation for more
information on how to implement the new `Keyring` interface.
- [\#5858](https://github.com/cosmos/cosmos-sdk/pull/5858) Make Keyring store keys by name and address's hexbytes representation.
### Features
@ -92,8 +95,8 @@ to now accept a `codec.JSONMarshaler` for modular serialization of genesis state
* (types) [\#5741](https://github.com/cosmos/cosmos-sdk/issues/5741) Prevent ChainAnteDecorators() from panicking when empty AnteDecorator slice is supplied.
* (modules) [\#5569](https://github.com/cosmos/cosmos-sdk/issues/5569) `InitGenesis`, for the relevant modules, now ensures module accounts exist.
* (crypto/keyring) [\#5844](https://github.com/cosmos/cosmos-sdk/pull/5844) Keybase/Keyring `Sign()` methods no longer decode amino signatures
when method receivers are offline/multisig keys.
* (crypto/keyring) [\#5844](https://github.com/cosmos/cosmos-sdk/pull/5844) `Keyring.Sign()` methods no longer decode amino signatures when method receivers
are offline/multisig keys.
* (x/auth) [\#5892](https://github.com/cosmos/cosmos-sdk/pull/5892) Add `RegisterKeyTypeCodec` to register new
types (eg. keys) to the `auth` module internal amino codec.
* (rest) [\#5906](https://github.com/cosmos/cosmos-sdk/pull/5906) Fix an issue that make some REST calls panic when sending

View File

@ -25,8 +25,8 @@ type CLIContext struct {
Client rpcclient.Client
ChainID string
Marshaler codec.Marshaler
Keybase keyring.Keybase
Input io.Reader
Keyring keyring.Keyring
Output io.Writer
OutputFormat string
Height int64
@ -58,8 +58,19 @@ func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext {
var nodeURI string
var rpc rpcclient.Client
homedir := viper.GetString(flags.FlagHome)
genOnly := viper.GetBool(flags.FlagGenerateOnly)
fromAddress, fromName, err := GetFromFields(input, from, genOnly)
backend := viper.GetString(flags.FlagKeyringBackend)
if len(backend) == 0 {
backend = keyring.BackendMemory
}
keyring, err := newKeyringFromFlags(backend, homedir, input, genOnly)
if err != nil {
panic(fmt.Errorf("couldn't acquire keyring: %v", err))
}
fromAddress, fromName, err := GetFromFields(keyring, from, genOnly)
if err != nil {
fmt.Printf("failed to get from fields: %v\n", err)
os.Exit(1)
@ -84,9 +95,10 @@ func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext {
Output: os.Stdout,
NodeURI: nodeURI,
From: viper.GetString(flags.FlagFrom),
Keyring: keyring,
OutputFormat: viper.GetString(cli.OutputFlag),
Height: viper.GetInt64(flags.FlagHeight),
HomeDir: viper.GetString(flags.FlagHome),
HomeDir: homedir,
TrustNode: viper.GetBool(flags.FlagTrustNode),
UseLedger: viper.GetBool(flags.FlagUseLedger),
BroadcastMode: viper.GetString(flags.FlagBroadcastMode),
@ -129,6 +141,12 @@ func NewCLIContextWithInput(input io.Reader) CLIContext {
return NewCLIContextWithInputAndFrom(input, viper.GetString(flags.FlagFrom))
}
// WithKeyring returns a copy of the context with an updated keyring.
func (ctx CLIContext) WithKeyring(k keyring.Keyring) CLIContext {
ctx.Keyring = k
return ctx
}
// WithInput returns a copy of the context with an updated input.
func (ctx CLIContext) WithInput(r io.Reader) CLIContext {
ctx.Input = r
@ -307,7 +325,7 @@ func (ctx CLIContext) PrintOutput(toPrint interface{}) error {
// GetFromFields returns a from account address and Keybase name given either
// an address or key name. If genOnly is true, only a valid Bech32 cosmos
// address is returned.
func GetFromFields(input io.Reader, from string, genOnly bool) (sdk.AccAddress, string, error) {
func GetFromFields(kr keyring.Keyring, from string, genOnly bool) (sdk.AccAddress, string, error) {
if from == "" {
return nil, "", nil
}
@ -321,20 +339,14 @@ func GetFromFields(input io.Reader, from string, genOnly bool) (sdk.AccAddress,
return addr, "", nil
}
keybase, err := keyring.NewKeyring(sdk.KeyringServiceName(),
viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), input)
if err != nil {
return nil, "", err
}
var info keyring.Info
if addr, err := sdk.AccAddressFromBech32(from); err == nil {
info, err = keybase.GetByAddress(addr)
info, err = kr.KeyByAddress(addr)
if err != nil {
return nil, "", err
}
} else {
info, err = keybase.Get(from)
info, err = kr.Key(from)
if err != nil {
return nil, "", err
}
@ -342,3 +354,10 @@ func GetFromFields(input io.Reader, from string, genOnly bool) (sdk.AccAddress,
return info.GetAddress(), info.GetName(), nil
}
func newKeyringFromFlags(backend, homedir string, input io.Reader, genOnly bool) (keyring.Keyring, error) {
if genOnly {
return keyring.New(sdk.KeyringServiceName(), keyring.BackendMemory, homedir, input)
}
return keyring.New(sdk.KeyringServiceName(), backend, homedir, input)
}

View File

@ -1,13 +1,16 @@
package context
package context_test
import (
"os"
"testing"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
)
@ -15,7 +18,7 @@ func TestCLIContext_WithOffline(t *testing.T) {
viper.Set(flags.FlagOffline, true)
viper.Set(flags.FlagNode, "tcp://localhost:26657")
ctx := NewCLIContext()
ctx := context.NewCLIContext()
require.True(t, ctx.Offline)
require.Nil(t, ctx.Client)
@ -24,7 +27,7 @@ func TestCLIContext_WithOffline(t *testing.T) {
viper.Set(flags.FlagOffline, false)
viper.Set(flags.FlagNode, "tcp://localhost:26657")
ctx = NewCLIContext()
ctx = context.NewCLIContext()
require.False(t, ctx.Offline)
require.NotNil(t, ctx.Client)
}
@ -59,10 +62,26 @@ func TestCLIContext_WithGenOnly(t *testing.T) {
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
ctx := NewCLIContextWithFrom(tt.from)
ctx := context.NewCLIContextWithFrom(tt.from)
require.Equal(t, tt.expectedFromAddr, ctx.FromAddress)
require.Equal(t, tt.expectedFromName, ctx.FromName)
})
}
}
func TestCLIContext_WithKeyring(t *testing.T) {
viper.Set(flags.FlagGenerateOnly, true)
ctx := context.NewCLIContextWithFrom("cosmos1q7380u26f7ntke3facjmynajs4umlr329vr4ja")
require.NotNil(t, ctx.Keyring)
kr := ctx.Keyring
ctx = ctx.WithKeyring(nil)
require.Nil(t, ctx.Keyring)
ctx = ctx.WithKeyring(kr)
require.Equal(t, kr, ctx.Keyring)
}
func TestMain(m *testing.M) {
viper.Set(flags.FlagKeyringBackend, keyring.BackendMemory)
os.Exit(m.Run())
}

View File

@ -2,8 +2,8 @@ package input
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"strings"
@ -36,40 +36,15 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
return pass, nil
}
// GetCheckPassword will prompt for a password twice to verify they
// match (for creating a new password).
// It enforces the password length. Only parses password once if
// input is piped in.
func GetCheckPassword(prompt, prompt2 string, buf *bufio.Reader) (string, error) {
// simple read on no-tty
if !inputIsTty() {
return GetPassword(prompt, buf)
}
// TODO: own function???
pass, err := GetPassword(prompt, buf)
if err != nil {
return "", err
}
pass2, err := GetPassword(prompt2, buf)
if err != nil {
return "", err
}
if pass != pass2 {
return "", errors.New("passphrases don't match")
}
return pass, nil
}
// GetConfirmation will request user give the confirmation from stdin.
// "y", "Y", "yes", "YES", and "Yes" all count as confirmations.
// If the input is not recognized, it returns false and a nil error.
func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) {
func GetConfirmation(prompt string, r *bufio.Reader, w io.Writer) (bool, error) {
if inputIsTty() {
fmt.Printf("%s [y/N]: ", prompt)
}
response, err := readLineFromBuf(buf)
response, err := readLineFromBuf(r)
if err != nil {
return false, err
}
@ -90,7 +65,7 @@ func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) {
// GetString simply returns the trimmed string output of a given reader.
func GetString(prompt string, buf *bufio.Reader) (string, error) {
if inputIsTty() && prompt != "" {
PrintPrefixed(prompt)
fmt.Fprintf(os.Stderr, "> %s\n", prompt)
}
out, err := readLineFromBuf(buf)
@ -117,9 +92,3 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) {
}
return strings.TrimSpace(pass), nil
}
// PrintPrefixed prints a string with > prefixed for use in prompts.
func PrintPrefixed(msg string) {
msg = fmt.Sprintf("> %s\n", msg)
fmt.Fprint(os.Stderr, msg)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -27,6 +28,7 @@ const (
flagInteractive = "interactive"
flagRecover = "recover"
flagNoBackup = "no-backup"
flagCoinType = "coin-type"
flagAccount = "account"
flagIndex = "index"
flagMultisig = "multisig"
@ -73,19 +75,20 @@ the flag --nosort is set.
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
cmd.Flags().Bool(flags.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(flagCoinType, sdk.CoinType, "coin type number for HD derivation")
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(keyring.Secp256k1), "Key signing algorithm to generate keys for")
cmd.Flags().String(flagKeyAlgo, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for")
return cmd
}
func getKeybase(transient bool, buf io.Reader) (keyring.Keybase, error) {
func getKeybase(transient bool, buf io.Reader) (keyring.Keyring, error) {
if transient {
return keyring.NewKeyring(sdk.KeyringServiceName(), keyring.BackendMemory, viper.GetString(flags.FlagHome), buf)
return keyring.New(sdk.KeyringServiceName(), keyring.BackendMemory, viper.GetString(flags.FlagHome), buf)
}
return keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
return keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
}
func runAddCmd(cmd *cobra.Command, args []string) error {
@ -107,7 +110,7 @@ input
output
- armor encrypted private key (saved to file)
*/
func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *bufio.Reader) error {
func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keyring, inBuf *bufio.Reader) error {
var err error
name := args[0]
@ -115,25 +118,27 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf
interactive := viper.GetBool(flagInteractive)
showMnemonic := !viper.GetBool(flagNoBackup)
algo := keyring.SigningAlgo(viper.GetString(flagKeyAlgo))
if algo == keyring.SigningAlgo("") {
algo = keyring.Secp256k1
}
if !keyring.IsSupportedAlgorithm(kb.SupportedAlgos(), algo) {
return keyring.ErrUnsupportedSigningAlgo
algo, err := keyring.NewSigningAlgoFromString(viper.GetString(flagKeyAlgo))
if err != nil {
algo = hd.Secp256k1
}
if !viper.GetBool(flags.FlagDryRun) {
_, err = kb.Get(name)
_, err = kb.Key(name)
if err == nil {
// account exists, ask for user confirmation
response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", name), inBuf)
response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", name), inBuf, cmd.ErrOrStderr())
if err2 != nil {
return err2
}
if !response {
return errors.New("aborted")
}
err2 = kb.Delete(name)
if err2 != nil {
return err2
}
}
multisigKeys := viper.GetStringSlice(flagMultisig)
@ -146,7 +151,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf
}
for _, keyname := range multisigKeys {
k, err := kb.Get(keyname)
k, err := kb.Key(keyname)
if err != nil {
return err
}
@ -161,7 +166,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf
}
pk := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks)
if _, err := kb.CreateMulti(name, pk); err != nil {
if _, err := kb.SaveMultisig(name, pk); err != nil {
return err
}
@ -175,13 +180,14 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf
if err != nil {
return err
}
_, err = kb.CreateOffline(name, pk, algo)
_, err = kb.SavePubKey(name, pk, algo.Name())
if err != nil {
return err
}
return nil
}
coinType := uint32(viper.GetInt(flagCoinType))
account := uint32(viper.GetInt(flagAccount))
index := uint32(viper.GetInt(flagIndex))
@ -189,7 +195,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf
var hdPath string
if useBIP44 {
hdPath = keyring.CreateHDPath(account, index).String()
hdPath = hd.CreateHDPath(coinType, account, index).String()
} else {
hdPath = viper.GetString(flagHDPath)
}
@ -201,12 +207,8 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf
return errors.New("cannot set custom bip32 path with ledger")
}
if !keyring.IsSupportedAlgorithm(kb.SupportedAlgosLedger(), algo) {
return keyring.ErrUnsupportedSigningAlgo
}
bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix()
info, err := kb.CreateLedger(name, keyring.Secp256k1, bech32PrefixAccAddr, account, index)
info, err := kb.SaveLedgerKey(name, hd.Secp256k1, bech32PrefixAccAddr, coinType, account, index)
if err != nil {
return err
}
@ -269,7 +271,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf
}
}
info, err := kb.CreateAccount(name, mnemonic, bip39Passphrase, DefaultKeyPass, hdPath, algo)
info, err := kb.NewAccount(name, mnemonic, bip39Passphrase, hdPath, algo)
if err != nil {
return err
}

View File

@ -41,6 +41,9 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) {
t.Cleanup(kbCleanUp)
viper.Set(flags.FlagHome, kbHome)
viper.Set(flags.FlagUseLedger, true)
viper.Set(flagAccount, "0")
viper.Set(flagIndex, "0")
viper.Set(flagCoinType, "330")
/// Test Text
viper.Set(cli.OutputFlag, OutputFormatText)
@ -50,14 +53,14 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) {
require.NoError(t, runAddCmd(cmd, []string{"keyname1"}))
// Now check that it has been stored properly
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
require.NoError(t, err)
require.NotNil(t, kb)
t.Cleanup(func() {
kb.Delete("keyname1", "", false)
kb.Delete("keyname1")
})
mockIn.Reset("test1234\n")
key1, err := kb.Get("keyname1")
key1, err := kb.Key("keyname1")
require.NoError(t, err)
require.NotNil(t, key1)
@ -90,17 +93,18 @@ func Test_runAddCmdLedger(t *testing.T) {
viper.Set(cli.OutputFlag, OutputFormatText)
// Now enter password
mockIn.Reset("test1234\ntest1234\n")
viper.Set(flagCoinType, sdk.CoinType)
require.NoError(t, runAddCmd(cmd, []string{"keyname1"}))
// Now check that it has been stored properly
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
require.NoError(t, err)
require.NotNil(t, kb)
t.Cleanup(func() {
kb.Delete("keyname1", "", false)
kb.Delete("keyname1")
})
mockIn.Reset("test1234\n")
key1, err := kb.Get("keyname1")
key1, err := kb.Key("keyname1")
require.NoError(t, err)
require.NotNil(t, key1)

View File

@ -4,7 +4,6 @@ import (
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/cli"
@ -17,36 +16,37 @@ import (
func Test_runAddCmdBasic(t *testing.T) {
cmd := AddKeyCommand()
assert.NotNil(t, cmd)
require.NotNil(t, cmd)
mockIn, _, _ := tests.ApplyMockIO(cmd)
kbHome, kbCleanUp := tests.NewTestCaseDir(t)
assert.NotNil(t, kbHome)
require.NotNil(t, kbHome)
t.Cleanup(kbCleanUp)
viper.Set(flags.FlagHome, kbHome)
viper.Set(cli.OutputFlag, OutputFormatText)
viper.Set(flags.FlagUseLedger, false)
mockIn.Reset("y\n")
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
require.NoError(t, err)
t.Cleanup(func() {
kb.Delete("keyname1", "", false) // nolint:errcheck
kb.Delete("keyname2", "", false) // nolint:errcheck
kb.Delete("keyname1") // nolint:errcheck
kb.Delete("keyname2") // nolint:errcheck
})
assert.NoError(t, runAddCmd(cmd, []string{"keyname1"}))
require.NoError(t, runAddCmd(cmd, []string{"keyname1"}))
mockIn.Reset("N\n")
assert.Error(t, runAddCmd(cmd, []string{"keyname1"}))
require.Error(t, runAddCmd(cmd, []string{"keyname1"}))
assert.NoError(t, runAddCmd(cmd, []string{"keyname2"}))
assert.Error(t, runAddCmd(cmd, []string{"keyname2"}))
require.NoError(t, runAddCmd(cmd, []string{"keyname2"}))
require.Error(t, runAddCmd(cmd, []string{"keyname2"}))
mockIn.Reset("y\n")
assert.NoError(t, runAddCmd(cmd, []string{"keyname2"}))
require.NoError(t, runAddCmd(cmd, []string{"keyname2"}))
// test --dry-run
assert.NoError(t, runAddCmd(cmd, []string{"keyname4"}))
assert.Error(t, runAddCmd(cmd, []string{"keyname4"}))
require.NoError(t, runAddCmd(cmd, []string{"keyname4"}))
require.Error(t, runAddCmd(cmd, []string{"keyname4"}))
viper.Set(flags.FlagDryRun, true)
assert.NoError(t, runAddCmd(cmd, []string{"keyname4"}))
require.NoError(t, runAddCmd(cmd, []string{"keyname4"}))
}

View File

@ -1,12 +1,12 @@
package keys
package keys_test
import (
"fmt"
"reflect"
"bytes"
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
)
@ -58,15 +58,9 @@ func TestMarshalJSON(t *testing.T) {
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got, err := MarshalJSON(tt.args.o)
if (err != nil) != tt.wantErr {
t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
fmt.Printf("%s\n", got)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MarshalJSON() = %v, want %v", got, tt.want)
}
got, err := keys.MarshalJSON(tt.args.o)
require.Equal(t, tt.wantErr, err != nil)
require.True(t, bytes.Equal(got, tt.want))
})
}
}
@ -94,10 +88,8 @@ func TestUnmarshalJSON(t *testing.T) {
for idx, tt := range tests {
idx, tt := idx, tt
t.Run(tt.name, func(t *testing.T) {
if err := UnmarshalJSON(tt.args.bz, tt.args.ptr); (err != nil) != tt.wantErr {
t.Errorf("unmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
err := keys.UnmarshalJSON(tt.args.bz, tt.args.ptr)
require.Equal(t, tt.wantErr, err != nil)
// Confirm deserialized objects are the same
require.Equal(t, data.Keys[idx], data.Answers[idx])
})

View File

@ -2,7 +2,6 @@ package keys
import (
"bufio"
"errors"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
@ -43,49 +42,36 @@ private keys stored in a ledger device cannot be deleted with the CLI.
func runDeleteCmd(cmd *cobra.Command, args []string) error {
buf := bufio.NewReader(cmd.InOrStdin())
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
if err != nil {
return err
}
for _, name := range args {
info, err := kb.Get(name)
info, err := kb.Key(name)
if err != nil {
return err
}
if info.GetType() == keyring.TypeLedger || info.GetType() == keyring.TypeOffline {
// confirm deletion, unless -y is passed
if !viper.GetBool(flagYes) {
if err := confirmDeletion(buf); err != nil {
if yes, err := input.GetConfirmation("Key reference will be deleted. Continue?", buf, cmd.ErrOrStderr()); err != nil {
return err
} else if !yes {
continue
}
}
if err := kb.Delete(name, "", true); err != nil {
if err := kb.Delete(name); err != nil {
return err
}
if info.GetType() == keyring.TypeLedger || info.GetType() == keyring.TypeOffline {
cmd.PrintErrln("Public key reference deleted")
return nil
}
// old password and skip flag arguments are ignored
if err := kb.Delete(name, "", true); err != nil {
return err
continue
}
cmd.PrintErrln("Key deleted forever (uh oh!)")
}
return nil
}
func confirmDeletion(buf *bufio.Reader) error {
answer, err := input.GetConfirmation("Key reference will be deleted. Continue?", buf)
if err != nil {
return err
}
if !answer {
return errors.New("aborted")
}
return nil
}

View File

@ -1,13 +1,13 @@
package keys
import (
"bufio"
"strings"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/tests"
@ -20,30 +20,25 @@ func Test_runDeleteCmd(t *testing.T) {
yesF, _ := deleteKeyCommand.Flags().GetBool(flagYes)
forceF, _ := deleteKeyCommand.Flags().GetBool(flagForce)
require.False(t, yesF)
require.False(t, forceF)
fakeKeyName1 := "runDeleteCmd_Key1"
fakeKeyName2 := "runDeleteCmd_Key2"
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
require.NoError(t, err)
t.Cleanup(func() {
kb.Delete("runDeleteCmd_Key1", "", false) // nolint:errcheck
kb.Delete("runDeleteCmd_Key2", "", false) // nolint:errcheck
})
// Now add a temporary keybase
kbHome, cleanUp := tests.NewTestCaseDir(t)
t.Cleanup(cleanUp)
viper.Set(flags.FlagHome, kbHome)
// Now
kb, err = keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
path := sdk.GetConfig().GetFullFundraiserPath()
backend := viper.GetString(flags.FlagKeyringBackend)
kb, err := keyring.New(sdk.KeyringServiceName(), backend, kbHome, mockIn)
require.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keyring.Secp256k1)
_, err = kb.NewAccount(fakeKeyName1, tests.TestMnemonic, "", path, hd.Secp256k1)
require.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keyring.Secp256k1)
_, _, err = kb.NewMnemonic(fakeKeyName2, keyring.English, sdk.FullFundraiserPath, hd.Secp256k1)
require.NoError(t, err)
err = runDeleteCmd(deleteKeyCommand, []string{"blah"})
@ -55,53 +50,21 @@ func Test_runDeleteCmd(t *testing.T) {
require.Error(t, err)
require.Equal(t, "EOF", err.Error())
{
_, err = kb.Get(fakeKeyName1)
_, err = kb.Key(fakeKeyName1)
require.NoError(t, err)
// Now there is a confirmation
viper.Set(flagYes, true)
require.NoError(t, runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1}))
_, err = kb.Get(fakeKeyName1)
_, err = kb.Key(fakeKeyName1)
require.Error(t, err) // Key1 is gone
}
viper.Set(flagYes, true)
_, err = kb.Get(fakeKeyName2)
_, err = kb.Key(fakeKeyName2)
require.NoError(t, err)
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName2})
require.NoError(t, err)
_, err = kb.Get(fakeKeyName2)
_, err = kb.Key(fakeKeyName2)
require.Error(t, err) // Key2 is gone
}
func Test_confirmDeletion(t *testing.T) {
type args struct {
buf *bufio.Reader
}
answerYes := bufio.NewReader(strings.NewReader("y\n"))
answerYes2 := bufio.NewReader(strings.NewReader("Y\n"))
answerNo := bufio.NewReader(strings.NewReader("n\n"))
answerInvalid := bufio.NewReader(strings.NewReader("245\n"))
tests := []struct {
name string
args args
wantErr bool
}{
{"Y", args{answerYes}, false},
{"y", args{answerYes2}, false},
{"N", args{answerNo}, true},
{"BAD", args{answerInvalid}, true},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
if err := confirmDeletion(tt.args.buf); (err != nil) != tt.wantErr {
t.Errorf("confirmDeletion() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -25,21 +25,17 @@ func ExportKeyCommand() *cobra.Command {
func runExportCmd(cmd *cobra.Command, args []string) error {
buf := bufio.NewReader(cmd.InOrStdin())
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
if err != nil {
return err
}
decryptPassword, err := input.GetPassword("Enter passphrase to decrypt your key:", buf)
if err != nil {
return err
}
encryptPassword, err := input.GetPassword("Enter passphrase to encrypt the exported key:", buf)
if err != nil {
return err
}
armored, err := kb.ExportPrivKey(args[0], decryptPassword, encryptPassword)
armored, err := kb.ExportPrivKeyArmor(args[0], encryptPassword)
if err != nil {
return err
}

View File

@ -6,6 +6,8 @@ import (
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/tests"
@ -22,13 +24,14 @@ func Test_runExportCmd(t *testing.T) {
viper.Set(flags.FlagHome, kbHome)
// create a key
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
require.NoError(t, err)
t.Cleanup(func() {
kb.Delete("keyname1", "", false) // nolint:errcheck
kb.Delete("keyname1") // nolint:errcheck
})
_, err = kb.CreateAccount("keyname1", tests.TestMnemonic, "", "123456789", "", keyring.Secp256k1)
path := sdk.GetConfig().GetFullFundraiserPath()
_, err = kb.NewAccount("keyname1", tests.TestMnemonic, "", path, hd.Secp256k1)
require.NoError(t, err)
// Now enter password

View File

@ -26,7 +26,7 @@ func ImportKeyCommand() *cobra.Command {
func runImportCmd(cmd *cobra.Command, args []string) error {
buf := bufio.NewReader(cmd.InOrStdin())
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
if err != nil {
return err
}

View File

@ -23,10 +23,10 @@ func Test_runImportCmd(t *testing.T) {
t.Cleanup(cleanUp)
viper.Set(flags.FlagHome, kbHome)
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
require.NoError(t, err)
t.Cleanup(func() {
kb.Delete("keyname1", "", false) // nolint:errcheck
kb.Delete("keyname1") // nolint:errcheck
})
keyfile := filepath.Join(kbHome, "key.asc")

View File

@ -26,7 +26,7 @@ along with their associated name and address.`,
}
func runListCmd(cmd *cobra.Command, _ []string) error {
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin())
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin())
if err != nil {
return err
}

View File

@ -7,6 +7,8 @@ import (
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/tests"
@ -31,14 +33,15 @@ func Test_runListCmd(t *testing.T) {
viper.Set(flags.FlagHome, kbHome2)
mockIn, _, _ := tests.ApplyMockIO(cmdBasic)
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
require.NoError(t, err)
_, err = kb.CreateAccount("something", tests.TestMnemonic, "", "", "", keyring.Secp256k1)
path := "" //sdk.GetConfig().GetFullFundraiserPath()
_, err = kb.NewAccount("something", tests.TestMnemonic, "", path, hd.Secp256k1)
require.NoError(t, err)
t.Cleanup(func() {
kb.Delete("something", "", false) // nolint:errcheck
kb.Delete("something") // nolint:errcheck
})
testData := []struct {

View File

@ -61,20 +61,20 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
var (
tmpDir string
keybase keyring.Keybase
migrator keyring.InfoImporter
)
if viper.GetBool(flags.FlagDryRun) {
tmpDir, err = ioutil.TempDir("", "keybase-migrate-dryrun")
tmpDir, err = ioutil.TempDir("", "migrator-migrate-dryrun")
if err != nil {
return errors.Wrap(err, "failed to create temporary directory for dryrun migration")
}
defer os.RemoveAll(tmpDir)
keybase, err = keyring.NewKeyring(keyringServiceName, "test", tmpDir, buf)
migrator, err = keyring.NewInfoImporter(keyringServiceName, "test", tmpDir, buf)
} else {
keybase, err = keyring.NewKeyring(keyringServiceName, viper.GetString(flags.FlagKeyringBackend), rootDir, buf)
migrator, err = keyring.NewInfoImporter(keyringServiceName, viper.GetString(flags.FlagKeyringBackend), rootDir, buf)
}
if err != nil {
return errors.Wrap(err, fmt.Sprintf(
@ -92,16 +92,10 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
keyName := key.GetName()
keyType := key.GetType()
// skip key if already migrated
if _, err := keybase.Get(keyName); err == nil {
cmd.PrintErrf("Key '%s (%s)' already exists; skipping ...\n", key.GetName(), keyType)
continue
}
cmd.PrintErrf("Migrating key: '%s (%s)' ...\n", key.GetName(), keyType)
// allow user to skip migrating specific keys
ok, err := input.GetConfirmation("Skip key migration?", buf)
ok, err := input.GetConfirmation("Skip key migration?", buf, cmd.ErrOrStderr())
if err != nil {
return err
}
@ -110,7 +104,7 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
}
if keyType != keyring.TypeLocal {
if err := keybase.Import(keyName, legKeyInfo); err != nil {
if err := migrator.Import(keyName, legKeyInfo); err != nil {
return err
}
@ -130,7 +124,7 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
return err
}
if err := keybase.ImportPrivKey(keyName, armoredPriv, migratePassphrase); err != nil {
if err := migrator.Import(keyName, armoredPriv); err != nil {
return err
}
}

View File

@ -3,6 +3,8 @@ package keys
import (
"testing"
"github.com/otiai10/copy"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/tests"
@ -18,18 +20,18 @@ func Test_runMigrateCmd(t *testing.T) {
mockIn, _, _ := tests.ApplyMockIO(cmd)
kbHome, kbCleanUp := tests.NewTestCaseDir(t)
copy.Copy("testdata", kbHome)
assert.NotNil(t, kbHome)
t.Cleanup(kbCleanUp)
viper.Set(flags.FlagHome, kbHome)
viper.Set(cli.OutputFlag, OutputFormatText)
mockIn.Reset("test1234\ntest1234\n")
assert.NoError(t, runAddCmd(cmd, []string{"keyname1"}))
viper.Set(flags.FlagDryRun, true)
cmd = MigrateCommand()
mockIn, _, _ = tests.ApplyMockIO(cmd)
mockIn.Reset("test1234\n")
mockIn.Reset("test1234\ntest1234\n")
assert.NoError(t, runMigrateCmd(cmd, []string{}))
}

View File

@ -46,7 +46,7 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
if len(inputEntropy) < 43 {
return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy))
}
conf, err := input.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf)
conf, err := input.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf, cmd.ErrOrStderr())
if err != nil {
return err
}

View File

@ -9,6 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestCommands(t *testing.T) {
@ -21,5 +22,6 @@ func TestCommands(t *testing.T) {
func TestMain(m *testing.M) {
viper.Set(flags.FlagKeyringBackend, keyring.BackendTest)
viper.Set(flagCoinType, sdk.CoinType)
os.Exit(m.Run())
}

View File

@ -57,7 +57,7 @@ consisting of all the keys provided by name and multisig threshold.`,
func runShowCmd(cmd *cobra.Command, args []string) (err error) {
var info keyring.Info
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin())
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin())
if err != nil {
return err
}
@ -142,15 +142,15 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) {
return nil
}
func fetchKey(kb keyring.Keybase, keyref string) (keyring.Info, error) {
info, err := kb.Get(keyref)
func fetchKey(kb keyring.Keyring, keyref string) (keyring.Info, error) {
info, err := kb.Key(keyref)
if err != nil {
accAddr, err := sdk.AccAddressFromBech32(keyref)
if err != nil {
return info, err
}
info, err = kb.GetByAddress(accAddr)
info, err = kb.KeyByAddress(accAddr)
if err != nil {
return info, errors.New("key not found")
}

View File

@ -11,6 +11,7 @@ import (
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -24,7 +25,7 @@ func Test_multiSigKey_Properties(t *testing.T) {
require.Equal(t, "myMultisig", tmp.GetName())
require.Equal(t, keyring.TypeMulti, tmp.GetType())
require.Equal(t, "D3923267FA8A3DD367BB768FA8BDC8FF7F89DA3F", tmp.GetPubKey().Address().String())
require.Equal(t, "cosmos16wfryel63g7axeamw68630wglalcnk3l0zuadc", tmp.GetAddress().String())
require.Equal(t, "cosmos16wfryel63g7axeamw68630wglalcnk3l0zuadc", sdk.MustBech32ifyAddressBytes("cosmos", tmp.GetAddress()))
}
func Test_showKeysCmd(t *testing.T) {
@ -48,16 +49,19 @@ func Test_runShowCmd(t *testing.T) {
fakeKeyName1 := "runShowCmd_Key1"
fakeKeyName2 := "runShowCmd_Key2"
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
require.NoError(t, err)
t.Cleanup(func() {
kb.Delete("runShowCmd_Key1", "", false)
kb.Delete("runShowCmd_Key2", "", false)
kb.Delete("runShowCmd_Key1")
kb.Delete("runShowCmd_Key2")
})
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keyring.Secp256k1)
path := hd.NewFundraiserParams(1, sdk.CoinType, 0).String()
_, err = kb.NewAccount(fakeKeyName1, tests.TestMnemonic, "", path, hd.Secp256k1)
require.NoError(t, err)
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keyring.Secp256k1)
path2 := hd.NewFundraiserParams(1, sdk.CoinType, 1).String()
_, err = kb.NewAccount(fakeKeyName2, tests.TestMnemonic, "", path2, hd.Secp256k1)
require.NoError(t, err)
// Now try single key
@ -69,7 +73,7 @@ func Test_runShowCmd(t *testing.T) {
// try fetch by name
require.NoError(t, runShowCmd(cmd, []string{fakeKeyName1}))
// try fetch by addr
info, err := kb.Get(fakeKeyName1)
info, err := kb.Key(fakeKeyName1)
require.NoError(t, err)
require.NoError(t, runShowCmd(cmd, []string{info.GetAddress().String()}))

View File

@ -21,7 +21,7 @@ type AccountRetriever interface {
// Factory defines a client transaction factory that facilitates generating and
// signing an application-specific transaction.
type Factory struct {
keybase keyring.Keybase
keybase keyring.Keyring
txGenerator Generator
accountRetriever AccountRetriever
accountNumber uint64
@ -36,7 +36,7 @@ type Factory struct {
}
func NewFactoryFromCLI(input io.Reader) Factory {
kb, err := keyring.NewKeyring(
kb, err := keyring.New(
sdk.KeyringServiceName(),
viper.GetString(flags.FlagKeyringBackend),
viper.GetString(flags.FlagHome),
@ -68,7 +68,7 @@ func (f Factory) AccountNumber() uint64 { return f.accountNumber }
func (f Factory) Sequence() uint64 { return f.sequence }
func (f Factory) Gas() uint64 { return f.gas }
func (f Factory) GasAdjustment() float64 { return f.gasAdjustment }
func (f Factory) Keybase() keyring.Keybase { return f.keybase }
func (f Factory) Keybase() keyring.Keyring { return f.keybase }
func (f Factory) ChainID() string { return f.chainID }
func (f Factory) Memo() string { return f.memo }
func (f Factory) Fees() sdk.Coins { return f.fees }
@ -126,7 +126,7 @@ func (f Factory) WithGasPrices(gasPrices string) Factory {
}
// WithKeybase returns a copy of the Factory with updated Keybase.
func (f Factory) WithKeybase(keybase keyring.Keybase) Factory {
func (f Factory) WithKeybase(keybase keyring.Keyring) Factory {
f.keybase = keybase
return f
}

View File

@ -127,7 +127,7 @@ func BroadcastTx(ctx context.CLIContext, txf Factory, msgs ...sdk.Msg) error {
_, _ = fmt.Fprintf(os.Stderr, "%s\n\n", out)
buf := bufio.NewReader(os.Stdin)
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf)
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr)
if err != nil || !ok {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction")
return err
@ -332,7 +332,7 @@ func Sign(txf Factory, name, passphrase string, tx ClientTx) ([]byte, error) {
return nil, err
}
sigBytes, pubkey, err := txf.keybase.Sign(name, passphrase, signBytes)
sigBytes, pubkey, err := txf.keybase.Sign(name, signBytes)
if err != nil {
return nil, err
}

View File

@ -15,8 +15,11 @@ import (
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/tendermint/tendermint/crypto/xsalsa20symmetric"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/types"
)
func TestArmorUnarmorPrivKey(t *testing.T) {
@ -26,7 +29,7 @@ func TestArmorUnarmorPrivKey(t *testing.T) {
require.Error(t, err)
decrypted, algo, err := crypto.UnarmorDecryptPrivKey(armored, "passphrase")
require.NoError(t, err)
require.Equal(t, string(keyring.Secp256k1), algo)
require.Equal(t, string(hd.Secp256k1Type), algo)
require.True(t, priv.Equals(decrypted))
// empty string
@ -70,14 +73,14 @@ func TestArmorUnarmorPubKey(t *testing.T) {
cstore := keyring.NewInMemory()
// Add keys and see they return in alphabetical order
info, _, err := cstore.CreateMnemonic("Bob", keyring.English, "passphrase", keyring.Secp256k1)
info, _, err := cstore.NewMnemonic("Bob", keyring.English, types.FullFundraiserPath, hd.Secp256k1)
require.NoError(t, err)
armored := crypto.ArmorPubKeyBytes(info.GetPubKey().Bytes(), "")
pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armored)
require.NoError(t, err)
pub, err := cryptoAmino.PubKeyFromBytes(pubBytes)
require.NoError(t, err)
require.Equal(t, string(keyring.Secp256k1), algo)
require.Equal(t, string(hd.Secp256k1Type), algo)
require.True(t, pub.Equals(info.GetPubKey()))
armored = crypto.ArmorPubKeyBytes(info.GetPubKey().Bytes(), "unknown")
@ -88,7 +91,7 @@ func TestArmorUnarmorPubKey(t *testing.T) {
require.Equal(t, "unknown", algo)
require.True(t, pub.Equals(info.GetPubKey()))
armored, err = cstore.ExportPrivKey("Bob", "passphrase", "alessio")
armored, err = cstore.ExportPrivKeyArmor("Bob", "passphrase")
require.NoError(t, err)
_, _, err = crypto.UnarmorPubKeyBytes(armored)
require.Error(t, err)

68
crypto/hd/algo.go Normal file
View File

@ -0,0 +1,68 @@
package hd
import (
"github.com/cosmos/go-bip39"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/secp256k1"
)
// PubKeyType defines an algorithm to derive key-pairs which can be used for cryptographic signing.
type PubKeyType string
const (
// MultiType implies that a pubkey is a multisignature
MultiType = PubKeyType("multi")
// Secp256k1Type uses the Bitcoin secp256k1 ECDSA parameters.
Secp256k1Type = PubKeyType("secp256k1")
// Ed25519Type represents the Ed25519Type signature system.
// It is currently not supported for end-user keys (wallets/ledgers).
Ed25519Type = PubKeyType("ed25519")
// Sr25519Type represents the Sr25519Type signature system.
Sr25519Type = PubKeyType("sr25519")
)
var (
// Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters.
Secp256k1 = secp256k1Algo{}
)
type DeriveFn func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error)
type GenerateFn func(bz []byte) crypto.PrivKey
type WalletGenerator interface {
Derive(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error)
Generate(bz []byte) crypto.PrivKey
}
type secp256k1Algo struct {
}
func (s secp256k1Algo) Name() PubKeyType {
return Secp256k1Type
}
// Derive derives and returns the secp256k1 private key for the given seed and HD path.
func (s secp256k1Algo) Derive() DeriveFn {
return func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) {
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
if err != nil {
return nil, err
}
masterPriv, ch := ComputeMastersFromSeed(seed)
if len(hdPath) == 0 {
return masterPriv[:], nil
}
derivedKey, err := DerivePrivateKeyForPath(masterPriv, ch, hdPath)
return derivedKey[:], err
}
}
// Generate generates a secp256k1 private key from the given bytes.
func (s secp256k1Algo) Generate() GenerateFn {
return func(bz []byte) crypto.PrivKey {
var bzArr [32]byte
copy(bzArr[:], bz)
return secp256k1.PrivKeySecp256k1(bzArr)
}
}

16
crypto/hd/algo_test.go Normal file
View File

@ -0,0 +1,16 @@
package hd_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/hd"
)
func TestDefaults(t *testing.T) {
require.Equal(t, hd.PubKeyType("multi"), hd.MultiType)
require.Equal(t, hd.PubKeyType("secp256k1"), hd.Secp256k1Type)
require.Equal(t, hd.PubKeyType("ed25519"), hd.Ed25519Type)
require.Equal(t, hd.PubKeyType("sr25519"), hd.Sr25519Type)
}

12
crypto/hd/doc.go Normal file
View File

@ -0,0 +1,12 @@
// Package hd provides support for hierarchical deterministic wallets generation and derivation.
//
// The user must understand the overall concept of the BIP 32 and the BIP 44 specs:
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
//
// In combination with the bip39 package in go-crypto this package provides the functionality for
// deriving keys using a BIP 44 HD path, or, more general, by passing a BIP 32 path.
//
// In particular, this package (together with bip39) provides all necessary functionality to derive
// keys from mnemonics generated during the cosmos fundraiser.
package hd

View File

@ -1,4 +1,4 @@
package hd
package hd_test
import (
"encoding/hex"
@ -13,6 +13,8 @@ import (
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/hd"
)
type addrData struct {
@ -24,19 +26,23 @@ type addrData struct {
Addr string
}
func TestFullFundraiserPath(t *testing.T) {
require.Equal(t, "44'/118'/0'/0/0", hd.NewFundraiserParams(0, 118, 0).String())
}
func initFundraiserTestVectors(t *testing.T) []addrData {
// NOTE: atom fundraiser address
// var hdPath string = "m/44'/118'/0'/0/0"
var hdToAddrTable []addrData
b, err := ioutil.ReadFile("test.json")
b, err := ioutil.ReadFile("testdata/test.json")
if err != nil {
t.Fatalf("could not read fundraiser test vector file (test.json): %s", err)
t.Fatalf("could not read fundraiser test vector file (testdata/test.json): %s", err)
}
err = json.Unmarshal(b, &hdToAddrTable)
if err != nil {
t.Fatalf("could not decode test vectors (test.json): %s", err)
t.Fatalf("could not decode test vectors (testdata/test.json): %s", err)
}
return hdToAddrTable
}
@ -56,8 +62,8 @@ func TestFundraiserCompatibility(t *testing.T) {
t.Log("================================")
t.Logf("ROUND: %d MNEMONIC: %s", i, d.Mnemonic)
master, ch := ComputeMastersFromSeed(seed)
priv, err := DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0")
master, ch := hd.ComputeMastersFromSeed(seed)
priv, err := hd.DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0")
require.NoError(t, err)
pub := secp256k1.PrivKeySecp256k1(priv).PubKey()

View File

@ -1,20 +1,8 @@
// Package hd provides basic functionality Hierarchical Deterministic Wallets.
//
// The user must understand the overall concept of the BIP 32 and the BIP 44 specs:
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
//
// In combination with the bip39 package in go-crypto this package provides the functionality for deriving keys using a
// BIP 44 HD path, or, more general, by passing a BIP 32 path.
//
// In particular, this package (together with bip39) provides all necessary functionality to derive keys from
// mnemonics generated during the cosmos fundraiser.
package hd
import (
"crypto/hmac"
"crypto/sha512"
"encoding/binary"
"errors"
"fmt"
@ -255,3 +243,8 @@ func i64(key []byte, data []byte) (il [32]byte, ir [32]byte) {
return
}
// CreateHDPath returns BIP 44 object from account and index parameters.
func CreateHDPath(coinType, account, index uint32) *BIP44Params {
return NewFundraiserParams(account, coinType, index)
}

View File

@ -1,10 +1,11 @@
package hd
package hd_test
import (
"encoding/hex"
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/types"
bip39 "github.com/cosmos/go-bip39"
@ -21,9 +22,9 @@ func mnemonicToSeed(mnemonic string) []byte {
// nolint:govet
func ExampleStringifyPathParams() {
path := NewParams(44, 0, 0, false, 0)
path := hd.NewParams(44, 0, 0, false, 0)
fmt.Println(path.String())
path = NewParams(44, 33, 7, true, 9)
path = hd.NewParams(44, 33, 7, true, 9)
fmt.Println(path.String())
// Output:
// 44'/0'/0'/0/0
@ -31,40 +32,40 @@ func ExampleStringifyPathParams() {
}
func TestStringifyFundraiserPathParams(t *testing.T) {
path := NewFundraiserParams(4, types.CoinType, 22)
path := hd.NewFundraiserParams(4, types.CoinType, 22)
require.Equal(t, "44'/118'/4'/0/22", path.String())
path = NewFundraiserParams(4, types.CoinType, 57)
path = hd.NewFundraiserParams(4, types.CoinType, 57)
require.Equal(t, "44'/118'/4'/0/57", path.String())
path = NewFundraiserParams(4, 12345, 57)
path = hd.NewFundraiserParams(4, 12345, 57)
require.Equal(t, "44'/12345'/4'/0/57", path.String())
}
func TestPathToArray(t *testing.T) {
path := NewParams(44, 118, 1, false, 4)
path := hd.NewParams(44, 118, 1, false, 4)
require.Equal(t, "[44 118 1 0 4]", fmt.Sprintf("%v", path.DerivationPath()))
path = NewParams(44, 118, 2, true, 15)
path = hd.NewParams(44, 118, 2, true, 15)
require.Equal(t, "[44 118 2 1 15]", fmt.Sprintf("%v", path.DerivationPath()))
}
func TestParamsFromPath(t *testing.T) {
goodCases := []struct {
params *BIP44Params
params *hd.BIP44Params
path string
}{
{&BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"},
{&BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"},
{&BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"},
{&BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"},
{&BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"},
{&BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"},
{&BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"},
{&hd.BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"},
{&hd.BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"},
{&hd.BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"},
{&hd.BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"},
{&hd.BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"},
{&hd.BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"},
{&hd.BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"},
}
for i, c := range goodCases {
params, err := NewParamsFromPath(c.path)
params, err := hd.NewParamsFromPath(c.path)
errStr := fmt.Sprintf("%d %v", i, c)
assert.NoError(t, err, errStr)
assert.EqualValues(t, c.params, params, errStr)
@ -93,7 +94,7 @@ func TestParamsFromPath(t *testing.T) {
}
for i, c := range badCases {
params, err := NewParamsFromPath(c.path)
params, err := hd.NewParamsFromPath(c.path)
errStr := fmt.Sprintf("%d %v", i, c)
assert.Nil(t, params, errStr)
assert.Error(t, err, errStr)
@ -106,38 +107,38 @@ func ExampleSomeBIP32TestVecs() {
seed := mnemonicToSeed("barrel original fuel morning among eternal " +
"filter ball stove pluck matrix mechanic")
master, ch := ComputeMastersFromSeed(seed)
master, ch := hd.ComputeMastersFromSeed(seed)
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
fmt.Println()
// cosmos
priv, err := DerivePrivateKeyForPath(master, ch, types.FullFundraiserPath)
priv, err := hd.DerivePrivateKeyForPath(master, ch, types.FullFundraiserPath)
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
// bitcoin
priv, err = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
// ether
priv, err = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
// INVALID
priv, err = DerivePrivateKeyForPath(master, ch, "X/0'/0'/0/0")
priv, err = hd.DerivePrivateKeyForPath(master, ch, "X/0'/0'/0/0")
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
priv, err = DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0")
priv, err = hd.DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0")
if err != nil {
fmt.Println("INVALID")
} else {
@ -151,14 +152,14 @@ func ExampleSomeBIP32TestVecs() {
seed = mnemonicToSeed(
"advice process birth april short trust crater change bacon monkey medal garment " +
"gorilla ranch hour rival razor call lunar mention taste vacant woman sister")
master, ch = ComputeMastersFromSeed(seed)
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4")
master, ch = hd.ComputeMastersFromSeed(seed)
priv, _ = hd.DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4")
fmt.Println(hex.EncodeToString(priv[:]))
seed = mnemonicToSeed("idea naive region square margin day captain habit " +
"gun second farm pact pulse someone armed")
master, ch = ComputeMastersFromSeed(seed)
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420")
master, ch = hd.ComputeMastersFromSeed(seed)
priv, _ = hd.DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420")
fmt.Println(hex.EncodeToString(priv[:]))
fmt.Println()
@ -167,8 +168,8 @@ func ExampleSomeBIP32TestVecs() {
// bip32 path: m/0/7
seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history")
master, ch = ComputeMastersFromSeed(seed)
priv, _ = DerivePrivateKeyForPath(master, ch, "0/7")
master, ch = hd.ComputeMastersFromSeed(seed)
priv, _ = hd.DerivePrivateKeyForPath(master, ch, "0/7")
fmt.Println(hex.EncodeToString(priv[:]))
// Output: keys from fundraiser test-vector (cosmos, bitcoin, ether)
@ -188,3 +189,27 @@ func ExampleSomeBIP32TestVecs() {
//
// c4c11d8c03625515905d7e89d25dfc66126fbc629ecca6db489a1a72fc4bda78
}
func TestCreateHDPath(t *testing.T) {
type args struct {
coinType uint32
account uint32
index uint32
}
tests := []struct {
name string
args args
want hd.BIP44Params
}{
{"44'/0'/0'/0/0", args{0, 0, 0}, hd.BIP44Params{Purpose: 44}},
{"44'/114'/0'/0/0", args{114, 0, 0}, hd.BIP44Params{Purpose: 44, CoinType: 114, Account: 0, AddressIndex: 0}},
{"44'/114'/1'/1/0", args{114, 1, 1}, hd.BIP44Params{Purpose: 44, CoinType: 114, Account: 1, AddressIndex: 1}},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
tt := tt
require.Equal(t, tt.want, *hd.CreateHDPath(tt.args.coinType, tt.args.account, tt.args.index))
})
}
}

View File

@ -1,234 +0,0 @@
package keyring
import (
"github.com/pkg/errors"
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/types"
bip39 "github.com/cosmos/go-bip39"
)
type (
// baseKeybase is an auxiliary type that groups Keybase storage agnostic features
// together.
baseKeybase struct {
options kbOptions
}
keyWriter interface {
writeLocalKeyer
infoWriter
}
writeLocalKeyer interface {
writeLocalKey(name string, priv tmcrypto.PrivKey, algo SigningAlgo) Info
}
infoWriter interface {
writeInfo(name string, info Info)
}
)
var fundraiserPath = types.GetConfig().GetFullFundraiserPath()
// newBaseKeybase generates the base keybase with defaulting to tendermint SECP256K1 key type
func newBaseKeybase(optionsFns ...KeybaseOption) baseKeybase {
// Default options for keybase
options := kbOptions{
keygenFunc: StdPrivKeyGen,
deriveFunc: StdDeriveKey,
supportedAlgos: []SigningAlgo{Secp256k1},
supportedAlgosLedger: []SigningAlgo{Secp256k1},
}
for _, optionFn := range optionsFns {
optionFn(&options)
}
return baseKeybase{options: options}
}
// 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)
}
// CreateAccount creates an account Info object.
func (kb baseKeybase) CreateAccount(
keyWriter keyWriter, name, mnemonic, bip39Passphrase, encryptPasswd, hdPath string, algo SigningAlgo,
) (Info, error) {
// create master key and derive first key for keyring
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 encryptPasswd != "" {
info = keyWriter.writeLocalKey(name, privKey, algo)
} else {
info = kb.writeOfflineKey(keyWriter, name, privKey.PubKey(), algo)
}
return info, nil
}
// CreateLedger creates a new reference to a Ledger key pair. It returns a public
// key and a derivation path. It returns an error if the device could not be queried.
func (kb baseKeybase) CreateLedger(
w infoWriter, name string, algo SigningAlgo, hrp string, account, index uint32,
) (Info, error) {
if !IsSupportedAlgorithm(kb.SupportedAlgosLedger(), algo) {
return nil, ErrUnsupportedSigningAlgo
}
coinType := types.GetConfig().GetCoinType()
hdPath := hd.NewFundraiserParams(account, coinType, index)
priv, _, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath, hrp)
if err != nil {
return nil, err
}
return kb.writeLedgerKey(w, name, priv.PubKey(), *hdPath, algo), nil
}
// CreateMnemonic generates a new key with the given algorithm and language pair.
func (kb baseKeybase) CreateMnemonic(
keyWriter keyWriter, name string, language Language, passwd string, algo SigningAlgo,
) (info Info, mnemonic string, err error) {
if language != English {
return nil, "", ErrUnsupportedLanguage
}
if !IsSupportedAlgorithm(kb.SupportedAlgos(), algo) {
return nil, "", ErrUnsupportedSigningAlgo
}
// Default number of words (24): This generates a mnemonic directly from the
// number of words by reading system entropy.
entropy, err := bip39.NewEntropy(defaultEntropySize)
if err != nil {
return nil, "", err
}
mnemonic, err = bip39.NewMnemonic(entropy)
if err != nil {
return nil, "", err
}
info, err = kb.CreateAccount(keyWriter, name, mnemonic, DefaultBIP39Passphrase, passwd, fundraiserPath, algo)
if err != nil {
return nil, "", err
}
return info, mnemonic, err
}
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, algo SigningAlgo) Info {
info := newOfflineInfo(name, pub, algo)
w.writeInfo(name, info)
return info
}
func (kb baseKeybase) writeMultisigKey(w infoWriter, name string, pub tmcrypto.PubKey) Info {
info := NewMultiInfo(name, pub)
w.writeInfo(name, info)
return info
}
// 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)
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.
func CreateHDPath(account uint32, index uint32) *hd.BIP44Params {
return hd.NewFundraiserParams(account, types.GetConfig().GetCoinType(), index)
}
// 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
}
// SignWithLedger signs a binary message with the ledger device referenced by an Info object
// and returns the signed bytes and the public key. It returns an error if the device could
// not be queried or it returned an error.
func SignWithLedger(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
switch info.(type) {
case *ledgerInfo, ledgerInfo:
default:
return nil, nil, errors.New("not a ledger object")
}
path, err := info.GetPath()
if err != nil {
return
}
priv, err := crypto.NewPrivKeyLedgerSecp256k1Unsafe(*path)
if err != nil {
return
}
sig, err = priv.Sign(msg)
if err != nil {
return nil, nil, err
}
return sig, priv.PubKey(), nil
}

View File

@ -4,7 +4,7 @@ import (
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/hd"
)
// CryptoCdc defines the codec required for keys and info

View File

@ -19,9 +19,9 @@
// generated keys are discarded when the process terminates or the type instance is garbage
// collected.
//
// NewKeyring
// New
//
// The NewKeyring constructor returns an implementation backed by a keyring library
// The New constructor returns an implementation backed by a keyring library
// (https://github.com/99designs/keyring), whose aim is to provide a common abstraction and uniform
// interface between secret stores available for Windows, macOS, and most GNU/Linux distributions
// as well as operating system-agnostic encrypted file-based backends.

View File

@ -6,7 +6,7 @@ import (
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/multisig"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/types"
)
@ -23,7 +23,7 @@ type Info interface {
// Bip44 Path
GetPath() (*hd.BIP44Params, error)
// Algo
GetAlgo() SigningAlgo
GetAlgo() hd.PubKeyType
}
var (
@ -39,10 +39,10 @@ type localInfo struct {
Name string `json:"name"`
PubKey crypto.PubKey `json:"pubkey"`
PrivKeyArmor string `json:"privkey.armor"`
Algo SigningAlgo `json:"algo"`
Algo hd.PubKeyType `json:"algo"`
}
func newLocalInfo(name string, pub crypto.PubKey, privArmor string, algo SigningAlgo) Info {
func newLocalInfo(name string, pub crypto.PubKey, privArmor string, algo hd.PubKeyType) Info {
return &localInfo{
Name: name,
PubKey: pub,
@ -72,7 +72,7 @@ func (i localInfo) GetAddress() types.AccAddress {
}
// GetType implements Info interface
func (i localInfo) GetAlgo() SigningAlgo {
func (i localInfo) GetAlgo() hd.PubKeyType {
return i.Algo
}
@ -87,10 +87,10 @@ type ledgerInfo struct {
Name string `json:"name"`
PubKey crypto.PubKey `json:"pubkey"`
Path hd.BIP44Params `json:"path"`
Algo SigningAlgo `json:"algo"`
Algo hd.PubKeyType `json:"algo"`
}
func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params, algo SigningAlgo) Info {
func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params, algo hd.PubKeyType) Info {
return &ledgerInfo{
Name: name,
PubKey: pub,
@ -120,7 +120,7 @@ func (i ledgerInfo) GetAddress() types.AccAddress {
}
// GetPath implements Info interface
func (i ledgerInfo) GetAlgo() SigningAlgo {
func (i ledgerInfo) GetAlgo() hd.PubKeyType {
return i.Algo
}
@ -135,10 +135,10 @@ func (i ledgerInfo) GetPath() (*hd.BIP44Params, error) {
type offlineInfo struct {
Name string `json:"name"`
PubKey crypto.PubKey `json:"pubkey"`
Algo SigningAlgo `json:"algo"`
Algo hd.PubKeyType `json:"algo"`
}
func newOfflineInfo(name string, pub crypto.PubKey, algo SigningAlgo) Info {
func newOfflineInfo(name string, pub crypto.PubKey, algo hd.PubKeyType) Info {
return &offlineInfo{
Name: name,
PubKey: pub,
@ -162,7 +162,7 @@ func (i offlineInfo) GetPubKey() crypto.PubKey {
}
// GetAlgo returns the signing algorithm for the key
func (i offlineInfo) GetAlgo() SigningAlgo {
func (i offlineInfo) GetAlgo() hd.PubKeyType {
return i.Algo
}
@ -228,8 +228,8 @@ func (i multiInfo) GetAddress() types.AccAddress {
}
// GetPath implements Info interface
func (i multiInfo) GetAlgo() SigningAlgo {
return MultiAlgo
func (i multiInfo) GetAlgo() hd.PubKeyType {
return hd.MultiType
}
// GetPath implements Info interface

View File

@ -1,76 +0,0 @@
package keyring
import (
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/types"
)
// Keybase exposes operations on a generic keystore
type Keybase interface {
// CRUD on the keystore
List() ([]Info, error)
// Get returns the public information about one key.
Get(name string) (Info, error)
// Get performs a by-address lookup and returns the public
// information about one key if there's any.
GetByAddress(address types.AccAddress) (Info, error)
// Delete removes a key.
Delete(name, passphrase string, skipPass bool) error
// Sign bytes, looking up the private key to use.
Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error)
// CreateMnemonic generates a new mnemonic, derives a hierarchical deterministic
// key from that. and persists it to storage, encrypted using the provided password.
// It returns the generated mnemonic and the key Info. It returns an error if it fails to
// generate a key for the given algo type, or if another key is already stored under the
// 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 and BIP 32 HD Path
// and persists it, encrypted with the given password.
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, 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)
// Import imports ASCII armored Info objects.
Import(name string, armor string) (err error)
// ImportPrivKey imports a private key in ASCII armor format.
// It returns an error if a key with the same name exists or a wrong encryption passphrase is
// supplied.
ImportPrivKey(name, armor, passphrase string) error
// ImportPubKey imports ASCII-armored public keys.
// Store a new Info object holding a public key only, i.e. it will
// not be possible to sign with it as it lacks the secret key.
ImportPubKey(name string, armor string) (err error)
// Export exports an Info object in ASCII armored format.
Export(name string) (armor string, err error)
// ExportPubKey returns public keys in ASCII armored format.
// Retrieve a Info object by its name and return the public key in
// a portable format.
ExportPubKey(name string) (armor string, err error)
// ExportPrivKey returns a private key in ASCII armored format.
// It returns an error if the key does not exist or a wrong encryption passphrase is supplied.
ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error)
// 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
}

View File

@ -12,15 +12,16 @@ import (
"strings"
"github.com/99designs/keyring"
"github.com/cosmos/go-bip39"
"github.com/pkg/errors"
"github.com/tendermint/crypto/bcrypt"
tmcrypto "github.com/tendermint/tendermint/crypto"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/crypto/hd"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
@ -34,48 +35,116 @@ const (
)
const (
keyringDirNameFmt = "keyring-%s"
testKeyringDirNameFmt = "keyring-test-%s"
passKeyringPrefix = keyringDirNameFmt
keyringFileDirName = "keyring-file"
keyringTestDirName = "keyring-test"
passKeyringPrefix = "keyring-%s"
)
var _ Keybase = keyringKeybase{}
var (
_ Keyring = &keystore{}
maxPassphraseEntryAttempts = 3
)
// keyringKeybase implements the Keybase interface by using the Keyring library
// for account key persistence.
type keyringKeybase struct {
base baseKeybase
db keyring.Keyring
// Keyring exposes operations over a backend supported by github.com/99designs/keyring.
type Keyring interface {
// List all keys.
List() ([]Info, error)
// Key and KeyByAddress return keys by uid and address respectively.
Key(uid string) (Info, error)
KeyByAddress(address sdk.Address) (Info, error)
// Delete and DeleteByAddress remove keys from the keyring.
Delete(uid string) error
DeleteByAddress(address sdk.Address) error
// NewMnemonic generates a new mnemonic, derives a hierarchical deterministic
// key from that, and persists it to the storage. Returns the generated mnemonic and the key
// Info. It returns an error if it fails to generate a key for the given algo type, or if
// another key is already stored under the same name.
NewMnemonic(uid string, language Language, hdPath string, algo SignatureAlgo) (Info, string, error)
// NewAccount converts a mnemonic to a private key and BIP-39 HD Path and persists it.
NewAccount(uid, mnemonic, bip39Passwd, hdPath string, algo SignatureAlgo) (Info, error)
// SaveLedgerKey retrieves a public key reference from a Ledger device and persists it.
SaveLedgerKey(uid string, algo SignatureAlgo, hrp string, coinType, account, index uint32) (Info, error)
// SavePubKey stores a public key and returns the persisted Info structure.
SavePubKey(uid string, pubkey tmcrypto.PubKey, algo hd.PubKeyType) (Info, error)
// SaveMultisig stores and returns a new multsig (offline) key reference.
SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error)
Signer
Importer
Exporter
}
var maxPassphraseEntryAttempts = 3
// Signer is implemented by key stores that want to provide signing capabilities.
type Signer interface {
// Sign sign byte messages with a user key.
Sign(uid string, msg []byte) ([]byte, tmcrypto.PubKey, error)
func newKeyringKeybase(db keyring.Keyring, opts ...KeybaseOption) Keybase {
return keyringKeybase{
db: db,
base: newBaseKeybase(opts...),
}
// SignByAddress sign byte messages with a user key providing the address.
SignByAddress(address sdk.Address, msg []byte) ([]byte, tmcrypto.PubKey, error)
}
// NewKeyring creates a new instance of a keyring. Keybase
// options can be applied when generating this new Keybase.
// Available backends are "os", "file", "kwallet", "pass", "test".
func NewKeyring(
appName, backend, rootDir string, userInput io.Reader, opts ...KeybaseOption,
) (Keybase, error) {
// Importer is implemented by key stores that support import of public and private keys.
type Importer interface {
// ImportPrivKey imports ASCII armored passphrase-encrypted private keys.
ImportPrivKey(uid, armor, passphrase string) error
// ImportPubKey imports ASCII armored public keys.
ImportPubKey(uid string, armor string) error
}
// Exporter is implemented by key stores that support export of public and private keys.
type Exporter interface {
// Export public key
ExportPubKeyArmor(uid string) (string, error)
ExportPubKeyArmorByAddress(address sdk.Address) (string, error)
// ExportPrivKey returns a private key in ASCII armored format.
// It returns an error if the key does not exist or a wrong encryption passphrase is supplied.
ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error)
ExportPrivKeyArmorByAddress(address sdk.Address, encryptPassphrase string) (armor string, err error)
}
// Option overrides keyring configuration options.
type Option func(options *Options)
//Options define the options of the Keyring
type Options struct {
SupportedAlgos SigningAlgoList
SupportedAlgosLedger SigningAlgoList
}
// NewInMemory creates a transient keyring useful for testing
// purposes and on-the-fly key generation.
// Keybase options can be applied when generating this new Keybase.
func NewInMemory(opts ...Option) Keyring {
return newKeystore(keyring.NewArrayKeyring(nil), opts...)
}
// NewKeyring creates a new instance of a keyring.
// Keyring ptions can be applied when generating the new instance.
// Available backends are "os", "file", "kwallet", "memory", "pass", "test".
func New(
appName, backend, rootDir string, userInput io.Reader, opts ...Option,
) (Keyring, error) {
var db keyring.Keyring
var err error
switch backend {
case BackendMemory:
return NewInMemory(opts...), nil
return NewInMemory(opts...), err
case BackendTest:
db, err = keyring.Open(lkbToKeyringConfig(appName, rootDir, nil, true))
db, err = keyring.Open(newTestBackendKeyringConfig(appName, rootDir))
case BackendFile:
db, err = keyring.Open(newFileBackendKeyringConfig(appName, rootDir, userInput))
case BackendOS:
db, err = keyring.Open(lkbToKeyringConfig(appName, rootDir, userInput, false))
db, err = keyring.Open(newOSBackendKeyringConfig(appName, rootDir, userInput))
case BackendKWallet:
db, err = keyring.Open(newKWalletBackendKeyringConfig(appName, rootDir, userInput))
case BackendPass:
@ -83,168 +152,72 @@ func NewKeyring(
default:
return nil, fmt.Errorf("unknown keyring backend %v", backend)
}
if err != nil {
return nil, err
}
return newKeyringKeybase(db, opts...), nil
return newKeystore(db, opts...), nil
}
// NewInMemory creates a transient keyring useful for testing
// purposes and on-the-fly key generation.
// Keybase options can be applied when generating this new Keybase.
func NewInMemory(opts ...KeybaseOption) Keybase {
return newKeyringKeybase(keyring.NewArrayKeyring(nil), opts...)
type keystore struct {
db keyring.Keyring
options Options
}
// CreateMnemonic generates a new key and persists it to storage, encrypted
// using the provided password. It returns the generated mnemonic and the key Info.
// An error is returned if it fails to generate a key for the given algo type,
// or if another key is already stored under the same name.
func (kb keyringKeybase) CreateMnemonic(
name string, language Language, passwd string, algo SigningAlgo,
) (info Info, mnemonic string, err error) {
func newKeystore(kr keyring.Keyring, opts ...Option) keystore {
// Default options for keybase
options := Options{
SupportedAlgos: SigningAlgoList{hd.Secp256k1},
SupportedAlgosLedger: SigningAlgoList{hd.Secp256k1},
}
return kb.base.CreateMnemonic(kb, name, language, passwd, algo)
for _, optionFn := range opts {
optionFn(&options)
}
return keystore{kr, options}
}
// CreateAccount converts a mnemonic to a private key and persists it, encrypted
// with the given password.
func (kb keyringKeybase) CreateAccount(
name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo,
) (Info, error) {
return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo)
}
// CreateLedger creates a new locally-stored reference to a Ledger keypair.
// It returns the created key info and an error if the Ledger could not be queried.
func (kb keyringKeybase) CreateLedger(
name string, algo SigningAlgo, hrp string, account, index uint32,
) (Info, error) {
return kb.base.CreateLedger(kb, name, algo, hrp, account, index)
}
// CreateOffline creates a new reference to an offline keypair. It returns the
// created key info.
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
// returns the created key Info object.
func (kb keyringKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.base.writeMultisigKey(kb, name, pub), nil
}
// List returns the keys from storage in alphabetical order.
func (kb keyringKeybase) List() ([]Info, error) {
var res []Info
keys, err := kb.db.Keys()
func (ks keystore) ExportPubKeyArmor(uid string) (string, error) {
bz, err := ks.Key(uid)
if err != nil {
return nil, err
return "", err
}
sort.Strings(keys)
for _, key := range keys {
if strings.HasSuffix(key, infoSuffix) {
rawInfo, err := kb.db.Get(key)
if err != nil {
return nil, err
if bz == nil {
return "", fmt.Errorf("no key to export with name: %s", uid)
}
if len(rawInfo.Data) == 0 {
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, key)
}
info, err := unmarshalInfo(rawInfo.Data)
if err != nil {
return nil, err
}
res = append(res, info)
}
}
return res, nil
return crypto.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil
}
// Get returns the public information about one key.
func (kb keyringKeybase) Get(name string) (Info, error) {
key := infoKey(name)
bs, err := kb.db.Get(string(key))
func (ks keystore) ExportPubKeyArmorByAddress(address sdk.Address) (string, error) {
info, err := ks.KeyByAddress(address)
if err != nil {
return nil, err
return "", err
}
if len(bs.Data) == 0 {
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, name)
}
return unmarshalInfo(bs.Data)
return ks.ExportPubKeyArmor(info.GetName())
}
// GetByAddress fetches a key by address and returns its public information.
func (kb keyringKeybase) GetByAddress(address types.AccAddress) (Info, error) {
ik, err := kb.db.Get(string(addrHexKey(address)))
func (ks keystore) ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error) {
priv, err := ks.ExportPrivateKeyObject(uid)
if err != nil {
return nil, err
return "", err
}
if len(ik.Data) == 0 {
return nil, fmt.Errorf("key with address %s not found", address)
}
bs, err := kb.db.Get(string(ik.Data))
info, err := ks.Key(uid)
if err != nil {
return nil, err
return "", err
}
return unmarshalInfo(bs.Data)
}
// Sign signs an arbitrary set of bytes with the named key. It returns an error
// if the key doesn't exist or the decryption fails.
func (kb keyringKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
info, err := kb.Get(name)
if err != nil {
return
}
var priv tmcrypto.PrivKey
switch i := info.(type) {
case localInfo:
if i.PrivKeyArmor == "" {
return nil, nil, fmt.Errorf("private key not available")
}
priv, err = cryptoAmino.PrivKeyFromBytes([]byte(i.PrivKeyArmor))
if err != nil {
return nil, nil, err
}
case ledgerInfo:
return SignWithLedger(info, msg)
case offlineInfo, multiInfo:
return nil, info.GetPubKey(), errors.New("cannot sign with offline keys")
}
sig, err = priv.Sign(msg)
if err != nil {
return nil, nil, err
}
return sig, priv.PubKey(), nil
return crypto.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil
}
// ExportPrivateKeyObject exports an armored private key object.
func (kb keyringKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) {
info, err := kb.Get(name)
func (ks keystore) ExportPrivateKeyObject(uid string) (tmcrypto.PrivKey, error) {
info, err := ks.Key(uid)
if err != nil {
return nil, err
}
@ -270,92 +243,18 @@ func (kb keyringKeybase) ExportPrivateKeyObject(name string, passphrase string)
return priv, nil
}
// Export exports armored private key to the caller.
func (kb keyringKeybase) Export(name string) (armor string, err error) {
bz, err := kb.db.Get(string(infoKey(name)))
func (ks keystore) ExportPrivKeyArmorByAddress(address sdk.Address, encryptPassphrase string) (armor string, err error) {
byAddress, err := ks.KeyByAddress(address)
if err != nil {
return "", err
}
if bz.Data == nil {
return "", fmt.Errorf("no key to export with name: %s", name)
}
return crypto.ArmorInfoBytes(bz.Data), nil
return ks.ExportPrivKeyArmor(byAddress.GetName(), encryptPassphrase)
}
// ExportPubKey returns public keys in ASCII armored format. It retrieves an Info
// object by its name and return the public key in a portable format.
func (kb keyringKeybase) ExportPubKey(name string) (armor string, err error) {
bz, err := kb.Get(name)
if err != nil {
return "", err
}
if bz == nil {
return "", fmt.Errorf("no key to export with name: %s", name)
}
return crypto.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil
}
// Import imports armored private key.
func (kb keyringKeybase) Import(name string, armor string) error {
bz, _ := kb.Get(name)
if bz != nil {
pubkey := bz.GetPubKey()
if len(pubkey.Bytes()) > 0 {
return fmt.Errorf("cannot overwrite data for name: %s", name)
}
}
infoBytes, err := crypto.UnarmorInfoBytes(armor)
if err != nil {
return err
}
info, err := unmarshalInfo(infoBytes)
if err != nil {
return err
}
kb.writeInfo(name, info)
err = kb.db.Set(keyring.Item{
Key: string(addrHexKey(info.GetAddress())),
Data: infoKey(name),
})
if err != nil {
return err
}
return nil
}
// ExportPrivKey returns a private key in ASCII armored format. An error is returned
// if the key does not exist or a wrong encryption passphrase is supplied.
func (kb keyringKeybase) ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) {
priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase)
if err != nil {
return "", err
}
info, err := kb.Get(name)
if err != nil {
return "", err
}
return crypto.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil
}
// ImportPrivKey imports a private key in ASCII armor format. An error is returned
// if a key with the same name exists or a wrong encryption passphrase is
// supplied.
func (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error {
if kb.HasKey(name) {
return fmt.Errorf("cannot overwrite key: %s", name)
func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error {
if _, err := ks.Key(uid); err == nil {
return fmt.Errorf("cannot overwrite key: %s", uid)
}
privKey, algo, err := crypto.UnarmorDecryptPrivKey(armor, passphrase)
@ -363,28 +262,17 @@ func (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error {
return errors.Wrap(err, "failed to decrypt private key")
}
// NOTE: The keyring keystore has no need for a passphrase.
kb.writeLocalKey(name, privKey, SigningAlgo(algo))
_, err = ks.writeLocalKey(uid, privKey, hd.PubKeyType(algo))
if err != nil {
return err
}
return nil
}
// HasKey returns whether the key exists in the keyring.
func (kb keyringKeybase) HasKey(name string) bool {
bz, _ := kb.Get(name)
return bz != nil
}
// ImportPubKey imports an ASCII-armored public key. It will store a new Info
// object holding a public key only, i.e. it will not be possible to sign with
// it as it lacks the secret key.
func (kb keyringKeybase) ImportPubKey(name string, armor string) error {
bz, _ := kb.Get(name)
if bz != nil {
pubkey := bz.GetPubKey()
if len(pubkey.Bytes()) > 0 {
return fmt.Errorf("cannot overwrite data for name: %s", name)
}
func (ks keystore) ImportPubKey(uid string, armor string) error {
if _, err := ks.Key(uid); err == nil {
return fmt.Errorf("cannot overwrite key: %s", uid)
}
pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armor)
@ -397,27 +285,7 @@ func (kb keyringKeybase) ImportPubKey(name string, armor string) error {
return err
}
kb.base.writeOfflineKey(kb, name, pubKey, SigningAlgo(algo))
return nil
}
// Delete removes key forever, but we must present the proper passphrase before
// deleting it (for security). It returns an error if the key doesn't exist or
// passphrases don't match. The passphrase is ignored when deleting references to
// offline and Ledger / HW wallet keys.
func (kb keyringKeybase) Delete(name, _ string, _ bool) error {
// verify we have the proper password before deleting
info, err := kb.Get(name)
if err != nil {
return err
}
err = kb.db.Remove(string(addrHexKey(info.GetAddress())))
if err != nil {
return err
}
err = kb.db.Remove(string(infoKey(name)))
_, err = ks.writeOfflineKey(uid, pubKey, hd.PubKeyType(algo))
if err != nil {
return err
}
@ -425,59 +293,257 @@ func (kb keyringKeybase) Delete(name, _ string, _ bool) error {
return nil
}
// 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()
}
func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, algo SigningAlgo) Info {
// encrypt private key using keyring
pub := priv.PubKey()
info := newLocalInfo(name, pub, string(priv.Bytes()), algo)
kb.writeInfo(name, info)
return info
}
func (kb keyringKeybase) writeInfo(name string, info Info) {
// write the info by key
key := infoKey(name)
serializedInfo := marshalInfo(info)
err := kb.db.Set(keyring.Item{
Key: string(key),
Data: serializedInfo,
})
func (ks keystore) Sign(uid string, msg []byte) ([]byte, tmcrypto.PubKey, error) {
info, err := ks.Key(uid)
if err != nil {
panic(err)
return nil, nil, err
}
err = kb.db.Set(keyring.Item{
Key: string(addrHexKey(info.GetAddress())),
Data: key,
})
if err != nil {
panic(err)
var priv tmcrypto.PrivKey
switch i := info.(type) {
case localInfo:
if i.PrivKeyArmor == "" {
return nil, nil, fmt.Errorf("private key not available")
}
priv, err = cryptoAmino.PrivKeyFromBytes([]byte(i.PrivKeyArmor))
if err != nil {
return nil, nil, err
}
case ledgerInfo:
return SignWithLedger(info, msg)
case offlineInfo, multiInfo:
return nil, info.GetPubKey(), errors.New("cannot sign with offline keys")
}
sig, err := priv.Sign(msg)
if err != nil {
return nil, nil, err
}
return sig, priv.PubKey(), nil
}
func lkbToKeyringConfig(appName, dir string, buf io.Reader, test bool) keyring.Config {
if test {
return keyring.Config{
AllowedBackends: []keyring.BackendType{keyring.FileBackend},
ServiceName: appName,
FileDir: filepath.Join(dir, fmt.Sprintf(testKeyringDirNameFmt, appName)),
FilePasswordFunc: func(_ string) (string, error) {
return "test", nil
},
func (ks keystore) SignByAddress(address sdk.Address, msg []byte) ([]byte, tmcrypto.PubKey, error) {
key, err := ks.KeyByAddress(address)
if err != nil {
return nil, nil, err
}
return ks.Sign(key.GetName(), msg)
}
func (ks keystore) SaveLedgerKey(uid string, algo SignatureAlgo, hrp string, coinType, account, index uint32) (Info, error) {
if !ks.options.SupportedAlgosLedger.Contains(algo) {
return nil, ErrUnsupportedSigningAlgo
}
hdPath := hd.NewFundraiserParams(account, coinType, index)
priv, _, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath, hrp)
if err != nil {
return nil, err
}
return ks.writeLedgerKey(uid, priv.PubKey(), *hdPath, algo.Name())
}
func (ks keystore) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params, algo hd.PubKeyType) (Info, error) {
info := newLedgerInfo(name, pub, path, algo)
err := ks.writeInfo(info)
if err != nil {
return nil, err
}
return info, nil
}
func (ks keystore) SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error) {
return ks.writeMultisigKey(uid, pubkey)
}
func (ks keystore) SavePubKey(uid string, pubkey tmcrypto.PubKey, algo hd.PubKeyType) (Info, error) {
return ks.writeOfflineKey(uid, pubkey, algo)
}
func (ks keystore) DeleteByAddress(address sdk.Address) error {
info, err := ks.KeyByAddress(address)
if err != nil {
return err
}
err = ks.Delete(info.GetName())
if err != nil {
return err
}
return nil
}
func (ks keystore) Delete(uid string) error {
info, err := ks.Key(uid)
if err != nil {
return err
}
err = ks.db.Remove(addrHexKeyAsString(info.GetAddress()))
if err != nil {
return err
}
err = ks.db.Remove(string(infoKey(uid)))
if err != nil {
return err
}
return nil
}
func (ks keystore) KeyByAddress(address sdk.Address) (Info, error) {
ik, err := ks.db.Get(addrHexKeyAsString(address))
if err != nil {
return nil, err
}
if len(ik.Data) == 0 {
return nil, fmt.Errorf("key with address %s not found", address)
}
bs, err := ks.db.Get(string(ik.Data))
if err != nil {
return nil, err
}
return unmarshalInfo(bs.Data)
}
func (ks keystore) List() ([]Info, error) {
var res []Info
keys, err := ks.db.Keys()
if err != nil {
return nil, err
}
sort.Strings(keys)
for _, key := range keys {
if strings.HasSuffix(key, infoSuffix) {
rawInfo, err := ks.db.Get(key)
if err != nil {
return nil, err
}
if len(rawInfo.Data) == 0 {
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, key)
}
info, err := unmarshalInfo(rawInfo.Data)
if err != nil {
return nil, err
}
res = append(res, info)
}
}
return res, nil
}
func (ks keystore) NewMnemonic(uid string, language Language, hdPath string, algo SignatureAlgo) (Info, string, error) {
if language != English {
return nil, "", ErrUnsupportedLanguage
}
if !ks.isSupportedSigningAlgo(algo) {
return nil, "", ErrUnsupportedSigningAlgo
}
// Default number of words (24): This generates a mnemonic directly from the
// number of words by reading system entropy.
entropy, err := bip39.NewEntropy(defaultEntropySize)
if err != nil {
return nil, "", err
}
mnemonic, err := bip39.NewMnemonic(entropy)
if err != nil {
return nil, "", err
}
info, err := ks.NewAccount(uid, mnemonic, DefaultBIP39Passphrase, hdPath, algo)
if err != nil {
return nil, "", err
}
return info, mnemonic, err
}
func (ks keystore) NewAccount(uid string, mnemonic string, bip39Passphrase string, hdPath string, algo SignatureAlgo) (Info, error) {
if !ks.isSupportedSigningAlgo(algo) {
return nil, ErrUnsupportedSigningAlgo
}
// create master key and derive first key for keyring
derivedPriv, err := algo.Derive()(mnemonic, bip39Passphrase, hdPath)
if err != nil {
return nil, err
}
privKey := algo.Generate()(derivedPriv)
return ks.writeLocalKey(uid, privKey, algo.Name())
}
func (ks keystore) isSupportedSigningAlgo(algo SignatureAlgo) bool {
return ks.options.SupportedAlgos.Contains(algo)
}
func (ks keystore) Key(uid string) (Info, error) {
key := infoKey(uid)
bs, err := ks.db.Get(string(key))
if err != nil {
return nil, err
}
if len(bs.Data) == 0 {
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, uid)
}
return unmarshalInfo(bs.Data)
}
// SignWithLedger signs a binary message with the ledger device referenced by an Info object
// and returns the signed bytes and the public key. It returns an error if the device could
// not be queried or it returned an error.
func SignWithLedger(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
switch info.(type) {
case *ledgerInfo, ledgerInfo:
default:
return nil, nil, errors.New("not a ledger object")
}
path, err := info.GetPath()
if err != nil {
return
}
priv, err := crypto.NewPrivKeyLedgerSecp256k1Unsafe(*path)
if err != nil {
return
}
sig, err = priv.Sign(msg)
if err != nil {
return nil, nil, err
}
return sig, priv.PubKey(), nil
}
func newOSBackendKeyringConfig(appName, dir string, buf io.Reader) keyring.Config {
return keyring.Config{
ServiceName: appName,
FileDir: dir,
@ -485,6 +551,17 @@ func lkbToKeyringConfig(appName, dir string, buf io.Reader, test bool) keyring.C
}
}
func newTestBackendKeyringConfig(appName, dir string) keyring.Config {
return keyring.Config{
AllowedBackends: []keyring.BackendType{keyring.FileBackend},
ServiceName: appName,
FileDir: filepath.Join(dir, keyringTestDirName),
FilePasswordFunc: func(_ string) (string, error) {
return "test", nil
},
}
}
func newKWalletBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Config {
return keyring.Config{
AllowedBackends: []keyring.BackendType{keyring.KWalletBackend},
@ -494,7 +571,7 @@ func newKWalletBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Conf
}
}
func newPassBackendKeyringConfig(appName, dir string, _ io.Reader) keyring.Config {
func newPassBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Config {
prefix := fmt.Sprintf(passKeyringPrefix, appName)
return keyring.Config{
AllowedBackends: []keyring.BackendType{keyring.PassBackend},
@ -504,7 +581,7 @@ func newPassBackendKeyringConfig(appName, dir string, _ io.Reader) keyring.Confi
}
func newFileBackendKeyringConfig(name, dir string, buf io.Reader) keyring.Config {
fileDir := filepath.Join(dir, fmt.Sprintf(keyringDirNameFmt, name))
fileDir := filepath.Join(dir, keyringFileDirName)
return keyring.Config{
AllowedBackends: []keyring.BackendType{keyring.FileBackend},
ServiceName: name,
@ -586,6 +663,88 @@ func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) {
}
}
func addrHexKey(address types.AccAddress) []byte {
return []byte(fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix))
func (ks keystore) writeLocalKey(name string, priv tmcrypto.PrivKey, algo hd.PubKeyType) (Info, error) {
// encrypt private key using keyring
pub := priv.PubKey()
info := newLocalInfo(name, pub, string(priv.Bytes()), algo)
err := ks.writeInfo(info)
if err != nil {
return nil, err
}
return info, nil
}
func (ks keystore) writeInfo(info Info) error {
// write the info by key
key := infoKey(info.GetName())
serializedInfo := marshalInfo(info)
exists, err := ks.existsInDb(info)
if exists {
return fmt.Errorf("public key already exist in keybase")
}
if err != nil {
return err
}
err = ks.db.Set(keyring.Item{
Key: string(key),
Data: serializedInfo,
})
if err != nil {
return err
}
err = ks.db.Set(keyring.Item{
Key: addrHexKeyAsString(info.GetAddress()),
Data: key,
})
if err != nil {
return err
}
return nil
}
func (ks keystore) existsInDb(info Info) (bool, error) {
if _, err := ks.db.Get(addrHexKeyAsString(info.GetAddress())); err == nil {
return true, nil // address lookup succeeds - info exists
} else if err != keyring.ErrKeyNotFound {
return false, err // received unexpected error - returns error
}
if _, err := ks.db.Get(string(infoKey(info.GetName()))); err == nil {
return true, nil // uid lookup succeeds - info exists
} else if err != keyring.ErrKeyNotFound {
return false, err // received unexpected error - returns
}
// both lookups failed, info does not exist
return false, nil
}
func (ks keystore) writeOfflineKey(name string, pub tmcrypto.PubKey, algo hd.PubKeyType) (Info, error) {
info := newOfflineInfo(name, pub, algo)
err := ks.writeInfo(info)
if err != nil {
return nil, err
}
return info, nil
}
func (ks keystore) writeMultisigKey(name string, pub tmcrypto.PubKey) (Info, error) {
info := NewMultiInfo(name, pub)
err := ks.writeInfo(info)
if err != nil {
return nil, err
}
return info, nil
}
func addrHexKeyAsString(address sdk.Address) string {
return fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix)
}

View File

@ -0,0 +1,128 @@
//+build ledger test_ledger_mock
package keyring
import (
"bytes"
"testing"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/cosmos/cosmos-sdk/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)
func TestInMemoryCreateLedger(t *testing.T) {
kb := NewInMemory()
ledger, err := kb.SaveLedgerKey("some_account", hd.Secp256k1, "cosmos", 118, 3, 1)
if err != nil {
require.Error(t, err)
require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
require.Nil(t, ledger)
t.Skip("ledger nano S: support for ledger devices is not available in this executable")
return
}
// The mock is available, check that the address is correct
pubKey := ledger.GetPubKey()
pk, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
require.NoError(t, err)
require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
// Check that restoring the key gets the same results
restoredKey, err := kb.Key("some_account")
require.NoError(t, err)
require.NotNil(t, restoredKey)
require.Equal(t, "some_account", restoredKey.GetName())
require.Equal(t, TypeLedger, restoredKey.GetType())
pubKey = restoredKey.GetPubKey()
pk, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
require.NoError(t, err)
require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
path, err := restoredKey.GetPath()
require.NoError(t, err)
require.Equal(t, "44'/118'/3'/0/1", path.String())
}
// TestSignVerify does some detailed checks on how we sign and validate
// signatures
func TestSignVerifyKeyRingWithLedger(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
t.Cleanup(cleanup)
kb, err := New("keybasename", "test", dir, nil)
require.NoError(t, err)
i1, err := kb.SaveLedgerKey("key", hd.Secp256k1, "cosmos", 118, 0, 0)
if err != nil {
require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
t.Skip("ledger nano S: support for ledger devices is not available in this executable")
return
}
require.Equal(t, "key", i1.GetName())
d1 := []byte("my first message")
s1, pub1, err := kb.Sign("key", d1)
require.NoError(t, err)
s2, pub2, err := SignWithLedger(i1, d1)
require.NoError(t, err)
require.True(t, pub1.Equals(pub2))
require.True(t, bytes.Equal(s1, s2))
require.Equal(t, i1.GetPubKey(), pub1)
require.Equal(t, i1.GetPubKey(), pub2)
require.True(t, pub1.VerifyBytes(d1, s1))
require.True(t, i1.GetPubKey().VerifyBytes(d1, s1))
require.True(t, bytes.Equal(s1, s2))
localInfo, _, err := kb.NewMnemonic("test", English, types.FullFundraiserPath, hd.Secp256k1)
require.NoError(t, err)
_, _, err = SignWithLedger(localInfo, d1)
require.Error(t, err)
require.Equal(t, "not a ledger object", err.Error())
}
func TestAltKeyring_SaveLedgerKey(t *testing.T) {
dir, clean := tests.NewTestCaseDir(t)
t.Cleanup(clean)
keyring, err := New(t.Name(), BackendTest, dir, nil)
require.NoError(t, err)
// Test unsupported Algo
_, err = keyring.SaveLedgerKey("key", notSupportedAlgo{}, "cosmos", 118, 0, 0)
require.EqualError(t, err, ErrUnsupportedSigningAlgo.Error())
ledger, err := keyring.SaveLedgerKey("some_account", hd.Secp256k1, "cosmos", 118, 3, 1)
if err != nil {
require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
t.Skip("ledger nano S: support for ledger devices is not available in this executable")
return
}
// The mock is available, check that the address is correct
require.Equal(t, "some_account", ledger.GetName())
pubKey := ledger.GetPubKey()
pk, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
require.NoError(t, err)
require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
// Check that restoring the key gets the same results
restoredKey, err := keyring.Key("some_account")
require.NoError(t, err)
require.NotNil(t, restoredKey)
require.Equal(t, "some_account", restoredKey.GetName())
require.Equal(t, TypeLedger, restoredKey.GetType())
pubKey = restoredKey.GetPubKey()
pk, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
require.NoError(t, err)
require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
path, err := restoredKey.GetPath()
require.NoError(t, err)
require.Equal(t, "44'/118'/3'/0/1", path.String())
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ package keyring
import (
"fmt"
"io"
"strings"
"github.com/pkg/errors"
@ -179,10 +180,53 @@ func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string,
}
// Close the underlying storage.
func (kb dbKeybase) Close() error {
return kb.db.Close()
func (kb dbKeybase) Close() error { return kb.db.Close() }
func infoKey(name string) []byte { return []byte(fmt.Sprintf("%s.%s", name, infoSuffix)) }
// InfoImporter is implemented by those types that want to provide functions necessary
// to migrate keys from LegacyKeybase types to Keyring types.
type InfoImporter interface {
// Import imports ASCII-armored private keys.
Import(uid string, armor string) error
}
func infoKey(name string) []byte {
return []byte(fmt.Sprintf("%s.%s", name, infoSuffix))
type keyringMigrator struct {
kr keystore
}
func NewInfoImporter(
appName, backend, rootDir string, userInput io.Reader, opts ...Option,
) (InfoImporter, error) {
keyring, err := New(appName, backend, rootDir, userInput, opts...)
if err != nil {
return keyringMigrator{}, err
}
kr := keyring.(keystore)
return keyringMigrator{kr}, nil
}
func (m keyringMigrator) Import(uid string, armor string) error {
_, err := m.kr.Key(uid)
if err == nil {
return fmt.Errorf("cannot overwrite key %q", uid)
}
infoBytes, err := crypto.UnarmorInfoBytes(armor)
if err != nil {
return err
}
info, err := unmarshalInfo(infoBytes)
if err != nil {
return err
}
return m.kr.writeInfo(info)
}
// KeybaseOption overrides options for the db.
type KeybaseOption func(*kbOptions)
type kbOptions struct {
}

View File

@ -1,6 +1,7 @@
package keyring_test
import (
"io"
"path/filepath"
"testing"
@ -38,7 +39,21 @@ func TestLegacyKeybase(t *testing.T) {
require.NoError(t, err)
require.NotEmpty(t, armor)
_, err = kb.ExportPrivKey(keys[0].GetName(), "12345678", "12345678")
require.Error(t, err)
armoredInfo, err := kb.Export(keys[0].GetName())
require.NoError(t, err)
require.NotEmpty(t, armoredInfo)
importer, err := keyring.NewInfoImporter("cosmos", "memory", "", nil)
require.NoError(t, err)
err = importer.Import("test", "")
require.Error(t, err)
require.Equal(t, io.EOF, err)
require.NoError(t, importer.Import("test", armoredInfo))
err = importer.Import("test", armoredInfo)
require.Error(t, err)
require.Equal(t, `public key already exist in keybase`, err.Error())
}

View File

@ -1,39 +0,0 @@
package keyring
// KeybaseOption overrides options for the db
type KeybaseOption func(*kbOptions)
type kbOptions struct {
keygenFunc PrivKeyGenFunc
deriveFunc DeriveKeyFunc
supportedAlgos []SigningAlgo
supportedAlgosLedger []SigningAlgo
}
// WithKeygenFunc applies an overridden key generation function to generate the private key.
func WithKeygenFunc(f PrivKeyGenFunc) KeybaseOption {
return func(o *kbOptions) {
o.keygenFunc = f
}
}
// 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
}
}

View File

@ -1,26 +1,33 @@
package keyring
// SigningAlgo defines an algorithm to derive key-pairs which can be used for cryptographic signing.
type SigningAlgo string
import (
"fmt"
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")
"github.com/cosmos/cosmos-sdk/crypto/hd"
)
// 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 {
type SignatureAlgo interface {
Name() hd.PubKeyType
Derive() hd.DeriveFn
Generate() hd.GenerateFn
}
func NewSigningAlgoFromString(str string) (SignatureAlgo, error) {
if str != string(hd.Secp256k1.Name()) {
return nil, fmt.Errorf("provided algorithm `%s` is not supported", str)
}
return hd.Secp256k1, nil
}
type SigningAlgoList []SignatureAlgo
func (l SigningAlgoList) Contains(algo SignatureAlgo) bool {
for _, cAlgo := range l {
if cAlgo.Name() == algo.Name() {
return true
}
}
return false
}

View File

@ -0,0 +1,71 @@
package keyring
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/hd"
)
func TestNewSigningAlgoByString(t *testing.T) {
tests := []struct {
name string
algoStr string
isSupported bool
expectedAlgo SignatureAlgo
expectedErr error
}{
{
"supported algorithm",
"secp256k1",
true,
hd.Secp256k1,
nil,
},
{
"not supported",
"notsupportedalgo",
false,
nil,
fmt.Errorf("provided algorithm `notsupportedalgo` is not supported"),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
algorithm, err := NewSigningAlgoFromString(tt.algoStr)
if tt.isSupported {
require.Equal(t, hd.Secp256k1, algorithm)
} else {
require.EqualError(t, err, tt.expectedErr.Error())
}
})
}
}
func TestAltSigningAlgoList_Contains(t *testing.T) {
list := SigningAlgoList{
hd.Secp256k1,
}
assert.True(t, list.Contains(hd.Secp256k1))
assert.False(t, list.Contains(notSupportedAlgo{}))
}
type notSupportedAlgo struct {
}
func (n notSupportedAlgo) Name() hd.PubKeyType {
return "notSupported"
}
func (n notSupportedAlgo) Derive() hd.DeriveFn {
return hd.Secp256k1.Derive()
}
func (n notSupportedAlgo) Generate() hd.GenerateFn {
return hd.Secp256k1.Generate()
}

View File

@ -1,6 +1,10 @@
package keyring
import "github.com/tendermint/tendermint/crypto"
import (
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/crypto/hd"
)
// Language is a language to create the BIP 39 mnemonic in.
// Currently, only english is supported though.
@ -62,7 +66,7 @@ func (kt KeyType) String() string {
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)
DeriveKeyFunc func(mnemonic string, bip39Passphrase, hdPath string, algo hd.PubKeyType) ([]byte, error)
// PrivKeyGenFunc defines the function to convert derived key bytes to a tendermint private key
PrivKeyGenFunc func(bz []byte, algo SigningAlgo) (crypto.PrivKey, error)
PrivKeyGenFunc func(bz []byte, algo hd.PubKeyType) (crypto.PrivKey, error)
)

View File

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/hd"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -16,7 +16,7 @@ func Test_writeReadLedgerInfo(t *testing.T) {
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
copy(tmpKey[:], bz)
lInfo := newLedgerInfo("some_name", tmpKey, *hd.NewFundraiserParams(5, sdk.CoinType, 1), Secp256k1)
lInfo := newLedgerInfo("some_name", tmpKey, *hd.NewFundraiserParams(5, sdk.CoinType, 1), hd.Secp256k1Type)
assert.Equal(t, TypeLedger, lInfo.GetType())
path, err := lInfo.GetPath()

View File

@ -14,7 +14,7 @@ import (
bip39 "github.com/cosmos/go-bip39"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
)

View File

@ -11,7 +11,7 @@ import (
tmcrypto "github.com/tendermint/tendermint/crypto"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/hd"
)
var (

View File

@ -9,7 +9,7 @@ import (
tmcrypto "github.com/tendermint/tendermint/crypto"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
)

View File

@ -3,7 +3,9 @@ package server
import (
"fmt"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -11,31 +13,39 @@ import (
// GenerateCoinKey returns the address of a public key, along with the secret
// phrase to recover the private key.
func GenerateCoinKey() (sdk.AccAddress, string, error) {
// generate a private key, with recovery phrase
info, secret, err := keyring.NewInMemory().CreateMnemonic(
"name", keyring.English, "pass", keyring.Secp256k1)
info, secret, err := keyring.NewInMemory().NewMnemonic("name", keyring.English, types.FullFundraiserPath, hd.Secp256k1)
if err != nil {
return sdk.AccAddress([]byte{}), "", err
}
addr := info.GetPubKey().Address()
return sdk.AccAddress(addr), secret, nil
return sdk.AccAddress(info.GetPubKey().Address()), secret, nil
}
// GenerateSaveCoinKey returns the address of a public key, along with the secret
// phrase to recover the private key.
func GenerateSaveCoinKey(keybase keyring.Keybase, keyName, keyPass string, overwrite bool) (sdk.AccAddress, string, error) {
// ensure no overwrite
if !overwrite {
_, err := keybase.Get(keyName)
func GenerateSaveCoinKey(keybase keyring.Keyring, keyName, keyPass string, overwrite bool) (sdk.AccAddress, string, error) {
exists := false
_, err := keybase.Key(keyName)
if err == nil {
exists = true
}
// ensure no overwrite
if !overwrite && exists {
return sdk.AccAddress([]byte{}), "", fmt.Errorf(
"key already exists, overwrite is disabled")
}
}
// generate a private key, with recovery phrase
info, secret, err := keybase.CreateMnemonic(keyName, keyring.English, keyPass, keyring.Secp256k1)
if exists {
err = keybase.Delete(keyName)
if err != nil {
return sdk.AccAddress([]byte{}), "", fmt.Errorf(
"failed to overwrite key")
}
}
info, secret, err := keybase.NewMnemonic(keyName, keyring.English, types.FullFundraiserPath, hd.Secp256k1)
if err != nil {
return sdk.AccAddress([]byte{}), "", err
}

View File

@ -5,9 +5,11 @@ import (
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/cosmos/cosmos-sdk/types"
)
func TestGenerateCoinKey(t *testing.T) {
@ -16,7 +18,7 @@ func TestGenerateCoinKey(t *testing.T) {
require.NoError(t, err)
// Test creation
info, err := keyring.NewInMemory().CreateAccount("xxx", mnemonic, "", "012345678", keyring.CreateHDPath(0, 0).String(), keyring.Secp256k1)
info, err := keyring.NewInMemory().NewAccount("xxx", mnemonic, "", hd.NewFundraiserParams(0, types.GetConfig().GetCoinType(), 0).String(), hd.Secp256k1)
require.NoError(t, err)
require.Equal(t, addr, info.GetAddress())
}
@ -26,19 +28,19 @@ func TestGenerateSaveCoinKey(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
t.Cleanup(cleanup)
kb, err := keyring.NewKeyring(t.Name(), "test", dir, nil)
kb, err := keyring.New(t.Name(), "test", dir, nil)
require.NoError(t, err)
addr, mnemonic, err := server.GenerateSaveCoinKey(kb, "keyname", "012345678", false)
require.NoError(t, err)
// Test key was actually saved
info, err := kb.Get("keyname")
info, err := kb.Key("keyname")
require.NoError(t, err)
require.Equal(t, addr, info.GetAddress())
// Test in-memory recovery
info, err = keyring.NewInMemory().CreateAccount("xxx", mnemonic, "", "012345678", keyring.CreateHDPath(0, 0).String(), keyring.Secp256k1)
info, err = keyring.NewInMemory().NewAccount("xxx", mnemonic, "", hd.NewFundraiserParams(0, types.GetConfig().GetCoinType(), 0).String(), hd.Secp256k1)
require.NoError(t, err)
require.Equal(t, addr, info.GetAddress())
}
@ -48,7 +50,7 @@ func TestGenerateSaveCoinKeyOverwriteFlag(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
t.Cleanup(cleanup)
kb, err := keyring.NewKeyring(t.Name(), "test", dir, nil)
kb, err := keyring.New(t.Name(), "test", dir, nil)
require.NoError(t, err)
keyname := "justakey"

View File

@ -65,13 +65,13 @@ func makeMultiSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string)
}
inBuf := bufio.NewReader(cmd.InOrStdin())
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(),
kb, err := keyring.New(sdk.KeyringServiceName(),
viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), inBuf)
if err != nil {
return
}
multisigInfo, err := kb.Get(args[1])
multisigInfo, err := kb.Key(args[1])
if err != nil {
return
}

View File

@ -97,7 +97,7 @@ func CompleteAndBroadcastTxCLI(txBldr authtypes.TxBuilder, cliCtx context.CLICon
_, _ = fmt.Fprintf(os.Stderr, "%s\n\n", json)
buf := bufio.NewReader(os.Stdin)
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf)
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr)
if err != nil || !ok {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction")
return err
@ -184,7 +184,7 @@ func SignStdTx(
var signedStdTx authtypes.StdTx
info, err := txBldr.Keybase().Get(name)
info, err := txBldr.Keybase().Key(name)
if err != nil {
return signedStdTx, err
}

View File

@ -4,8 +4,9 @@ import (
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)
func TestParamsEqual(t *testing.T) {

View File

@ -17,7 +17,7 @@ import (
// TxBuilder implements a transaction context created in SDK modules.
type TxBuilder struct {
txEncoder sdk.TxEncoder
keybase keyring.Keybase
keybase keyring.Keyring
accountNumber uint64
sequence uint64
gas uint64
@ -53,7 +53,7 @@ func NewTxBuilder(
// NewTxBuilderFromCLI returns a new initialized TxBuilder with parameters from
// the command line using Viper.
func NewTxBuilderFromCLI(input io.Reader) TxBuilder {
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), input)
kb, err := keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), input)
if err != nil {
panic(err)
}
@ -90,7 +90,7 @@ func (bldr TxBuilder) Gas() uint64 { return bldr.gas }
func (bldr TxBuilder) GasAdjustment() float64 { return bldr.gasAdjustment }
// Keybase returns the keybase
func (bldr TxBuilder) Keybase() keyring.Keybase { return bldr.keybase }
func (bldr TxBuilder) Keybase() keyring.Keyring { return bldr.keybase }
// SimulateAndExecute returns the option to simulate and then execute the transaction
// using the gas from the simulation results
@ -149,7 +149,7 @@ func (bldr TxBuilder) WithGasPrices(gasPrices string) TxBuilder {
}
// WithKeybase returns a copy of the context with updated keybase.
func (bldr TxBuilder) WithKeybase(keybase keyring.Keybase) TxBuilder {
func (bldr TxBuilder) WithKeybase(keybase keyring.Keyring) TxBuilder {
bldr.keybase = keybase
return bldr
}
@ -272,17 +272,17 @@ func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx StdTx, appendSig
}
// MakeSignature builds a StdSignature given keybase, key name, passphrase, and a StdSignMsg.
func MakeSignature(keybase keyring.Keybase, name, passphrase string,
func MakeSignature(keybase keyring.Keyring, name, passphrase string,
msg StdSignMsg) (sig StdSignature, err error) {
if keybase == nil {
keybase, err = keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), os.Stdin)
keybase, err = keyring.New(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), os.Stdin)
if err != nil {
return
}
}
sigBytes, pubkey, err := keybase.Sign(name, passphrase, msg.Bytes())
sigBytes, pubkey, err := keybase.Sign(name, msg.Bytes())
if err != nil {
return
}

View File

@ -93,14 +93,14 @@ func GenTxCmd(ctx *server.Context, cdc *codec.Codec, mbm module.BasicManager, sm
}
inBuf := bufio.NewReader(cmd.InOrStdin())
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(),
kb, err := keyring.New(sdk.KeyringServiceName(),
viper.GetString(flags.FlagKeyringBackend), viper.GetString(flagClientHome), inBuf)
if err != nil {
return errors.Wrap(err, "failed to initialize keybase")
}
name := viper.GetString(flags.FlagName)
key, err := kb.Get(name)
key, err := kb.Key(name)
if err != nil {
return errors.Wrap(err, "failed to read from keybase")
}