Merge PR #3461: GaiaCLI - refactor/fix --account and --index
This commit is contained in:
parent
5e35354269
commit
f5ada58780
|
@ -42,12 +42,6 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
||||||
return pass, nil
|
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
|
// GetCheckPassword will prompt for a password twice to verify they
|
||||||
// match (for creating a new password).
|
// match (for creating a new password).
|
||||||
// It enforces the password length. Only parses password once if
|
// It enforces the password length. Only parses password once if
|
||||||
|
|
|
@ -9,26 +9,24 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"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/cosmos/go-bip39"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/tendermint/tendermint/crypto"
|
|
||||||
"github.com/tendermint/tendermint/libs/cli"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
|
"github.com/tendermint/tendermint/crypto/multisig"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
flagInteractive = "interactive"
|
flagInteractive = "interactive"
|
||||||
flagBIP44Path = "bip44-path"
|
|
||||||
flagRecover = "recover"
|
flagRecover = "recover"
|
||||||
flagNoBackup = "no-backup"
|
flagNoBackup = "no-backup"
|
||||||
flagDryRun = "dry-run"
|
flagDryRun = "dry-run"
|
||||||
|
@ -38,6 +36,11 @@ const (
|
||||||
flagNoSort = "nosort"
|
flagNoSort = "nosort"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxValidAccountValue = int(0x80000000 - 1)
|
||||||
|
maxValidIndexalue = int(0x80000000 - 1)
|
||||||
|
)
|
||||||
|
|
||||||
func addKeyCommand() *cobra.Command {
|
func addKeyCommand() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "add <name>",
|
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().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().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().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(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(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
||||||
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
|
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
|
||||||
|
@ -95,24 +97,25 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
interactive := viper.GetBool(flagInteractive)
|
interactive := viper.GetBool(flagInteractive)
|
||||||
|
showMnemonic := viper.GetBool(flagNoBackup)
|
||||||
|
|
||||||
if viper.GetBool(flagDryRun) {
|
if viper.GetBool(flagDryRun) {
|
||||||
// we throw this away, so don't enforce args,
|
// we throw this away, so don't enforce args,
|
||||||
// we want to get a new random seed phrase quickly
|
// we want to get a new random seed phrase quickly
|
||||||
kb = client.MockKeyBase()
|
kb = client.MockKeyBase()
|
||||||
encryptPassword = "throwing-this-key-away"
|
encryptPassword = app.DefaultKeyPass
|
||||||
} else {
|
} else {
|
||||||
kb, err = GetKeyBaseWithWritePerm()
|
kb, err = GetKeyBaseWithWritePerm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := kb.Get(name)
|
_, err = kb.Get(name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// account exists, ask for user confirmation
|
// account exists, ask for user confirmation
|
||||||
if response, err := client.GetConfirmation(
|
if response, err2 := client.GetConfirmation(
|
||||||
fmt.Sprintf("override the existing name %s", name), buf); err != nil || !response {
|
fmt.Sprintf("override the existing name %s", name), buf); err2 != nil || !response {
|
||||||
return err
|
return err2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +147,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
if _, err := kb.CreateOffline(name, pk); err != nil {
|
if _, err := kb.CreateOffline(name, pk); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name)
|
fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -164,51 +168,37 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
kb.CreateOffline(name, pk)
|
_, err = kb.CreateOffline(name, pk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bipFlag := cmd.Flags().Lookup(flagBIP44Path)
|
account := uint32(viper.GetInt(flagAccount))
|
||||||
bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || !interactive)
|
index := uint32(viper.GetInt(flagIndex))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're using ledger, only thing we need is the path. So generate key and
|
// If we're using ledger, only thing we need is the path. So generate key and we're done.
|
||||||
// we're done.
|
|
||||||
if viper.GetBool(client.FlagUseLedger) {
|
if viper.GetBool(client.FlagUseLedger) {
|
||||||
account := uint32(viper.GetInt(flagAccount))
|
info, err := kb.CreateLedger(name, keys.Secp256k1, account, index)
|
||||||
index := uint32(viper.GetInt(flagIndex))
|
|
||||||
path := ccrypto.DerivationPath{44, 118, account, 0, index}
|
|
||||||
info, err := kb.CreateLedger(name, path, keys.Secp256k1)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
printCreate(info, "")
|
return printCreate(info, false, "")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get bip39 mnemonic
|
||||||
var mnemonic string
|
var mnemonic string
|
||||||
if interactive {
|
var bip39Passphrase string
|
||||||
mnemonic, err = client.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", buf)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -227,8 +217,12 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get bip39 passphrase
|
if !bip39.IsMnemonicValid(mnemonic) {
|
||||||
var bip39Passphrase string
|
fmt.Fprintf(os.Stderr, "Error: Mnemonic is not valid")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// override bip39 passphrase
|
||||||
if interactive {
|
if interactive {
|
||||||
bip39Passphrase, err = client.GetString(
|
bip39Passphrase, err = client.GetString(
|
||||||
"Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed. "+
|
"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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printCreate(info, mnemonic)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) {
|
// Recover key from seed passphrase
|
||||||
buf := client.BufferStdin()
|
if viper.GetBool(flagRecover) {
|
||||||
bip44Path := path
|
// Hide mnemonic from output
|
||||||
|
showMnemonic = false
|
||||||
// if it wasn't set in the flag, give it a chance to overide interactively
|
mnemonic = ""
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bip44Path) == 0 {
|
|
||||||
bip44Path = path
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bip44params, err := hd.NewParamsFromPath(bip44Path)
|
return printCreate(info, showMnemonic, mnemonic)
|
||||||
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)
|
output := viper.Get(cli.OutputFlag)
|
||||||
|
|
||||||
switch output {
|
switch output {
|
||||||
case "text":
|
case "text":
|
||||||
fmt.Fprintln(os.Stderr, "")
|
fmt.Fprintln(os.Stderr)
|
||||||
printKeyInfo(info, Bech32KeyOutput)
|
printKeyInfo(info, Bech32KeyOutput)
|
||||||
|
|
||||||
// print seed unless requested not to.
|
// print mnemonic unless requested not to.
|
||||||
if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) {
|
if showMnemonic {
|
||||||
fmt.Fprintln(os.Stderr, "\n**Important** write this seed phrase in a safe place.")
|
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, "It is the only way to recover your account if you ever forget your password.")
|
||||||
fmt.Fprintln(os.Stderr)
|
fmt.Fprintln(os.Stderr, "")
|
||||||
fmt.Fprintln(os.Stderr, seed)
|
fmt.Fprintln(os.Stderr, mnemonic)
|
||||||
}
|
}
|
||||||
case "json":
|
case "json":
|
||||||
out, err := Bech32KeyOutput(info)
|
out, err := Bech32KeyOutput(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
if !viper.GetBool(flagNoBackup) {
|
|
||||||
out.Seed = seed
|
if showMnemonic {
|
||||||
|
out.Mnemonic = mnemonic
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonString []byte
|
var jsonString []byte
|
||||||
if viper.GetBool(client.FlagIndentResponse) {
|
if viper.GetBool(client.FlagIndentResponse) {
|
||||||
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
|
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
|
||||||
} else {
|
} else {
|
||||||
jsonString, err = cdc.MarshalJSON(out)
|
jsonString, err = cdc.MarshalJSON(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // really shouldn't happen...
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, string(jsonString))
|
fmt.Fprintln(os.Stderr, string(jsonString))
|
||||||
default:
|
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
|
return nil
|
||||||
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("-------------------------------------")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// REST
|
// REST
|
||||||
|
|
||||||
// new key request REST body
|
// function to just create a new seed to display in the UI before actually persisting it in the keybase
|
||||||
type NewKeyBody struct {
|
func generateMnemonic(algo keys.SigningAlgo) string {
|
||||||
Name string `json:"name"`
|
kb := client.MockKeyBase()
|
||||||
Password string `json:"password"`
|
pass := app.DefaultKeyPass
|
||||||
Seed string `json:"seed"`
|
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
|
// add new key REST handler
|
||||||
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var kb keys.Keybase
|
var kb keys.Keybase
|
||||||
var m NewKeyBody
|
var m AddNewKey
|
||||||
|
|
||||||
kb, err := GetKeyBaseWithWritePerm()
|
kb, err := GetKeyBaseWithWritePerm()
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(body, &m)
|
err = json.Unmarshal(body, &m)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check parameters
|
||||||
if m.Name == "" {
|
if m.Name == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
|
||||||
err = errMissingName()
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if m.Password == "" {
|
if m.Password == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
|
||||||
err = errMissingPassword()
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if already exists
|
mnemonic := m.Mnemonic
|
||||||
infos, err := kb.List()
|
// if mnemonic is empty, generate one
|
||||||
for _, info := range infos {
|
if mnemonic == "" {
|
||||||
if info.GetName() == m.Name {
|
mnemonic = generateMnemonic(keys.Secp256k1)
|
||||||
w.WriteHeader(http.StatusConflict)
|
}
|
||||||
err = errKeyNameConflict(m.Name)
|
if !bip39.IsMnemonicValid(mnemonic) {
|
||||||
w.Write([]byte(err.Error()))
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
|
||||||
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(m.Name)
|
||||||
|
if err == nil {
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(m.Name))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// create account
|
// create account
|
||||||
seed := m.Seed
|
account := uint32(m.Account)
|
||||||
if seed == "" {
|
index := uint32(m.Index)
|
||||||
seed = getSeed(keys.Secp256k1)
|
info, err := kb.CreateAccount(m.Name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index)
|
||||||
}
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
info, err := kb.CreateKey(m.Name, seed, m.Password)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
keyOutput, err := Bech32KeyOutput(info)
|
keyOutput, err := Bech32KeyOutput(info)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
keyOutput.Seed = seed
|
keyOutput.Mnemonic = mnemonic
|
||||||
|
|
||||||
PostProcessResponse(w, cdc, keyOutput, indent)
|
PostProcessResponse(w, cdc, keyOutput, indent)
|
||||||
}
|
}
|
||||||
|
@ -426,22 +405,17 @@ func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
|
||||||
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
|
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
algoType := vars["type"]
|
algoType := vars["type"]
|
||||||
|
|
||||||
// algo type defaults to secp256k1
|
// algo type defaults to secp256k1
|
||||||
if algoType == "" {
|
if algoType == "" {
|
||||||
algoType = "secp256k1"
|
algoType = "secp256k1"
|
||||||
}
|
}
|
||||||
algo := keys.SigningAlgo(algoType)
|
|
||||||
|
|
||||||
seed := getSeed(algo)
|
algo := keys.SigningAlgo(algoType)
|
||||||
|
seed := generateMnemonic(algo)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write([]byte(seed))
|
_, _ = w.Write([]byte(seed))
|
||||||
}
|
|
||||||
|
|
||||||
// RecoverKeyBody is recover key request REST body
|
|
||||||
type RecoverKeyBody struct {
|
|
||||||
Password string `json:"password"`
|
|
||||||
Seed string `json:"seed"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecoverRequestHandler performs key recover request
|
// RecoverRequestHandler performs key recover request
|
||||||
|
@ -449,67 +423,66 @@ func RecoverRequestHandler(indent bool) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
var m RecoverKeyBody
|
var m RecoverKey
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||||
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()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == "" {
|
err = cdc.UnmarshalJSON(body, &m)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
|
||||||
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()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
kb, err := GetKeyBaseWithWritePerm()
|
kb, err := GetKeyBaseWithWritePerm()
|
||||||
if err != nil {
|
CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
if name == "" {
|
||||||
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// check if already exists
|
if m.Password == "" {
|
||||||
infos, err := kb.List()
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword())
|
||||||
for _, info := range infos {
|
return
|
||||||
if info.GetName() == name {
|
|
||||||
w.WriteHeader(http.StatusConflict)
|
|
||||||
err = errKeyNameConflict(name)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := kb.CreateKey(name, m.Seed, m.Password)
|
mnemonic := m.Mnemonic
|
||||||
if err != nil {
|
if !bip39.IsMnemonicValid(mnemonic) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic())
|
||||||
w.Write([]byte(err.Error()))
|
}
|
||||||
|
|
||||||
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
keyOutput, err := Bech32KeyOutput(info)
|
keyOutput, err := Bech32KeyOutput(info)
|
||||||
if err != nil {
|
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
keyerror "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package keys
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func errKeyNameConflict(name string) error {
|
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 {
|
func errMissingName() error {
|
||||||
|
@ -14,6 +14,18 @@ func errMissingPassword() error {
|
||||||
return fmt.Errorf("you have to specify a password for the locally stored account")
|
return fmt.Errorf("you have to specify a password for the locally stored account")
|
||||||
}
|
}
|
||||||
|
|
||||||
func errMissingSeed() error {
|
func errMissingMnemonic() error {
|
||||||
return fmt.Errorf("you have to specify seed for key recover")
|
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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,9 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
|
|
||||||
bip39 "github.com/bartekn/go-bip39"
|
bip39 "github.com/bartekn/go-bip39"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -58,7 +56,6 @@ func runMnemonicCmd(cmd *cobra.Command, args []string) error {
|
||||||
// hash input entropy to get entropy seed
|
// hash input entropy to get entropy seed
|
||||||
hashedEntropy := sha256.Sum256([]byte(inputEntropy))
|
hashedEntropy := sha256.Sum256([]byte(inputEntropy))
|
||||||
entropySeed = hashedEntropy[:]
|
entropySeed = hashedEntropy[:]
|
||||||
printStep()
|
|
||||||
} else {
|
} else {
|
||||||
// read entropy seed straight from crypto.Rand
|
// read entropy seed straight from crypto.Rand
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -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"`
|
||||||
|
}
|
|
@ -118,15 +118,6 @@ func SetKeyBase(kb keys.Keybase) {
|
||||||
keybase = kb
|
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
|
// create a list of KeyOutput in bech32 format
|
||||||
func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) {
|
func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) {
|
||||||
kos := make([]KeyOutput, len(infos))
|
kos := make([]KeyOutput, len(infos))
|
||||||
|
|
|
@ -12,10 +12,9 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"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/client/rest"
|
||||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||||
"github.com/cosmos/cosmos-sdk/tests"
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
@ -47,41 +46,100 @@ func init() {
|
||||||
version.Version = os.Getenv("VERSION")
|
version.Version = os.Getenv("VERSION")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeys(t *testing.T) {
|
func TestSeedsAreDifferent(t *testing.T) {
|
||||||
addr, _ := CreateAddr(t, name1, pw, GetKeyBase(t))
|
addr, _ := CreateAddr(t, name1, pw, GetKeyBase(t))
|
||||||
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})
|
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
// get new seed
|
mnemonic1 := getKeysSeed(t, port)
|
||||||
seed := 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
|
// 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
|
// add key
|
||||||
resp := doKeysPost(t, port, name3, pw, seed)
|
mnemonic3 := mnemonic2
|
||||||
|
resp := doKeysPost(t, port, name3, pw, mnemonic3, 0, 0)
|
||||||
|
|
||||||
addrBech32 := addr.String()
|
addr3Bech32 := resp.Address
|
||||||
addr2Bech32 := resp.Address
|
_, err := sdk.AccAddressFromBech32(addr3Bech32)
|
||||||
_, err := sdk.AccAddressFromBech32(addr2Bech32)
|
|
||||||
require.NoError(t, err, "Failed to return a correct bech32 address")
|
require.NoError(t, err, "Failed to return a correct bech32 address")
|
||||||
|
|
||||||
// test if created account is the correct account
|
// test if created account is the correct account
|
||||||
expectedInfo, _ := GetKeyBase(t).CreateKey(name3, seed, pw)
|
expectedInfo3, _ := GetKeyBase(t).CreateAccount(name3, mnemonic3, "", pw, 0, 0)
|
||||||
expectedAccount := sdk.AccAddress(expectedInfo.GetPubKey().Address().Bytes())
|
expectedAddress3 := sdk.AccAddress(expectedInfo3.GetPubKey().Address()).String()
|
||||||
require.Equal(t, expectedAccount.String(), addr2Bech32)
|
require.Equal(t, expectedAddress3, addr3Bech32)
|
||||||
|
|
||||||
// existing keys
|
// existing keys
|
||||||
keys := getKeys(t, port)
|
require.Equal(t, name1, getKey(t, port, name1).Name, "Did not serve keys name correctly")
|
||||||
require.Equal(t, name1, keys[0].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, addrBech32, keys[0].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, name2, keys[1].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, addr2Bech32, keys[1].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
|
// select key
|
||||||
key := getKey(t, port, name3)
|
key := getKey(t, port, name3)
|
||||||
require.Equal(t, name3, key.Name, "Did not serve keys name correctly")
|
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
|
// update key
|
||||||
updateKey(t, port, name3, pw, altPw, false)
|
updateKey(t, port, name3, pw, altPw, false)
|
||||||
|
|
|
@ -15,15 +15,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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"
|
||||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
|
@ -32,12 +23,23 @@ import (
|
||||||
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
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/server"
|
||||||
"github.com/cosmos/cosmos-sdk/tests"
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"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"
|
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"
|
"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/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -46,6 +48,7 @@ import (
|
||||||
tmcfg "github.com/tendermint/tendermint/config"
|
tmcfg "github.com/tendermint/tendermint/config"
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
"github.com/tendermint/tendermint/libs/cli"
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
@ -53,18 +56,12 @@ import (
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
pvm "github.com/tendermint/tendermint/privval"
|
pvm "github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tendermint/proxy"
|
"github.com/tendermint/tendermint/proxy"
|
||||||
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
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"
|
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
||||||
distrRest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
|
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
|
// 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
|
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.
|
// 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.
|
// 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) {
|
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"
|
password := "1234567890"
|
||||||
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
|
info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1)
|
||||||
require.NoError(t, err)
|
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)
|
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
|
return addrs, seeds, names, passwords
|
||||||
}
|
}
|
||||||
|
|
||||||
// implement `Interface` in sort package.
|
// AddrSeedSlice implements `Interface` in sort package.
|
||||||
type AddrSeedSlice []AddrSeed
|
type AddrSeedSlice []rest.AddrSeed
|
||||||
|
|
||||||
func (b AddrSeedSlice) Len() int {
|
func (b AddrSeedSlice) Len() int {
|
||||||
return len(b)
|
return len(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorts lexographically by Address
|
// Less sorts lexicographically by Address
|
||||||
func (b AddrSeedSlice) Less(i, j int) bool {
|
func (b AddrSeedSlice) Less(i, j int) bool {
|
||||||
// bytes package already implements Comparable for []byte.
|
// bytes package already implements Comparable for []byte.
|
||||||
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
|
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
|
// POST /keys Create a new account locally
|
||||||
func doKeysPost(t *testing.T, port, name, password, seed string) keys.KeyOutput {
|
func doKeysPost(t *testing.T, port, name, password, mnemonic string, account int, index int) keys.KeyOutput {
|
||||||
pk := postKeys{name, password, seed}
|
pk := keys.AddNewKey{name, password, mnemonic, account, index}
|
||||||
req, err := cdc.MarshalJSON(pk)
|
req, err := cdc.MarshalJSON(pk)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
res, body := Request(t, port, "POST", "/keys", req)
|
res, body := Request(t, port, "POST", "/keys", req)
|
||||||
|
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
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
|
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
|
// GET /keys/seed Create a new seed to create a new account defaultValidFor
|
||||||
func getKeysSeed(t *testing.T, port string) string {
|
func getKeysSeed(t *testing.T, port string) string {
|
||||||
res, body := Request(t, port, "GET", "/keys/seed", nil)
|
res, body := Request(t, port, "GET", "/keys/seed", nil)
|
||||||
|
@ -577,14 +561,17 @@ func getKeysSeed(t *testing.T, port string) string {
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /keys/{name}/recover Recover a account from a seed
|
// POST /keys/{name}/recove Recover a account from a seed
|
||||||
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, seed string) {
|
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, mnemonic string, account uint32, index uint32) {
|
||||||
jsonStr := []byte(fmt.Sprintf(`{"password":"%s", "seed":"%s"}`, recoverPassword, seed))
|
pk := keys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)}
|
||||||
res, body := Request(t, port, "POST", fmt.Sprintf("/keys/%s/recover", recoverName), jsonStr)
|
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)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
var resp keys.KeyOutput
|
var resp keys.KeyOutput
|
||||||
err := codec.Cdc.UnmarshalJSON([]byte(body), &resp)
|
err = codec.Cdc.UnmarshalJSON([]byte(body), &resp)
|
||||||
require.Nil(t, err, body)
|
require.Nil(t, err, body)
|
||||||
|
|
||||||
addr1Bech32 := resp.Address
|
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
|
// PUT /keys/{name} Update the password for this account in the KMS
|
||||||
func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) {
|
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)
|
req, err := cdc.MarshalJSON(kr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
keyEndpoint := fmt.Sprintf("/keys/%s", name)
|
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)
|
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
|
// DELETE /keys/{name} Remove an account
|
||||||
func deleteKey(t *testing.T, port, name, password string) {
|
func deleteKey(t *testing.T, port, name, password string) {
|
||||||
dk := deleteKeyReq{password}
|
dk := keys.DeleteKeyReq{password}
|
||||||
req, err := cdc.MarshalJSON(dk)
|
req, err := cdc.MarshalJSON(dk)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
keyEndpoint := fmt.Sprintf("/keys/%s", name)
|
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)
|
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
|
// GET /auth/accounts/{address} Get the account information on blockchain
|
||||||
func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
|
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)
|
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
|
// POST /tx/broadcast Send a signed Tx
|
||||||
func doBroadcast(t *testing.T, port string, msg auth.StdTx) sdk.TxResponse {
|
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)
|
req, err := cdc.MarshalJSON(tx)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
res, body := Request(t, port, "POST", "/tx/broadcast", req)
|
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
|
return resultTx
|
||||||
}
|
}
|
||||||
|
|
||||||
type broadcastReq struct {
|
|
||||||
Tx auth.StdTx `json:"tx"`
|
|
||||||
Return string `json:"return"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /bank/balances/{address} Get the account balances
|
// GET /bank/balances/{address} Get the account balances
|
||||||
|
|
||||||
// POST /bank/accounts/{address}/transfers Send coins (build -> sign -> send)
|
// POST /bank/accounts/{address}/transfers Send coins (build -> sign -> send)
|
||||||
|
@ -726,7 +699,7 @@ func doTransferWithGas(
|
||||||
generateOnly, simulate,
|
generateOnly, simulate,
|
||||||
)
|
)
|
||||||
|
|
||||||
sr := sendReq{
|
sr := rest.SendReq{
|
||||||
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)},
|
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)},
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
}
|
}
|
||||||
|
@ -759,7 +732,7 @@ func doTransferWithGasAccAuto(
|
||||||
fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, generateOnly, simulate,
|
fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, generateOnly, simulate,
|
||||||
)
|
)
|
||||||
|
|
||||||
sr := sendReq{
|
sr := rest.SendReq{
|
||||||
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)},
|
Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)},
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
}
|
}
|
||||||
|
@ -859,7 +832,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string,
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
msg := msgBeginRedelegateInput{
|
msg := rest.MsgBeginRedelegateInput{
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
DelegatorAddr: delAddr,
|
DelegatorAddr: delAddr,
|
||||||
ValidatorSrcAddr: valSrcAddr,
|
ValidatorSrcAddr: valSrcAddr,
|
||||||
|
@ -1090,7 +1063,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
pr := postProposalReq{
|
pr := rest.PostProposalReq{
|
||||||
Title: "Test",
|
Title: "Test",
|
||||||
Description: "test",
|
Description: "test",
|
||||||
ProposalType: "Text",
|
ProposalType: "Text",
|
||||||
|
@ -1186,7 +1159,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
dr := depositReq{
|
dr := rest.DepositReq{
|
||||||
Depositor: proposerAddr,
|
Depositor: proposerAddr,
|
||||||
Amount: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))},
|
Amount: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))},
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
|
@ -1240,7 +1213,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||||
|
|
||||||
vr := voteReq{
|
vr := rest.VoteReq{
|
||||||
Voter: proposerAddr,
|
Voter: proposerAddr,
|
||||||
Option: option,
|
Option: option,
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
|
@ -1372,7 +1345,7 @@ func doUnjail(t *testing.T, port, seed, name, password string,
|
||||||
chainID := viper.GetString(client.FlagChainID)
|
chainID := viper.GetString(client.FlagChainID)
|
||||||
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false)
|
baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false)
|
||||||
|
|
||||||
ur := unjailReq{
|
ur := rest.UnjailReq{
|
||||||
BaseReq: baseReq,
|
BaseReq: baseReq,
|
||||||
}
|
}
|
||||||
req, err := cdc.MarshalJSON(ur)
|
req, err := cdc.MarshalJSON(ur)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GasEstimateResponse defines a response definition for tx gas estimation.
|
// 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
|
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"`
|
||||||
|
}
|
||||||
|
|
|
@ -48,7 +48,24 @@ func TestGaiaCLIKeysAddRecover(t *testing.T) {
|
||||||
f := InitFixtures(t)
|
f := InitFixtures(t)
|
||||||
|
|
||||||
f.KeysAddRecover("test-recover", "dentist task convince chimney quality leave banana trade firm crawl eternal easily")
|
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) {
|
func TestGaiaCLIMinimumFees(t *testing.T) {
|
||||||
|
|
|
@ -225,12 +225,18 @@ func (f *Fixtures) KeysAdd(name string, flags ...string) {
|
||||||
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
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) {
|
func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) {
|
||||||
cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s", f.GCLIHome, name)
|
cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s", f.GCLIHome, name)
|
||||||
executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic)
|
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
|
// KeysShow is gaiacli keys show
|
||||||
func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput {
|
func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput {
|
||||||
cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name)
|
cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name)
|
||||||
|
|
|
@ -14,6 +14,7 @@ package hd
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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))
|
return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl))
|
||||||
}
|
}
|
||||||
|
|
||||||
if spl[0] != "44'" {
|
// Check items can be parsed
|
||||||
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])
|
|
||||||
}
|
|
||||||
|
|
||||||
purpose, err := hardenedInt(spl[0])
|
purpose, err := hardenedInt(spl[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -91,15 +80,30 @@ func NewParamsFromPath(path string) (*BIP44Params, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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])
|
addressIdx, err := hardenedInt(spl[4])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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{
|
return &BIP44Params{
|
||||||
purpose: purpose,
|
purpose: purpose,
|
||||||
coinType: coinType,
|
coinType: coinType,
|
||||||
|
@ -132,7 +136,7 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
|
||||||
return NewParams(44, 118, account, false, addressIdx)
|
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 {
|
func (p BIP44Params) DerivationPath() []uint32 {
|
||||||
change := uint32(0)
|
change := uint32(0)
|
||||||
if p.change {
|
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)
|
mac := hmac.New(sha512.New, key)
|
||||||
// sha512 does not err
|
// sha512 does not err
|
||||||
_, _ = mac.Write(data)
|
_, _ = mac.Write(data)
|
||||||
|
|
||||||
I := mac.Sum(nil)
|
I := mac.Sum(nil)
|
||||||
copy(IL[:], I[:32])
|
copy(IL[:], I[:32])
|
||||||
copy(IR[:], I[32:])
|
copy(IR[:], I[32:])
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/cosmos/go-bip39"
|
"github.com/cosmos/go-bip39"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultBIP39Passphrase = ""
|
var defaultBIP39Passphrase = ""
|
||||||
|
@ -21,7 +21,27 @@ func mnemonicToSeed(mnemonic string) []byte {
|
||||||
func ExampleStringifyPathParams() {
|
func ExampleStringifyPathParams() {
|
||||||
path := NewParams(44, 0, 0, false, 0)
|
path := NewParams(44, 0, 0, false, 0)
|
||||||
fmt.Println(path.String())
|
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) {
|
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'/0'/0'/0/0'"}, // fifth field must not have '
|
||||||
{"44'/-1'/0'/0/0"}, // no negatives
|
{"44'/-1'/0'/0/0"}, // no negatives
|
||||||
{"44'/0'/0'/-1/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 {
|
for i, c := range badCases {
|
||||||
|
@ -80,14 +105,39 @@ func ExampleSomeBIP32TestVecs() {
|
||||||
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
// cosmos
|
// cosmos
|
||||||
priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
|
priv, err := DerivePrivateKeyForPath(master, ch, FullFundraiserPath)
|
||||||
fmt.Println(hex.EncodeToString(priv[:]))
|
if err != nil {
|
||||||
|
fmt.Println("INVALID")
|
||||||
|
} else {
|
||||||
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
}
|
||||||
// bitcoin
|
// bitcoin
|
||||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
|
priv, err = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0")
|
||||||
fmt.Println(hex.EncodeToString(priv[:]))
|
if err != nil {
|
||||||
|
fmt.Println("INVALID")
|
||||||
|
} else {
|
||||||
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
}
|
||||||
// ether
|
// ether
|
||||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
|
priv, err = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0")
|
||||||
fmt.Println(hex.EncodeToString(priv[:]))
|
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()
|
||||||
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
||||||
|
@ -121,6 +171,8 @@ func ExampleSomeBIP32TestVecs() {
|
||||||
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c
|
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c
|
||||||
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d
|
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d
|
||||||
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc
|
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc
|
||||||
|
// INVALID
|
||||||
|
// INVALID
|
||||||
//
|
//
|
||||||
// keys generated via https://coinomi.com/recovery-phrase-tool.html
|
// keys generated via https://coinomi.com/recovery-phrase-tool.html
|
||||||
//
|
//
|
||||||
|
|
|
@ -8,19 +8,18 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/cosmos/go-bip39"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto"
|
"github.com/cosmos/cosmos-sdk/crypto"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
"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/crypto/keys/mintkey"
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/cosmos/go-bip39"
|
||||||
|
|
||||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Keybase = dbKeybase{}
|
var _ Keybase = dbKeybase{}
|
||||||
|
@ -30,6 +29,7 @@ var _ Keybase = dbKeybase{}
|
||||||
// Find a list of all supported languages in the BIP 39 spec (word lists).
|
// Find a list of all supported languages in the BIP 39 spec (word lists).
|
||||||
type Language int
|
type Language int
|
||||||
|
|
||||||
|
//noinspection ALL
|
||||||
const (
|
const (
|
||||||
// English is the default language to create a mnemonic.
|
// English is the default language to create a mnemonic.
|
||||||
// It is the only supported language by this package.
|
// It is the only supported language by this package.
|
||||||
|
@ -54,7 +54,7 @@ const (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// used for deriving seed from mnemonic
|
// used for deriving seed from mnemonic
|
||||||
defaultBIP39Passphrase = ""
|
DefaultBIP39Passphrase = ""
|
||||||
|
|
||||||
// bits of entropy to draw when creating a mnemonic
|
// bits of entropy to draw when creating a mnemonic
|
||||||
defaultEntropySize = 256
|
defaultEntropySize = 256
|
||||||
|
@ -109,41 +109,15 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
|
seed := bip39.NewSeed(mnemonic, DefaultBIP39Passphrase)
|
||||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEMPORARY METHOD UNTIL WE FIGURE OUT USER FACING HD DERIVATION API
|
// CreateAccount converts a mnemonic to a private key and persists it, encrypted with the given password.
|
||||||
func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err error) {
|
func (kb dbKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
|
||||||
words := strings.Split(mnemonic, " ")
|
hdPath := hd.NewFundraiserParams(account, index)
|
||||||
if len(words) != 12 && len(words) != 24 {
|
return kb.Derive(name, mnemonic, bip39Passwd, encryptPasswd, *hdPath)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
|
||||||
|
|
||||||
|
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLedger creates a new locally-stored reference to a Ledger keypair
|
// 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
|
// 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 {
|
if algo != Secp256k1 {
|
||||||
return nil, ErrUnsupportedSigningAlgo
|
return nil, ErrUnsupportedSigningAlgo
|
||||||
}
|
}
|
||||||
priv, err := crypto.NewPrivKeyLedgerSecp256k1(path)
|
|
||||||
|
hdPath := hd.NewFundraiserParams(account, index)
|
||||||
|
priv, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pub := priv.PubKey()
|
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
|
// 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
|
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)
|
info := newLedgerInfo(name, pub, path)
|
||||||
kb.writeInfo(info, name)
|
kb.writeInfo(info, name)
|
||||||
return info
|
return info
|
||||||
|
|
|
@ -344,7 +344,7 @@ func TestSeedPhrase(t *testing.T) {
|
||||||
|
|
||||||
// let us re-create it from the mnemonic-phrase
|
// let us re-create it from the mnemonic-phrase
|
||||||
params := *hd.NewFundraiserParams(0, 0)
|
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.NoError(t, err)
|
||||||
require.Equal(t, n2, newInfo.GetName())
|
require.Equal(t, n2, newInfo.GetName())
|
||||||
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
||||||
|
|
|
@ -3,8 +3,6 @@ package keys
|
||||||
import (
|
import (
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"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/crypto/keys/hd"
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
@ -23,20 +21,20 @@ type Keybase interface {
|
||||||
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
|
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
|
||||||
// key from that.
|
// key from that.
|
||||||
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error)
|
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)
|
// CreateAccount creates an account based using the BIP44 path (44'/118'/{account}'/0/{index}
|
||||||
// CreateFundraiserKey takes a mnemonic and derives, a password
|
CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error)
|
||||||
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
|
|
||||||
// Compute a BIP39 seed from th mnemonic and bip39Passwd.
|
// Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
|
||||||
// Derive private key from the seed using the BIP44 params.
|
// Derive private key from the seed using the BIP44 params.
|
||||||
// Encrypt the key to disk using encryptPasswd.
|
// Encrypt the key to disk using encryptPasswd.
|
||||||
// See https://github.com/cosmos/cosmos-sdk/issues/2095
|
// See https://github.com/cosmos/cosmos-sdk/issues/2095
|
||||||
Derive(name, mnemonic, bip39Passwd,
|
Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error)
|
||||||
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)
|
|
||||||
|
|
||||||
// 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)
|
CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error)
|
||||||
|
|
||||||
// The following operations will *only* work on locally-stored keys
|
// The following operations will *only* work on locally-stored keys
|
||||||
|
@ -46,10 +44,10 @@ type Keybase interface {
|
||||||
Export(name string) (armor string, err error)
|
Export(name string) (armor string, err error)
|
||||||
ExportPubKey(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)
|
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
|
||||||
|
|
||||||
// Close closes the database.
|
// CloseDB closes the database.
|
||||||
CloseDB()
|
CloseDB()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,12 +121,12 @@ func (i localInfo) GetAddress() types.AccAddress {
|
||||||
|
|
||||||
// ledgerInfo is the public information about a Ledger key
|
// ledgerInfo is the public information about a Ledger key
|
||||||
type ledgerInfo struct {
|
type ledgerInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
PubKey crypto.PubKey `json:"pubkey"`
|
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{
|
return &ledgerInfo{
|
||||||
Name: name,
|
Name: name,
|
||||||
PubKey: pub,
|
PubKey: pub,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
secp256k1 "github.com/btcsuite/btcd/btcec"
|
secp256k1 "github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
)
|
)
|
||||||
|
@ -22,9 +23,6 @@ type (
|
||||||
// dependencies when Ledger support is potentially not enabled.
|
// dependencies when Ledger support is potentially not enabled.
|
||||||
discoverLedgerFn func() (LedgerSECP256K1, error)
|
discoverLedgerFn func() (LedgerSECP256K1, error)
|
||||||
|
|
||||||
// DerivationPath represents a Ledger derivation path.
|
|
||||||
DerivationPath []uint32
|
|
||||||
|
|
||||||
// LedgerSECP256K1 reflects an interface a Ledger API must implement for
|
// LedgerSECP256K1 reflects an interface a Ledger API must implement for
|
||||||
// the SECP256K1 scheme.
|
// the SECP256K1 scheme.
|
||||||
LedgerSECP256K1 interface {
|
LedgerSECP256K1 interface {
|
||||||
|
@ -39,7 +37,7 @@ type (
|
||||||
// go-amino so we can view the address later, even without having the
|
// go-amino so we can view the address later, even without having the
|
||||||
// ledger attached.
|
// ledger attached.
|
||||||
CachedPubKey tmcrypto.PubKey
|
CachedPubKey tmcrypto.PubKey
|
||||||
Path DerivationPath
|
Path hd.BIP44Params
|
||||||
ledger LedgerSECP256K1
|
ledger LedgerSECP256K1
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -49,7 +47,7 @@ type (
|
||||||
//
|
//
|
||||||
// CONTRACT: The ledger device, ledgerDevice, must be loaded and set prior to
|
// CONTRACT: The ledger device, ledgerDevice, must be loaded and set prior to
|
||||||
// any creation of a PrivKeyLedgerSecp256k1.
|
// any creation of a PrivKeyLedgerSecp256k1.
|
||||||
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (tmcrypto.PrivKey, error) {
|
func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params) (tmcrypto.PrivKey, error) {
|
||||||
if discoverLedger == nil {
|
if discoverLedger == nil {
|
||||||
return nil, errors.New("no Ledger discovery function defined")
|
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) {
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error fetching public key: %v", err)
|
return nil, fmt.Errorf("error fetching public key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
"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))
|
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\"}")
|
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)
|
require.Nil(t, err, "%s", err)
|
||||||
|
|
||||||
pub := priv.PubKey()
|
pub := priv.PubKey()
|
||||||
|
@ -58,7 +60,7 @@ func TestRealLedgerErrorHandling(t *testing.T) {
|
||||||
|
|
||||||
// first, try to generate a key, must return an error
|
// first, try to generate a key, must return an error
|
||||||
// (no panic)
|
// (no panic)
|
||||||
path := DerivationPath{44, 60, 0, 0, 0}
|
path := hd.NewParams(44, 60, 0, false, 0)
|
||||||
_, err := NewPrivKeyLedgerSecp256k1(path)
|
_, err := NewPrivKeyLedgerSecp256k1(*path)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue