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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue