Merge PR #3461: GaiaCLI - refactor/fix --account and --index

This commit is contained in:
Juan Leni 2019-02-05 17:22:56 +01:00 committed by Jack Zampolin
parent 5e35354269
commit f5ada58780
19 changed files with 559 additions and 408 deletions

View File

@ -42,12 +42,6 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
return pass, nil
}
// GetSeed will request a seed phrase from stdin and trims off
// leading/trailing spaces
func GetSeed(prompt string, buf *bufio.Reader) (string, error) {
return GetString(prompt, buf)
}
// 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

View File

@ -9,26 +9,24 @@ import (
"os"
"sort"
"github.com/tendermint/tendermint/crypto/multisig"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/crypto/keys"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/go-bip39"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client"
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/multisig"
"github.com/tendermint/tendermint/libs/cli"
)
const (
flagInteractive = "interactive"
flagBIP44Path = "bip44-path"
flagRecover = "recover"
flagNoBackup = "no-backup"
flagDryRun = "dry-run"
@ -38,6 +36,11 @@ const (
flagNoSort = "nosort"
)
const (
maxValidAccountValue = int(0x80000000 - 1)
maxValidIndexalue = int(0x80000000 - 1)
)
func addKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add <name>",
@ -68,7 +71,6 @@ the flag --nosort is set.
cmd.Flags().String(FlagPublicKey, "", "Parse a public key in bech32 format and save it to disk")
cmd.Flags().BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic")
cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
cmd.Flags().String(flagBIP44Path, "44'/118'/0'/0/0", "BIP44 path from which to derive a private key")
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
@ -95,24 +97,25 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
name := args[0]
interactive := viper.GetBool(flagInteractive)
showMnemonic := viper.GetBool(flagNoBackup)
if viper.GetBool(flagDryRun) {
// we throw this away, so don't enforce args,
// we want to get a new random seed phrase quickly
kb = client.MockKeyBase()
encryptPassword = "throwing-this-key-away"
encryptPassword = app.DefaultKeyPass
} else {
kb, err = GetKeyBaseWithWritePerm()
if err != nil {
return err
}
_, err := kb.Get(name)
_, err = kb.Get(name)
if err == nil {
// account exists, ask for user confirmation
if response, err := client.GetConfirmation(
fmt.Sprintf("override the existing name %s", name), buf); err != nil || !response {
return err
if response, err2 := client.GetConfirmation(
fmt.Sprintf("override the existing name %s", name), buf); err2 != nil || !response {
return err2
}
}
@ -144,6 +147,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
if _, err := kb.CreateOffline(name, pk); err != nil {
return err
}
fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name)
return nil
}
@ -164,51 +168,37 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
kb.CreateOffline(name, pk)
return nil
}
bipFlag := cmd.Flags().Lookup(flagBIP44Path)
bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || !interactive)
_, err = kb.CreateOffline(name, pk)
if err != nil {
return err
}
return nil
}
// If we're using ledger, only thing we need is the path. So generate key and
// we're done.
if viper.GetBool(client.FlagUseLedger) {
account := uint32(viper.GetInt(flagAccount))
index := uint32(viper.GetInt(flagIndex))
path := ccrypto.DerivationPath{44, 118, account, 0, index}
info, err := kb.CreateLedger(name, path, keys.Secp256k1)
// If we're using ledger, only thing we need is the path. So generate key and we're done.
if viper.GetBool(client.FlagUseLedger) {
info, err := kb.CreateLedger(name, keys.Secp256k1, account, index)
if err != nil {
return err
}
printCreate(info, "")
return nil
}
// Recover key from seed passphrase
if viper.GetBool(flagRecover) {
seed, err := client.GetSeed(
"Enter your recovery seed phrase:", buf)
if err != nil {
return err
}
info, err := kb.CreateKey(name, seed, encryptPassword)
if err != nil {
return err
}
// print out results without the seed phrase
viper.Set(flagNoBackup, true)
printCreate(info, "")
return nil
return printCreate(info, false, "")
}
// Get bip39 mnemonic
var mnemonic string
if interactive {
mnemonic, err = client.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", buf)
var bip39Passphrase string
if interactive || viper.GetBool(flagRecover) {
bip39Message := "Enter your bip39 mnemonic"
if !viper.GetBool(flagRecover) {
bip39Message = "Enter your bip39 mnemonic, or hit enter to generate one."
}
mnemonic, err = client.GetString(bip39Message, buf)
if err != nil {
return err
}
@ -227,8 +217,12 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
}
}
// get bip39 passphrase
var bip39Passphrase string
if !bip39.IsMnemonicValid(mnemonic) {
fmt.Fprintf(os.Stderr, "Error: Mnemonic is not valid")
return nil
}
// override bip39 passphrase
if interactive {
bip39Passphrase, err = client.GetString(
"Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed. "+
@ -250,173 +244,158 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
}
}
info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params)
info, err := kb.CreateAccount(name, mnemonic, keys.DefaultBIP39Passphrase, encryptPassword, account, index)
if err != nil {
return err
}
printCreate(info, mnemonic)
return nil
// Recover key from seed passphrase
if viper.GetBool(flagRecover) {
// Hide mnemonic from output
showMnemonic = false
mnemonic = ""
}
func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) {
buf := client.BufferStdin()
bip44Path := path
// if it wasn't set in the flag, give it a chance to overide interactively
if !flagSet {
var err error
bip44Path, err = client.GetString(fmt.Sprintf("Enter your bip44 path. Default is %s\n", path), buf)
if err != nil {
return nil, err
return printCreate(info, showMnemonic, mnemonic)
}
if len(bip44Path) == 0 {
bip44Path = path
}
}
bip44params, err := hd.NewParamsFromPath(bip44Path)
if err != nil {
return nil, err
}
return bip44params, nil
}
func printCreate(info keys.Info, seed string) {
func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {
output := viper.Get(cli.OutputFlag)
switch output {
case "text":
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr)
printKeyInfo(info, Bech32KeyOutput)
// print seed unless requested not to.
if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) {
fmt.Fprintln(os.Stderr, "\n**Important** write this seed phrase in a safe place.")
// print mnemonic unless requested not to.
if showMnemonic {
fmt.Fprintln(os.Stderr, "\n**Important** write this mnemonic phrase in a safe place.")
fmt.Fprintln(os.Stderr, "It is the only way to recover your account if you ever forget your password.")
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, seed)
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, mnemonic)
}
case "json":
out, err := Bech32KeyOutput(info)
if err != nil {
panic(err)
return err
}
if !viper.GetBool(flagNoBackup) {
out.Seed = seed
if showMnemonic {
out.Mnemonic = mnemonic
}
var jsonString []byte
if viper.GetBool(client.FlagIndentResponse) {
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
} else {
jsonString, err = cdc.MarshalJSON(out)
}
if err != nil {
panic(err) // really shouldn't happen...
return err
}
fmt.Fprintln(os.Stderr, string(jsonString))
default:
panic(fmt.Sprintf("I can't speak: %s", output))
}
return errors.Errorf("I can't speak: %s", output)
}
// function to just a new seed to display in the UI before actually persisting it in the keybase
func getSeed(algo keys.SigningAlgo) string {
kb := client.MockKeyBase()
pass := "throwing-this-key-away"
name := "inmemorykey"
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
return seed
}
func printPrefixed(msg string) {
fmt.Fprintln(os.Stderr, msg)
}
func printStep() {
printPrefixed("-------------------------------------")
return nil
}
/////////////////////////////
// REST
// new key request REST body
type NewKeyBody struct {
Name string `json:"name"`
Password string `json:"password"`
Seed string `json:"seed"`
// function to just create a new seed to display in the UI before actually persisting it in the keybase
func generateMnemonic(algo keys.SigningAlgo) string {
kb := client.MockKeyBase()
pass := app.DefaultKeyPass
name := "inmemorykey"
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
return seed
}
// CheckAndWriteErrorResponse will check for errors and return
// a given error message when corresponding
//TODO: Move to utils/rest or similar
func CheckAndWriteErrorResponse(w http.ResponseWriter, httpErr int, err error) bool {
if err != nil {
w.WriteHeader(httpErr)
_, _ = w.Write([]byte(err.Error()))
return true
}
return false
}
// add new key REST handler
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var kb keys.Keybase
var m NewKeyBody
var m AddNewKey
kb, err := GetKeyBaseWithWritePerm()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
return
}
err = json.Unmarshal(body, &m)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
return
}
// Check parameters
if m.Name == "" {
w.WriteHeader(http.StatusBadRequest)
err = errMissingName()
w.Write([]byte(err.Error()))
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
return
}
if m.Password == "" {
w.WriteHeader(http.StatusBadRequest)
err = errMissingPassword()
w.Write([]byte(err.Error()))
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
return
}
// check if already exists
infos, err := kb.List()
for _, info := range infos {
if info.GetName() == m.Name {
w.WriteHeader(http.StatusConflict)
err = errKeyNameConflict(m.Name)
w.Write([]byte(err.Error()))
mnemonic := m.Mnemonic
// if mnemonic is empty, generate one
if mnemonic == "" {
mnemonic = generateMnemonic(keys.Secp256k1)
}
if !bip39.IsMnemonicValid(mnemonic) {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
}
if m.Account < 0 || m.Account > maxValidAccountValue {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidAccountNumber())
return
}
if m.Index < 0 || m.Index > maxValidIndexalue {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidIndexNumber())
return
}
_, err = kb.Get(m.Name)
if err == nil {
CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(m.Name))
return
}
// create account
seed := m.Seed
if seed == "" {
seed = getSeed(keys.Secp256k1)
}
info, err := kb.CreateKey(m.Name, seed, m.Password)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
account := uint32(m.Account)
index := uint32(m.Index)
info, err := kb.CreateAccount(m.Name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index)
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return
}
keyOutput, err := Bech32KeyOutput(info)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return
}
keyOutput.Seed = seed
keyOutput.Mnemonic = mnemonic
PostProcessResponse(w, cdc, keyOutput, indent)
}
@ -426,22 +405,17 @@ func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
algoType := vars["type"]
// algo type defaults to secp256k1
if algoType == "" {
algoType = "secp256k1"
}
algo := keys.SigningAlgo(algoType)
seed := getSeed(algo)
algo := keys.SigningAlgo(algoType)
seed := generateMnemonic(algo)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(seed))
}
// RecoverKeyBody is recover key request REST body
type RecoverKeyBody struct {
Password string `json:"password"`
Seed string `json:"seed"`
_, _ = w.Write([]byte(seed))
}
// RecoverRequestHandler performs key recover request
@ -449,67 +423,66 @@ func RecoverRequestHandler(indent bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
var m RecoverKeyBody
var m RecoverKey
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
err = cdc.UnmarshalJSON(body, &m)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
return
}
if name == "" {
w.WriteHeader(http.StatusBadRequest)
err = errMissingName()
w.Write([]byte(err.Error()))
return
}
if m.Password == "" {
w.WriteHeader(http.StatusBadRequest)
err = errMissingPassword()
w.Write([]byte(err.Error()))
return
}
if m.Seed == "" {
w.WriteHeader(http.StatusBadRequest)
err = errMissingSeed()
w.Write([]byte(err.Error()))
err = cdc.UnmarshalJSON(body, &m)
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
return
}
kb, err := GetKeyBaseWithWritePerm()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err)
if name == "" {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
return
}
// check if already exists
infos, err := kb.List()
for _, info := range infos {
if info.GetName() == name {
w.WriteHeader(http.StatusConflict)
err = errKeyNameConflict(name)
w.Write([]byte(err.Error()))
if m.Password == "" {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
return
}
}
info, err := kb.CreateKey(name, m.Seed, m.Password)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
mnemonic := m.Mnemonic
if !bip39.IsMnemonicValid(mnemonic) {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
}
if m.Mnemonic == "" {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingMnemonic())
return
}
if m.Account < 0 || m.Account > maxValidAccountValue {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidAccountNumber())
return
}
if m.Index < 0 || m.Index > maxValidIndexalue {
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidIndexNumber())
return
}
_, err = kb.Get(name)
if err == nil {
CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(name))
return
}
account := uint32(m.Account)
index := uint32(m.Index)
info, err := kb.CreateAccount(name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index)
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return
}
keyOutput, err := Bech32KeyOutput(info)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return
}

View File

@ -13,8 +13,8 @@ import (
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client"
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
keyerror "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/spf13/cobra"
)

View File

@ -3,7 +3,7 @@ package keys
import "fmt"
func errKeyNameConflict(name string) error {
return fmt.Errorf("acount with name %s already exists", name)
return fmt.Errorf("account with name %s already exists", name)
}
func errMissingName() error {
@ -14,6 +14,18 @@ func errMissingPassword() error {
return fmt.Errorf("you have to specify a password for the locally stored account")
}
func errMissingSeed() error {
return fmt.Errorf("you have to specify seed for key recover")
func errMissingMnemonic() error {
return fmt.Errorf("you have to specify a mnemonic for key recovery")
}
func errInvalidMnemonic() error {
return fmt.Errorf("the mnemonic is invalid")
}
func errInvalidAccountNumber() error {
return fmt.Errorf("the account number is invalid")
}
func errInvalidIndexNumber() error {
return fmt.Errorf("the index number is invalid")
}

View File

@ -4,11 +4,9 @@ import (
"crypto/sha256"
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
bip39 "github.com/bartekn/go-bip39"
"github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/cobra"
)
const (
@ -58,7 +56,6 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
// hash input entropy to get entropy seed
hashedEntropy := sha256.Sum256([]byte(inputEntropy))
entropySeed = hashedEntropy[:]
printStep()
} else {
// read entropy seed straight from crypto.Rand
var err error

38
client/keys/types.go Normal file
View File

@ -0,0 +1,38 @@
package keys
// used for outputting keys.Info over REST
type KeyOutput struct {
Name string `json:"name"`
Type string `json:"type"`
Address string `json:"address"`
PubKey string `json:"pub_key"`
Mnemonic string `json:"mnemonic,omitempty"`
}
// AddNewKey request a new key
type AddNewKey struct {
Name string `json:"name"`
Password string `json:"password"`
Mnemonic string `json:"mnemonic"`
Account int `json:"account,string,omitempty"`
Index int `json:"index,string,omitempty"`
}
// RecoverKeyBody recovers a key
type RecoverKey struct {
Password string `json:"password"`
Mnemonic string `json:"mnemonic"`
Account int `json:"account,string,omitempty"`
Index int `json:"index,string,omitempty"`
}
// UpdateKeyReq requests updating a key
type UpdateKeyReq struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
// DeleteKeyReq requests deleting a key
type DeleteKeyReq struct {
Password string `json:"password"`
}

View File

@ -118,15 +118,6 @@ func SetKeyBase(kb keys.Keybase) {
keybase = kb
}
// used for outputting keys.Info over REST
type KeyOutput struct {
Name string `json:"name"`
Type string `json:"type"`
Address string `json:"address"`
PubKey string `json:"pub_key"`
Seed string `json:"seed,omitempty"`
}
// create a list of KeyOutput in bech32 format
func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) {
kos := make([]KeyOutput, len(infos))

View File

@ -12,10 +12,9 @@ import (
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
client "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/rest"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -47,41 +46,100 @@ func init() {
version.Version = os.Getenv("VERSION")
}
func TestKeys(t *testing.T) {
func TestSeedsAreDifferent(t *testing.T) {
addr, _ := CreateAddr(t, name1, pw, GetKeyBase(t))
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})
defer cleanup()
// get new seed
seed := getKeysSeed(t, port)
mnemonic1 := getKeysSeed(t, port)
mnemonic2 := getKeysSeed(t, port)
require.NotEqual(t, mnemonic1, mnemonic2)
}
func TestKeyRecover(t *testing.T) {
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{})
defer cleanup()
myName1 := "TestKeyRecover_1"
myName2 := "TestKeyRecover_2"
mnemonic := getKeysSeed(t, port)
expectedInfo, _ := GetKeyBase(t).CreateAccount(myName1, mnemonic, "", pw, 0, 0)
expectedAddress := expectedInfo.GetAddress().String()
expectedPubKey := sdk.MustBech32ifyAccPub(expectedInfo.GetPubKey())
// recover key
doRecoverKey(t, port, name2, pw, seed)
doRecoverKey(t, port, myName2, pw, mnemonic, 0, 0)
keys := getKeys(t, port)
require.Equal(t, expectedAddress, keys[0].Address)
require.Equal(t, expectedPubKey, keys[0].PubKey)
}
func TestKeyRecoverHDPath(t *testing.T) {
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{})
defer cleanup()
mnemonic := getKeysSeed(t, port)
for account := uint32(0); account < 50; account += 13 {
for index := uint32(0); index < 50; index += 15 {
name1Idx := fmt.Sprintf("name1_%d_%d", account, index)
name2Idx := fmt.Sprintf("name2_%d_%d", account, index)
expectedInfo, _ := GetKeyBase(t).CreateAccount(name1Idx, mnemonic, "", pw, account, index)
expectedAddress := expectedInfo.GetAddress().String()
expectedPubKey := sdk.MustBech32ifyAccPub(expectedInfo.GetPubKey())
// recover key
doRecoverKey(t, port, name2Idx, pw, mnemonic, account, index)
keysName2Idx := getKey(t, port, name2Idx)
require.Equal(t, expectedAddress, keysName2Idx.Address)
require.Equal(t, expectedPubKey, keysName2Idx.PubKey)
}
}
}
func TestKeys(t *testing.T) {
addr1, _ := CreateAddr(t, name1, pw, GetKeyBase(t))
addr1Bech32 := addr1.String()
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr1})
defer cleanup()
// get new seed & recover key
mnemonic2 := getKeysSeed(t, port)
doRecoverKey(t, port, name2, pw, mnemonic2, 0, 0)
// add key
resp := doKeysPost(t, port, name3, pw, seed)
mnemonic3 := mnemonic2
resp := doKeysPost(t, port, name3, pw, mnemonic3, 0, 0)
addrBech32 := addr.String()
addr2Bech32 := resp.Address
_, err := sdk.AccAddressFromBech32(addr2Bech32)
addr3Bech32 := resp.Address
_, err := sdk.AccAddressFromBech32(addr3Bech32)
require.NoError(t, err, "Failed to return a correct bech32 address")
// test if created account is the correct account
expectedInfo, _ := GetKeyBase(t).CreateKey(name3, seed, pw)
expectedAccount := sdk.AccAddress(expectedInfo.GetPubKey().Address().Bytes())
require.Equal(t, expectedAccount.String(), addr2Bech32)
expectedInfo3, _ := GetKeyBase(t).CreateAccount(name3, mnemonic3, "", pw, 0, 0)
expectedAddress3 := sdk.AccAddress(expectedInfo3.GetPubKey().Address()).String()
require.Equal(t, expectedAddress3, addr3Bech32)
// existing keys
keys := getKeys(t, port)
require.Equal(t, name1, keys[0].Name, "Did not serve keys name correctly")
require.Equal(t, addrBech32, keys[0].Address, "Did not serve keys Address correctly")
require.Equal(t, name2, keys[1].Name, "Did not serve keys name correctly")
require.Equal(t, addr2Bech32, keys[1].Address, "Did not serve keys Address correctly")
require.Equal(t, name1, getKey(t, port, name1).Name, "Did not serve keys name correctly")
require.Equal(t, addr1Bech32, getKey(t, port, name1).Address, "Did not serve keys Address correctly")
require.Equal(t, name2, getKey(t, port, name2).Name, "Did not serve keys name correctly")
require.Equal(t, addr3Bech32, getKey(t, port, name2).Address, "Did not serve keys Address correctly")
require.Equal(t, name3, getKey(t, port, name3).Name, "Did not serve keys name correctly")
require.Equal(t, addr3Bech32, getKey(t, port, name3).Address, "Did not serve keys Address correctly")
// select key
key := getKey(t, port, name3)
require.Equal(t, name3, key.Name, "Did not serve keys name correctly")
require.Equal(t, addr2Bech32, key.Address, "Did not serve keys Address correctly")
require.Equal(t, addr3Bech32, key.Address, "Did not serve keys Address correctly")
// update key
updateKey(t, port, name3, pw, altPw, false)

View File

@ -15,15 +15,6 @@ import (
"strings"
"testing"
"github.com/tendermint/tendermint/crypto/secp256k1"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/slashing"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/rest"
@ -32,12 +23,23 @@ import (
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/codec"
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authRest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
bankRest "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
"github.com/cosmos/cosmos-sdk/x/gov"
govRest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingRest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingRest "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
@ -46,6 +48,7 @@ import (
tmcfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/tendermint/tendermint/libs/cli"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
@ -53,18 +56,12 @@ import (
"github.com/tendermint/tendermint/p2p"
pvm "github.com/tendermint/tendermint/privval"
"github.com/tendermint/tendermint/proxy"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
tmtypes "github.com/tendermint/tendermint/types"
txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
authRest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
bankRest "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
distrRest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
govRest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
slashingRest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
stakingRest "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
)
// makePathname creates a unique pathname for each test. It will panic if it
@ -145,14 +142,6 @@ func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.Acc
return sdk.AccAddress(info.GetPubKey().Address()), seed
}
// Type that combines an Address with the pnemonic of the private key to that address
type AddrSeed struct {
Address sdk.AccAddress
Seed string
Name string
Password string
}
// CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address.
// It also requires that the keys could be created.
func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names, passwords []string) {
@ -169,7 +158,7 @@ func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.Acc
password := "1234567890"
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
require.NoError(t, err)
addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password})
addrSeeds = append(addrSeeds, rest.AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password})
}
sort.Sort(addrSeeds)
@ -184,14 +173,14 @@ func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.Acc
return addrs, seeds, names, passwords
}
// implement `Interface` in sort package.
type AddrSeedSlice []AddrSeed
// AddrSeedSlice implements `Interface` in sort package.
type AddrSeedSlice []rest.AddrSeed
func (b AddrSeedSlice) Len() int {
return len(b)
}
// Sorts lexographically by Address
// Less sorts lexicographically by Address
func (b AddrSeedSlice) Less(i, j int) bool {
// bytes package already implements Comparable for []byte.
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
@ -547,10 +536,11 @@ func getKeys(t *testing.T, port string) []keys.KeyOutput {
}
// POST /keys Create a new account locally
func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput {
pk := postKeys{name, password, seed}
func doKeysPost(t *testing.T, port, name, password, mnemonic string, account int, index int) keys.KeyOutput {
pk := keys.AddNewKey{name, password, mnemonic, account, index}
req, err := cdc.MarshalJSON(pk)
require.NoError(t, err)
res, body := Request(t, port, "POST", "/keys", req)
require.Equal(t, http.StatusOK, res.StatusCode, body)
@ -560,12 +550,6 @@ func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput
return resp
}
type postKeys struct {
Name string `json:"name"`
Password string `json:"password"`
Seed string `json:"seed"`
}
// GET /keys/seed Create a new seed to create a new account defaultValidFor
func getKeysSeed(t *testing.T, port string) string {
res, body := Request(t, port, "GET", "/keys/seed", nil)
@ -577,14 +561,17 @@ func getKeysSeed(t *testing.T, port string) string {
return body
}
// POST /keys/{name}/recover Recover a account from a seed
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, seed string) {
jsonStr := []byte(fmt.Sprintf(`{"password":"%s", "seed":"%s"}`, recoverPassword, seed))
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), jsonStr)
// POST /keys/{name}/recove Recover a account from a seed
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, mnemonic string, account uint32, index uint32) {
pk := keys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)}
req, err := cdc.MarshalJSON(pk)
require.NoError(t, err)
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), req)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var resp keys.KeyOutput
err := codec.Cdc.UnmarshalJSON([]byte(body), &resp)
err = codec.Cdc.UnmarshalJSON([]byte(body), &resp)
require.Nil(t, err, body)
addr1Bech32 := resp.Address
@ -604,7 +591,7 @@ func getKey(t *testing.T, port, name string) keys.KeyOutput {
// PUT /keys/{name} Update the password for this account in the KMS
func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) {
kr := updateKeyReq{oldPassword, newPassword}
kr := keys.UpdateKeyReq{oldPassword, newPassword}
req, err := cdc.MarshalJSON(kr)
require.NoError(t, err)
keyEndpoint := fmt.Sprintf("/keys/%s", name)
@ -616,14 +603,9 @@ func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail b
require.Equal(t, http.StatusOK, res.StatusCode, body)
}
type updateKeyReq struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
// DELETE /keys/{name} Remove an account
func deleteKey(t *testing.T, port, name, password string) {
dk := deleteKeyReq{password}
dk := keys.DeleteKeyReq{password}
req, err := cdc.MarshalJSON(dk)
require.NoError(t, err)
keyEndpoint := fmt.Sprintf("/keys/%s", name)
@ -631,10 +613,6 @@ func deleteKey(t *testing.T, port, name, password string) {
require.Equal(t, http.StatusOK, res.StatusCode, body)
}
type deleteKeyReq struct {
Password string `json:"password"`
}
// GET /auth/accounts/{address} Get the account information on blockchain
func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr.String()), nil)
@ -668,7 +646,7 @@ func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence
// POST /tx/broadcast Send a signed Tx
func doBroadcast(t *testing.T, port string, msg auth.StdTx) sdk.TxResponse {
tx := broadcastReq{Tx: msg, Return: "block"}
tx := rest.BroadcastReq{Tx: msg, Return: "block"}
req, err := cdc.MarshalJSON(tx)
require.Nil(t, err)
res, body := Request(t, port, "POST", "/tx/broadcast", req)
@ -678,11 +656,6 @@ func doBroadcast(t *testing.T, port string, msg auth.StdTx) sdk.TxResponse {
return resultTx
}
type broadcastReq struct {
Tx auth.StdTx `json:"tx"`
Return string `json:"return"`
}
// GET /bank/balances/{address} Get the account balances
// POST /bank/accounts/{address}/transfers Send coins (build -> sign -> send)
@ -726,7 +699,7 @@ func doTransferWithGas(
generateOnly, simulate,
)
sr := sendReq{
sr := rest.SendReq{
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)},
BaseReq: baseReq,
}
@ -759,7 +732,7 @@ func doTransferWithGasAccAuto(
fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, generateOnly, simulate,
)
sr := sendReq{
sr := rest.SendReq{
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)},
BaseReq: baseReq,
}
@ -859,7 +832,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string,
chainID := viper.GetString(client.FlagChainID)
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
msg := msgBeginRedelegateInput{
msg := rest.MsgBeginRedelegateInput{
BaseReq: baseReq,
DelegatorAddr: delAddr,
ValidatorSrcAddr: valSrcAddr,
@ -1090,7 +1063,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
chainID := viper.GetString(client.FlagChainID)
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
pr := postProposalReq{
pr := rest.PostProposalReq{
Title: "Test",
Description: "test",
ProposalType: "Text",
@ -1186,7 +1159,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
chainID := viper.GetString(client.FlagChainID)
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
dr := depositReq{
dr := rest.DepositReq{
Depositor: proposerAddr,
Amount: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))},
BaseReq: baseReq,
@ -1240,7 +1213,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
chainID := viper.GetString(client.FlagChainID)
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
vr := voteReq{
vr := rest.VoteReq{
Voter: proposerAddr,
Option: option,
BaseReq: baseReq,
@ -1372,7 +1345,7 @@ func doUnjail(t *testing.T, port, seed, name, password string,
chainID := viper.GetString(client.FlagChainID)
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false)
ur := unjailReq{
ur := rest.UnjailReq{
BaseReq: baseReq,
}
req, err := cdc.MarshalJSON(ur)

View File

@ -8,6 +8,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
)
// GasEstimateResponse defines a response definition for tx gas estimation.
@ -126,3 +127,61 @@ func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req i
return true
}
// AddrSeed combines an Address with the mnemonic of the private key to that address
type AddrSeed struct {
Address sdk.AccAddress
Seed string
Name string
Password string
}
// SendReq requests sending an amount of coins
type SendReq struct {
Amount sdk.Coins `json:"amount"`
BaseReq BaseReq `json:"base_req"`
}
// MsgBeginRedelegateInput request to begin a redelegation
type MsgBeginRedelegateInput struct {
BaseReq BaseReq `json:"base_req"`
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32
ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32
ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32
SharesAmount sdk.Dec `json:"shares"`
}
// PostProposalReq requests a proposals
type PostProposalReq struct {
BaseReq BaseReq `json:"base_req"`
Title string `json:"title"` // Title of the proposal
Description string `json:"description"` // Description of the proposal
ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer
InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit
}
// BroadcastReq requests broadcasting a transaction
type BroadcastReq struct {
Tx auth.StdTx `json:"tx"`
Return string `json:"return"`
}
// DepositReq requests a deposit of an amount of coins
type DepositReq struct {
BaseReq BaseReq `json:"base_req"`
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
}
// VoteReq requests sending a vote
type VoteReq struct {
BaseReq BaseReq `json:"base_req"`
Voter sdk.AccAddress `json:"voter"` // address of the voter
Option string `json:"option"` // option from OptionSet chosen by the voter
}
// UnjailReq request unjailing
type UnjailReq struct {
BaseReq BaseReq `json:"base_req"`
}

View File

@ -48,7 +48,24 @@ func TestGaiaCLIKeysAddRecover(t *testing.T) {
f := InitFixtures(t)
f.KeysAddRecover("test-recover", "dentist task convince chimney quality leave banana trade firm crawl eternal easily")
require.Equal(t, f.KeyAddress("test-recover").String(), "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4")
require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recover").String())
}
func TestGaiaCLIKeysAddRecoverHDPath(t *testing.T) {
t.Parallel()
f := InitFixtures(t)
f.KeysAddRecoverHDPath("test-recoverHD1", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 0, 0)
require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recoverHD1").String())
f.KeysAddRecoverHDPath("test-recoverH2", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 1, 5)
require.Equal(t, "cosmos1pdfav2cjhry9k79nu6r8kgknnjtq6a7rykmafy", f.KeyAddress("test-recoverH2").String())
f.KeysAddRecoverHDPath("test-recoverH3", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 1, 17)
require.Equal(t, "cosmos1909k354n6wl8ujzu6kmh49w4d02ax7qvlkv4sn", f.KeyAddress("test-recoverH3").String())
f.KeysAddRecoverHDPath("test-recoverH4", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 2, 17)
require.Equal(t, "cosmos1v9plmhvyhgxk3th9ydacm7j4z357s3nhtwsjat", f.KeyAddress("test-recoverH4").String())
}
func TestGaiaCLIMinimumFees(t *testing.T) {

View File

@ -225,12 +225,18 @@ func (f *Fixtures) KeysAdd(name string, flags ...string) {
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
}
// KeysAdd is gaiacli keys add --recover
// KeysAddRecover prepares gaiacli keys add --recover
func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) {
cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s", f.GCLIHome, name)
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic)
}
// KeysAddRecoverHDPath prepares gaiacli keys add --recover --account --index
func (f *Fixtures) KeysAddRecoverHDPath(name, mnemonic string, account uint32, index uint32, flags ...string) {
cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s --account %d --index %d", f.GCLIHome, name, account, index)
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic)
}
// KeysShow is gaiacli keys show
func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput {
cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name)

View File

@ -14,6 +14,7 @@ package hd
import (
"crypto/hmac"
"crypto/sha512"
"encoding/binary"
"errors"
"fmt"
@ -62,19 +63,7 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl))
}
if spl[0] != "44'" {
return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0])
}
if !isHardened(spl[1]) || !isHardened(spl[2]) {
return nil,
fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2])
}
if isHardened(spl[3]) || isHardened(spl[4]) {
return nil,
fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4])
}
// Check items can be parsed
purpose, err := hardenedInt(spl[0])
if err != nil {
return nil, err
@ -91,15 +80,30 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
if err != nil {
return nil, err
}
if !(change == 0 || change == 1) {
return nil, fmt.Errorf("change field can only be 0 or 1")
}
addressIdx, err := hardenedInt(spl[4])
if err != nil {
return nil, err
}
// Confirm valid values
if spl[0] != "44'" {
return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0])
}
if !isHardened(spl[1]) || !isHardened(spl[2]) {
return nil,
fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2])
}
if isHardened(spl[3]) || isHardened(spl[4]) {
return nil,
fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4])
}
if !(change == 0 || change == 1) {
return nil, fmt.Errorf("change field can only be 0 or 1")
}
return &BIP44Params{
purpose: purpose,
coinType: coinType,
@ -132,7 +136,7 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
return NewParams(44, 118, account, false, addressIdx)
}
// Return the BIP44 fields as an array.
// DerivationPath returns the BIP44 fields as an array.
func (p BIP44Params) DerivationPath() []uint32 {
change := uint32(0)
if p.change {
@ -251,8 +255,10 @@ func i64(key []byte, data []byte) (IL [32]byte, IR [32]byte) {
mac := hmac.New(sha512.New, key)
// sha512 does not err
_, _ = mac.Write(data)
I := mac.Sum(nil)
copy(IL[:], I[:32])
copy(IR[:], I[32:])
return
}

View File

@ -5,9 +5,9 @@ import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/cosmos/go-bip39"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var defaultBIP39Passphrase = ""
@ -21,7 +21,27 @@ func mnemonicToSeed(mnemonic string) []byte {
func ExampleStringifyPathParams() {
path := NewParams(44, 0, 0, false, 0)
fmt.Println(path.String())
// Output: 44'/0'/0'/0/0
path = NewParams(44, 33, 7, true, 9)
fmt.Println(path.String())
// Output:
// 44'/0'/0'/0/0
// 44'/33'/7'/1/9
}
func TestStringifyFundraiserPathParams(t *testing.T) {
path := NewFundraiserParams(4, 22)
require.Equal(t, "44'/118'/4'/0/22", path.String())
path = NewFundraiserParams(4, 57)
require.Equal(t, "44'/118'/4'/0/57", path.String())
}
func TestPathToArray(t *testing.T) {
path := 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)
require.Equal(t, "[44 118 2 1 15]", fmt.Sprintf("%v", path.DerivationPath()))
}
func TestParamsFromPath(t *testing.T) {
@ -60,6 +80,11 @@ func TestParamsFromPath(t *testing.T) {
{"44'/0'/0'/0/0'"}, // fifth field must not have '
{"44'/-1'/0'/0/0"}, // no negatives
{"44'/0'/0'/-1/0"}, // no negatives
{"a'/0'/0'/-1/0"}, // valid values
{"0/X/0'/-1/0"}, // valid values
{"44'/0'/X/-1/0"}, // valid values
{"44'/0'/0'/%/0"}, // valid values
{"44'/0'/0'/0/%"}, // valid values
}
for i, c := range badCases {
@ -80,14 +105,39 @@ func ExampleSomeBIP32TestVecs() {
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
fmt.Println()
// cosmos
priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
priv, err := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
// bitcoin
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
priv, err = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
// ether
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
priv, err = 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")
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
priv, err = DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0")
if err != nil {
fmt.Println("INVALID")
} else {
fmt.Println(hex.EncodeToString(priv[:]))
}
fmt.Println()
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
@ -121,6 +171,8 @@ func ExampleSomeBIP32TestVecs() {
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc
// INVALID
// INVALID
//
// keys generated via https://coinomi.com/recovery-phrase-tool.html
//

View File

@ -8,19 +8,18 @@ import (
"github.com/pkg/errors"
"github.com/cosmos/go-bip39"
"github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/go-bip39"
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/tendermint/tendermint/crypto/secp256k1"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
)
var _ Keybase = dbKeybase{}
@ -30,6 +29,7 @@ var _ Keybase = dbKeybase{}
// Find a list of all supported languages in the BIP 39 spec (word lists).
type Language int
//noinspection ALL
const (
// English is the default language to create a mnemonic.
// It is the only supported language by this package.
@ -54,7 +54,7 @@ const (
const (
// used for deriving seed from mnemonic
defaultBIP39Passphrase = ""
DefaultBIP39Passphrase = ""
// bits of entropy to draw when creating a mnemonic
defaultEntropySize = 256
@ -109,41 +109,15 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string
return
}
seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
seed := bip39.NewSeed(mnemonic, DefaultBIP39Passphrase)
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
return
}
// TEMPORARY METHOD UNTIL WE FIGURE OUT USER FACING HD DERIVATION API
func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err error) {
words := strings.Split(mnemonic, " ")
if len(words) != 12 && len(words) != 24 {
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
return
}
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
if err != nil {
return
}
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
return
}
// CreateFundraiserKey converts a mnemonic to a private key and persists it,
// encrypted with the given password.
// TODO(ismail)
func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) {
words := strings.Split(mnemonic, " ")
if len(words) != 12 {
err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words))
return
}
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
if err != nil {
return
}
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
return
// CreateAccount converts a mnemonic to a private key and persists it, encrypted with the given password.
func (kb dbKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
hdPath := hd.NewFundraiserParams(account, index)
return kb.Derive(name, mnemonic, bip39Passwd, encryptPasswd, *hdPath)
}
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
@ -151,23 +125,26 @@ func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string
if err != nil {
return
}
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
return
}
// CreateLedger creates a new locally-stored reference to a Ledger keypair
// It returns the created key info and an error if the Ledger could not be queried
func (kb dbKeybase) CreateLedger(name string, path crypto.DerivationPath, algo SigningAlgo) (Info, error) {
func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (Info, error) {
if algo != Secp256k1 {
return nil, ErrUnsupportedSigningAlgo
}
priv, err := crypto.NewPrivKeyLedgerSecp256k1(path)
hdPath := hd.NewFundraiserParams(account, index)
priv, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath)
if err != nil {
return nil, err
}
pub := priv.PubKey()
return kb.writeLedgerKey(pub, path, name), nil
return kb.writeLedgerKey(pub, *hdPath, name), nil
}
// CreateOffline creates a new reference to an offline keypair
@ -432,7 +409,7 @@ func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string
return info
}
func (kb dbKeybase) writeLedgerKey(pub tmcrypto.PubKey, path crypto.DerivationPath, name string) Info {
func (kb dbKeybase) writeLedgerKey(pub tmcrypto.PubKey, path hd.BIP44Params, name string) Info {
info := newLedgerInfo(name, pub, path)
kb.writeInfo(info, name)
return info

View File

@ -344,7 +344,7 @@ func TestSeedPhrase(t *testing.T) {
// let us re-create it from the mnemonic-phrase
params := *hd.NewFundraiserParams(0, 0)
newInfo, err := cstore.Derive(n2, mnemonic, defaultBIP39Passphrase, p2, params)
newInfo, err := cstore.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params)
require.NoError(t, err)
require.Equal(t, n2, newInfo.GetName())
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())

View File

@ -3,8 +3,6 @@ package keys
import (
"github.com/tendermint/tendermint/crypto"
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/types"
)
@ -23,20 +21,20 @@ type Keybase interface {
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
// key from that.
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error)
// CreateKey takes a mnemonic and derives, a password. This method is temporary
CreateKey(name, mnemonic, passwd string) (info Info, err error)
// CreateFundraiserKey takes a mnemonic and derives, a password
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
// Compute a BIP39 seed from th mnemonic and bip39Passwd.
// CreateAccount creates an account based using the BIP44 path (44'/118'/{account}'/0/{index}
CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error)
// Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
// Derive private key from the seed using the BIP44 params.
// Encrypt the key to disk using encryptPasswd.
// See https://github.com/cosmos/cosmos-sdk/issues/2095
Derive(name, mnemonic, bip39Passwd,
encryptPasswd string, params hd.BIP44Params) (Info, error)
// Create, store, and return a new Ledger key reference
CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error)
Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error)
// Create, store, and return a new offline key reference
// CreateLedger creates, stores, and returns a new Ledger key reference
CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (info Info, err error)
// CreateOffline creates, stores, and returns a new offline key reference
CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error)
// The following operations will *only* work on locally-stored keys
@ -46,10 +44,10 @@ type Keybase interface {
Export(name string) (armor string, err error)
ExportPubKey(name string) (armor string, err error)
// *only* works on locally-stored keys. Temporary method until we redo the exporting API
// ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
// Close closes the database.
// CloseDB closes the database.
CloseDB()
}
@ -125,10 +123,10 @@ func (i localInfo) GetAddress() types.AccAddress {
type ledgerInfo struct {
Name string `json:"name"`
PubKey crypto.PubKey `json:"pubkey"`
Path ccrypto.DerivationPath `json:"path"`
Path hd.BIP44Params `json:"path"`
}
func newLedgerInfo(name string, pub crypto.PubKey, path ccrypto.DerivationPath) Info {
func newLedgerInfo(name string, pub crypto.PubKey, path hd.BIP44Params) Info {
return &ledgerInfo{
Name: name,
PubKey: pub,

View File

@ -6,6 +6,7 @@ import (
"github.com/pkg/errors"
secp256k1 "github.com/btcsuite/btcd/btcec"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
tmcrypto "github.com/tendermint/tendermint/crypto"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
)
@ -22,9 +23,6 @@ type (
// dependencies when Ledger support is potentially not enabled.
discoverLedgerFn func() (LedgerSECP256K1, error)
// DerivationPath represents a Ledger derivation path.
DerivationPath []uint32
// LedgerSECP256K1 reflects an interface a Ledger API must implement for
// the SECP256K1 scheme.
LedgerSECP256K1 interface {
@ -39,7 +37,7 @@ type (
// go-amino so we can view the address later, even without having the
// ledger attached.
CachedPubKey tmcrypto.PubKey
Path DerivationPath
Path hd.BIP44Params
ledger LedgerSECP256K1
}
)
@ -49,7 +47,7 @@ type (
//
// CONTRACT: The ledger device, ledgerDevice, must be loaded and set prior to
// any creation of a PrivKeyLedgerSecp256k1.
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (tmcrypto.PrivKey, error) {
func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params) (tmcrypto.PrivKey, error) {
if discoverLedger == nil {
return nil, errors.New("no Ledger discovery function defined")
}
@ -138,11 +136,11 @@ func (pkl PrivKeyLedgerSecp256k1) getPubKey() (key tmcrypto.PubKey, err error) {
}
func (pkl PrivKeyLedgerSecp256k1) signLedgerSecp256k1(msg []byte) ([]byte, error) {
return pkl.ledger.SignSECP256K1(pkl.Path, msg)
return pkl.ledger.SignSECP256K1(pkl.Path.DerivationPath(), msg)
}
func (pkl PrivKeyLedgerSecp256k1) pubkeyLedgerSecp256k1() (pub tmcrypto.PubKey, err error) {
key, err := pkl.ledger.GetPublicKeySECP256K1(pkl.Path)
key, err := pkl.ledger.GetPublicKeySECP256K1(pkl.Path.DerivationPath())
if err != nil {
return nil, fmt.Errorf("error fetching public key: %v", err)
}

View File

@ -5,6 +5,8 @@ import (
"os"
"testing"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/encoding/amino"
)
@ -16,9 +18,9 @@ func TestRealLedgerSecp256k1(t *testing.T) {
t.Skip(fmt.Sprintf("Set '%s' to run code on a real ledger", ledgerEnabledEnv))
}
msg := []byte("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}")
path := DerivationPath{44, 60, 0, 0, 0}
path := hd.NewParams(44, 60, 0, false, 0)
priv, err := NewPrivKeyLedgerSecp256k1(path)
priv, err := NewPrivKeyLedgerSecp256k1(*path)
require.Nil(t, err, "%s", err)
pub := priv.PubKey()
@ -58,7 +60,7 @@ func TestRealLedgerErrorHandling(t *testing.T) {
// first, try to generate a key, must return an error
// (no panic)
path := DerivationPath{44, 60, 0, 0, 0}
_, err := NewPrivKeyLedgerSecp256k1(path)
path := hd.NewParams(44, 60, 0, false, 0)
_, err := NewPrivKeyLedgerSecp256k1(*path)
require.Error(t, err)
}