Merge branch 'develop' into cwgoes/check-supply-in-simulation
|
@ -48,12 +48,19 @@
|
||||||
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
|
digest = "1:e8a3550c8786316675ff54ad6f09d265d129c9d986919af7f541afba50d87ce2"
|
||||||
|
name = "github.com/cosmos/go-bip39"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "52158e4697b87de16ed390e1bdaf813e581008fa"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||||
name = "github.com/davecgh/go-spew"
|
name = "github.com/davecgh/go-spew"
|
||||||
packages = ["spew"]
|
packages = ["spew"]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
|
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
version = "v1.1.1"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b"
|
digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b"
|
||||||
|
@ -390,8 +397,7 @@
|
||||||
version = "v1.2.1"
|
version = "v1.2.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b"
|
||||||
digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5"
|
|
||||||
name = "github.com/syndtr/goleveldb"
|
name = "github.com/syndtr/goleveldb"
|
||||||
packages = [
|
packages = [
|
||||||
"leveldb",
|
"leveldb",
|
||||||
|
@ -408,7 +414,7 @@
|
||||||
"leveldb/util",
|
"leveldb/util",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd"
|
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f"
|
digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f"
|
||||||
|
@ -524,10 +530,10 @@
|
||||||
version = "v0.1.0"
|
version = "v0.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
digest = "1:aaff04fa01d9b824fde6799759cc597b3ac3671b9ad31924c28b6557d0ee5284"
|
||||||
digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a"
|
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
packages = [
|
packages = [
|
||||||
|
"bcrypt",
|
||||||
"blowfish",
|
"blowfish",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"curve25519",
|
"curve25519",
|
||||||
|
@ -544,7 +550,8 @@
|
||||||
"salsa20/salsa",
|
"salsa20/salsa",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "e3636079e1a4c1f337f212cc5cd2aca108f6c900"
|
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||||
|
source = "https://github.com/tendermint/crypto"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
|
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
|
||||||
|
@ -563,15 +570,14 @@
|
||||||
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
digest = "1:4bd75b1a219bc590b05c976bbebf47f4e993314ebb5c7cbf2efe05a09a184d54"
|
||||||
digest = "1:8bc8ecef1d63576cfab4d08b44a1f255dd67e5b019b7a44837d62380f266a91c"
|
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = [
|
packages = [
|
||||||
"cpu",
|
"cpu",
|
||||||
"unix",
|
"unix",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "e4b3c5e9061176387e7cea65e4dc5853801f3fb7"
|
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||||
|
@ -597,12 +603,11 @@
|
||||||
version = "v0.3.0"
|
version = "v0.3.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c"
|
||||||
digest = "1:1e6b0176e8c5dd8ff551af65c76f8b73a99bcf4d812cedff1b91711b7df4804c"
|
|
||||||
name = "google.golang.org/genproto"
|
name = "google.golang.org/genproto"
|
||||||
packages = ["googleapis/rpc/status"]
|
packages = ["googleapis/rpc/status"]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "c7e5094acea1ca1b899e2259d80a6b0f882f81f8"
|
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
|
digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
|
||||||
|
@ -653,6 +658,7 @@
|
||||||
"github.com/bartekn/go-bip39",
|
"github.com/bartekn/go-bip39",
|
||||||
"github.com/bgentry/speakeasy",
|
"github.com/bgentry/speakeasy",
|
||||||
"github.com/btcsuite/btcd/btcec",
|
"github.com/btcsuite/btcd/btcec",
|
||||||
|
"github.com/cosmos/go-bip39",
|
||||||
"github.com/golang/protobuf/proto",
|
"github.com/golang/protobuf/proto",
|
||||||
"github.com/gorilla/mux",
|
"github.com/gorilla/mux",
|
||||||
"github.com/mattn/go-isatty",
|
"github.com/mattn/go-isatty",
|
||||||
|
@ -699,7 +705,7 @@
|
||||||
"github.com/tendermint/tendermint/types",
|
"github.com/tendermint/tendermint/types",
|
||||||
"github.com/tendermint/tendermint/version",
|
"github.com/tendermint/tendermint/version",
|
||||||
"github.com/zondax/ledger-goclient",
|
"github.com/zondax/ledger-goclient",
|
||||||
"golang.org/x/crypto/blowfish",
|
"golang.org/x/crypto/bcrypt",
|
||||||
]
|
]
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
39
Gopkg.toml
|
@ -59,22 +59,51 @@
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
version = "=0.25.0"
|
version = "=0.25.0"
|
||||||
|
|
||||||
|
## deps without releases:
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/bartekn/go-bip39"
|
name = "golang.org/x/crypto"
|
||||||
revision = "a05967ea095d81c8fe4833776774cfaff8e5036c"
|
source = "https://github.com/tendermint/crypto"
|
||||||
|
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/cosmos/go-bip39"
|
||||||
|
revision = "52158e4697b87de16ed390e1bdaf813e581008fa"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/zondax/ledger-goclient"
|
name = "github.com/zondax/ledger-goclient"
|
||||||
version = "=v0.1.0"
|
version = "=v0.1.0"
|
||||||
|
|
||||||
|
## transitive deps, with releases:
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/davecgh/go-spew"
|
||||||
|
version = "=v1.1.0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/rakyll/statik"
|
name = "github.com/rakyll/statik"
|
||||||
version = "=v0.1.4"
|
version = "=v0.1.4"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/mitchellh/go-homedir"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
## transitive deps, without releases:
|
||||||
|
#
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/syndtr/goleveldb"
|
||||||
|
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "golang.org/x/sys"
|
||||||
|
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "google.golang.org/genproto"
|
||||||
|
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||||
|
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
unused-packages = true
|
unused-packages = true
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/mitchellh/go-homedir"
|
|
||||||
version = "1.0.0"
|
|
||||||
|
|
|
@ -142,6 +142,7 @@ IMPROVEMENTS
|
||||||
|
|
||||||
* Gaia REST API (`gaiacli advanced rest-server`)
|
* Gaia REST API (`gaiacli advanced rest-server`)
|
||||||
* [x/stake] [\#2000](https://github.com/cosmos/cosmos-sdk/issues/2000) Added tests for new staking endpoints
|
* [x/stake] [\#2000](https://github.com/cosmos/cosmos-sdk/issues/2000) Added tests for new staking endpoints
|
||||||
|
* [gaia-lite] Added example to Swagger specification for /keys/seed.
|
||||||
|
|
||||||
* Gaia CLI (`gaiacli`)
|
* Gaia CLI (`gaiacli`)
|
||||||
* [cli] #2060 removed `--select` from `block` command
|
* [cli] #2060 removed `--select` from `block` command
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bgentry/speakeasy"
|
"github.com/bgentry/speakeasy"
|
||||||
isatty "github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,13 +44,8 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
||||||
|
|
||||||
// GetSeed will request a seed phrase from stdin and trims off
|
// GetSeed will request a seed phrase from stdin and trims off
|
||||||
// leading/trailing spaces
|
// leading/trailing spaces
|
||||||
func GetSeed(prompt string, buf *bufio.Reader) (seed string, err error) {
|
func GetSeed(prompt string, buf *bufio.Reader) (string, error) {
|
||||||
if inputIsTty() {
|
return GetString(prompt, buf)
|
||||||
fmt.Println(prompt)
|
|
||||||
}
|
|
||||||
seed, err = readLineFromBuf(buf)
|
|
||||||
seed = strings.TrimSpace(seed)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCheckPassword will prompt for a password twice to verify they
|
// GetCheckPassword will prompt for a password twice to verify they
|
||||||
|
@ -133,5 +128,6 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) {
|
||||||
|
|
||||||
// PrintPrefixed prints a string with > prefixed for use in prompts.
|
// PrintPrefixed prints a string with > prefixed for use in prompts.
|
||||||
func PrintPrefixed(msg string) {
|
func PrintPrefixed(msg string) {
|
||||||
fmt.Printf("> %s\n", msg)
|
msg = fmt.Sprintf("> %s\n", msg)
|
||||||
|
fmt.Fprint(os.Stderr, msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
bip39 "github.com/bartekn/go-bip39"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagUserEntropy = "unsafe-entropy"
|
||||||
|
|
||||||
|
mnemonicEntropySize = 256
|
||||||
|
)
|
||||||
|
|
||||||
|
func mnemonicKeyCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "mnemonic",
|
||||||
|
Short: "Compute the bip39 mnemonic for some input entropy",
|
||||||
|
Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy",
|
||||||
|
RunE: runMnemonicCmd,
|
||||||
|
}
|
||||||
|
cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMnemonicCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
flags := cmd.Flags()
|
||||||
|
|
||||||
|
userEntropy, _ := flags.GetBool(flagUserEntropy)
|
||||||
|
|
||||||
|
var entropySeed []byte
|
||||||
|
|
||||||
|
if userEntropy {
|
||||||
|
// prompt the user to enter some entropy
|
||||||
|
buf := client.BufferStdin()
|
||||||
|
inputEntropy, err := client.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(inputEntropy) < 43 {
|
||||||
|
return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy))
|
||||||
|
}
|
||||||
|
conf, err := client.GetConfirmation(
|
||||||
|
fmt.Sprintf("> Input length: %d", len(inputEntropy)),
|
||||||
|
buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !conf {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
entropySeed, err = bip39.NewEntropy(mnemonicEntropySize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mnemonic, err := bip39.NewMnemonic(entropySeed[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(mnemonic)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/bartekn/go-bip39"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagNewDefault = "default"
|
||||||
|
flagBIP44Path = "bip44-path"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newKeyCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "new",
|
||||||
|
Short: "Interactive command to derive a new private key, encrypt it, and save to disk",
|
||||||
|
Long: `Derive a new private key using an interactive command that will prompt you for each input.
|
||||||
|
Optionally specify a bip39 mnemonic, a bip39 passphrase to further secure the mnemonic,
|
||||||
|
and a bip32 HD path to derive a specific account. The key will be stored under the given name
|
||||||
|
and encrypted with the given password. The only input that is required is the encryption password.`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: runNewCmd,
|
||||||
|
}
|
||||||
|
cmd.Flags().Bool(flagNewDefault, false, "Skip the prompts and just use the default values for everything")
|
||||||
|
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")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
input
|
||||||
|
- bip39 mnemonic
|
||||||
|
- bip39 passphrase
|
||||||
|
- bip44 path
|
||||||
|
- local encryption password
|
||||||
|
output
|
||||||
|
- armor encrypted private key (saved to file)
|
||||||
|
*/
|
||||||
|
// nolint: gocyclo
|
||||||
|
func runNewCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
name := args[0]
|
||||||
|
kb, err := GetKeyBase()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := client.BufferStdin()
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
useDefaults, _ := flags.GetBool(flagNewDefault)
|
||||||
|
bipFlag := flags.Lookup(flagBIP44Path)
|
||||||
|
|
||||||
|
bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || useDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're using ledger, only thing we need is the path.
|
||||||
|
// generate key and we're done.
|
||||||
|
if viper.GetBool(client.FlagUseLedger) {
|
||||||
|
|
||||||
|
algo := keys.Secp256k1 // SigningAlgo(viper.GetString(flagType))
|
||||||
|
path := bip44Params.DerivationPath() // ccrypto.DerivationPath{44, 118, account, 0, index}
|
||||||
|
info, err := kb.CreateLedger(name, path, algo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printCreate(info, "")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the mnemonic
|
||||||
|
var mnemonic string
|
||||||
|
if !useDefaults {
|
||||||
|
mnemonic, err = client.GetString("> Enter your bip39 mnemonic, or hit enter to generate one.", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mnemonic) == 0 {
|
||||||
|
// read entropy seed straight from crypto.Rand and convert to mnemonic
|
||||||
|
entropySeed, err := bip39.NewEntropy(mnemonicEntropySize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mnemonic, err = bip39.NewMnemonic(entropySeed[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get bip39 passphrase
|
||||||
|
var bip39Passphrase string
|
||||||
|
if !useDefaults {
|
||||||
|
printStep()
|
||||||
|
printPrefixed("Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed")
|
||||||
|
bip39Passphrase, err = client.GetString("> Most users should just hit enter to use the default, \"\"", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if they use one, make them re-enter it
|
||||||
|
if len(bip39Passphrase) != 0 {
|
||||||
|
p2, err := client.GetString("Repeat the passphrase:", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bip39Passphrase != p2 {
|
||||||
|
return errors.New("passphrases don't match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the encryption password
|
||||||
|
printStep()
|
||||||
|
encryptPassword, err := client.GetCheckPassword(
|
||||||
|
"> Enter a passphrase to encrypt your key to disk:",
|
||||||
|
"> Repeat the passphrase:", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = info
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) {
|
||||||
|
buf := bufio.NewReader(os.Stdin)
|
||||||
|
bip44Path := path
|
||||||
|
|
||||||
|
// if it wasnt set in the flag, give it a chance to overide interactively
|
||||||
|
if !flagSet {
|
||||||
|
printStep()
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bip44params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printPrefixed(msg string) {
|
||||||
|
fmt.Printf("> %s\n", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printStep() {
|
||||||
|
printPrefixed("-------------------------------------")
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ func Commands() *cobra.Command {
|
||||||
needs to sign with a private key.`,
|
needs to sign with a private key.`,
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
|
mnemonicKeyCommand(),
|
||||||
|
newKeyCommand(),
|
||||||
addKeyCommand(),
|
addKeyCommand(),
|
||||||
listKeysCmd,
|
listKeysCmd,
|
||||||
showKeysCmd(),
|
showKeysCmd(),
|
||||||
|
|
|
@ -10,20 +10,20 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
p2p "github.com/tendermint/tendermint/p2p"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
p2p "github.com/tendermint/tendermint/p2p"
|
||||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
|
||||||
client "github.com/cosmos/cosmos-sdk/client"
|
client "github.com/cosmos/cosmos-sdk/client"
|
||||||
keys "github.com/cosmos/cosmos-sdk/client/keys"
|
keys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||||
tests "github.com/cosmos/cosmos-sdk/tests"
|
tests "github.com/cosmos/cosmos-sdk/tests"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
version "github.com/cosmos/cosmos-sdk/version"
|
version "github.com/cosmos/cosmos-sdk/version"
|
||||||
|
@ -35,7 +35,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cryptoKeys.BcryptSecurityParameter = 1
|
mintkey.BcryptSecurityParameter = 1
|
||||||
version.Version = os.Getenv("VERSION")
|
version.Version = os.Getenv("VERSION")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,55 +717,53 @@ func TestUnjail(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProposalsQuery(t *testing.T) {
|
func TestProposalsQuery(t *testing.T) {
|
||||||
name, password1 := "test", "1234567890"
|
addrs, seeds, names, passwords := CreateAddrs(t, GetKeyBase(t), 2)
|
||||||
name2, password2 := "test2", "1234567890"
|
|
||||||
addr, seed := CreateAddr(t, "test", password1, GetKeyBase(t))
|
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addrs[0], addrs[1]})
|
||||||
addr2, seed2 := CreateAddr(t, "test2", password2, GetKeyBase(t))
|
|
||||||
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr, addr2})
|
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
// Addr1 proposes (and deposits) proposals #1 and #2
|
// Addr1 proposes (and deposits) proposals #1 and #2
|
||||||
resultTx := doSubmitProposal(t, port, seed, name, password1, addr, 5)
|
resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5)
|
||||||
var proposalID1 int64
|
var proposalID1 int64
|
||||||
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1)
|
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
resultTx = doSubmitProposal(t, port, seed, name, password1, addr, 5)
|
resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5)
|
||||||
var proposalID2 int64
|
var proposalID2 int64
|
||||||
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2)
|
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
|
|
||||||
// Addr2 proposes (and deposits) proposals #3
|
// Addr2 proposes (and deposits) proposals #3
|
||||||
resultTx = doSubmitProposal(t, port, seed2, name2, password2, addr2, 5)
|
resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], 5)
|
||||||
var proposalID3 int64
|
var proposalID3 int64
|
||||||
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3)
|
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
|
|
||||||
// Addr2 deposits on proposals #2 & #3
|
// Addr2 deposits on proposals #2 & #3
|
||||||
resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID2, 5)
|
resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID2, 5)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID3, 5)
|
resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, 5)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
|
|
||||||
// check deposits match proposal and individual deposits
|
// check deposits match proposal and individual deposits
|
||||||
deposits := getDeposits(t, port, proposalID1)
|
deposits := getDeposits(t, port, proposalID1)
|
||||||
require.Len(t, deposits, 1)
|
require.Len(t, deposits, 1)
|
||||||
deposit := getDeposit(t, port, proposalID1, addr)
|
deposit := getDeposit(t, port, proposalID1, addrs[0])
|
||||||
require.Equal(t, deposit, deposits[0])
|
require.Equal(t, deposit, deposits[0])
|
||||||
|
|
||||||
deposits = getDeposits(t, port, proposalID2)
|
deposits = getDeposits(t, port, proposalID2)
|
||||||
require.Len(t, deposits, 2)
|
require.Len(t, deposits, 2)
|
||||||
deposit = getDeposit(t, port, proposalID2, addr)
|
deposit = getDeposit(t, port, proposalID2, addrs[0])
|
||||||
require.Equal(t, deposit, deposits[0])
|
require.True(t, deposit.Equals(deposits[0]))
|
||||||
deposit = getDeposit(t, port, proposalID2, addr2)
|
deposit = getDeposit(t, port, proposalID2, addrs[1])
|
||||||
require.Equal(t, deposit, deposits[1])
|
require.True(t, deposit.Equals(deposits[1]))
|
||||||
|
|
||||||
deposits = getDeposits(t, port, proposalID3)
|
deposits = getDeposits(t, port, proposalID3)
|
||||||
require.Len(t, deposits, 1)
|
require.Len(t, deposits, 1)
|
||||||
deposit = getDeposit(t, port, proposalID3, addr2)
|
deposit = getDeposit(t, port, proposalID3, addrs[1])
|
||||||
require.Equal(t, deposit, deposits[0])
|
require.Equal(t, deposit, deposits[0])
|
||||||
|
|
||||||
// increasing the amount of the deposit should update the existing one
|
// increasing the amount of the deposit should update the existing one
|
||||||
resultTx = doDeposit(t, port, seed, name, password1, addr, proposalID1, 1)
|
resultTx = doDeposit(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID1, 1)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
|
|
||||||
deposits = getDeposits(t, port, proposalID1)
|
deposits = getDeposits(t, port, proposalID1)
|
||||||
|
@ -782,13 +780,13 @@ func TestProposalsQuery(t *testing.T) {
|
||||||
require.Equal(t, proposalID3, proposals[1].GetProposalID())
|
require.Equal(t, proposalID3, proposals[1].GetProposalID())
|
||||||
|
|
||||||
// Addr1 votes on proposals #2 & #3
|
// Addr1 votes on proposals #2 & #3
|
||||||
resultTx = doVote(t, port, seed, name, password1, addr, proposalID2)
|
resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID2)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
resultTx = doVote(t, port, seed, name, password1, addr, proposalID3)
|
resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID3)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
|
|
||||||
// Addr2 votes on proposal #3
|
// Addr2 votes on proposal #3
|
||||||
resultTx = doVote(t, port, seed2, name2, password2, addr2, proposalID3)
|
resultTx = doVote(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3)
|
||||||
tests.WaitForHeight(resultTx.Height+1, port)
|
tests.WaitForHeight(resultTx.Height+1, port)
|
||||||
|
|
||||||
// Test query all proposals
|
// Test query all proposals
|
||||||
|
@ -798,37 +796,37 @@ func TestProposalsQuery(t *testing.T) {
|
||||||
require.Equal(t, proposalID3, (proposals[2]).GetProposalID())
|
require.Equal(t, proposalID3, (proposals[2]).GetProposalID())
|
||||||
|
|
||||||
// Test query deposited by addr1
|
// Test query deposited by addr1
|
||||||
proposals = getProposalsFilterDepositer(t, port, addr)
|
proposals = getProposalsFilterDepositer(t, port, addrs[0])
|
||||||
require.Equal(t, proposalID1, (proposals[0]).GetProposalID())
|
require.Equal(t, proposalID1, (proposals[0]).GetProposalID())
|
||||||
|
|
||||||
// Test query deposited by addr2
|
// Test query deposited by addr2
|
||||||
proposals = getProposalsFilterDepositer(t, port, addr2)
|
proposals = getProposalsFilterDepositer(t, port, addrs[1])
|
||||||
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
|
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
|
||||||
require.Equal(t, proposalID3, (proposals[1]).GetProposalID())
|
require.Equal(t, proposalID3, (proposals[1]).GetProposalID())
|
||||||
|
|
||||||
// Test query voted by addr1
|
// Test query voted by addr1
|
||||||
proposals = getProposalsFilterVoter(t, port, addr)
|
proposals = getProposalsFilterVoter(t, port, addrs[0])
|
||||||
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
|
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
|
||||||
require.Equal(t, proposalID3, (proposals[1]).GetProposalID())
|
require.Equal(t, proposalID3, (proposals[1]).GetProposalID())
|
||||||
|
|
||||||
// Test query voted by addr2
|
// Test query voted by addr2
|
||||||
proposals = getProposalsFilterVoter(t, port, addr2)
|
proposals = getProposalsFilterVoter(t, port, addrs[1])
|
||||||
require.Equal(t, proposalID3, (proposals[0]).GetProposalID())
|
require.Equal(t, proposalID3, (proposals[0]).GetProposalID())
|
||||||
|
|
||||||
// Test query voted and deposited by addr1
|
// Test query voted and deposited by addr1
|
||||||
proposals = getProposalsFilterVoterDepositer(t, port, addr, addr)
|
proposals = getProposalsFilterVoterDepositer(t, port, addrs[0], addrs[0])
|
||||||
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
|
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
|
||||||
|
|
||||||
// Test query votes on Proposal 2
|
// Test query votes on Proposal 2
|
||||||
votes := getVotes(t, port, proposalID2)
|
votes := getVotes(t, port, proposalID2)
|
||||||
require.Len(t, votes, 1)
|
require.Len(t, votes, 1)
|
||||||
require.Equal(t, addr, votes[0].Voter)
|
require.Equal(t, addrs[0], votes[0].Voter)
|
||||||
|
|
||||||
// Test query votes on Proposal 3
|
// Test query votes on Proposal 3
|
||||||
votes = getVotes(t, port, proposalID3)
|
votes = getVotes(t, port, proposalID3)
|
||||||
require.Len(t, votes, 2)
|
require.Len(t, votes, 2)
|
||||||
require.True(t, addr.String() == votes[0].Voter.String() || addr.String() == votes[1].Voter.String())
|
require.True(t, addrs[0].String() == votes[0].Voter.String() || addrs[0].String() == votes[1].Voter.String())
|
||||||
require.True(t, addr2.String() == votes[0].Voter.String() || addr2.String() == votes[1].Voter.String())
|
require.True(t, addrs[1].String() == votes[0].Voter.String() || addrs[1].String() == votes[1].Voter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
//_____________________________________________________________________________
|
//_____________________________________________________________________________
|
||||||
|
|
|
@ -400,6 +400,7 @@ paths:
|
||||||
description: 16 word Seed
|
description: 16 word Seed
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
example: blossom pool issue kidney elevator blame furnace winter account merry vessel security depend exact travel bargain problem jelly rural net again mask roast chest
|
||||||
/keys/{name}/recover:
|
/keys/{name}/recover:
|
||||||
post:
|
post:
|
||||||
summary: Recover a account from a seed
|
summary: Recover a account from a seed
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -111,6 +112,69 @@ 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.
|
||||||
|
// 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) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
info crkeys.Info
|
||||||
|
seed string
|
||||||
|
)
|
||||||
|
|
||||||
|
addrSeeds := AddrSeedSlice{}
|
||||||
|
|
||||||
|
for i := 0; i < numAddrs; i++ {
|
||||||
|
name := fmt.Sprintf("test%d", i)
|
||||||
|
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})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(addrSeeds)
|
||||||
|
|
||||||
|
for i := range addrSeeds {
|
||||||
|
addrs = append(addrs, addrSeeds[i].Address)
|
||||||
|
seeds = append(seeds, addrSeeds[i].Seed)
|
||||||
|
names = append(names, addrSeeds[i].Name)
|
||||||
|
passwords = append(passwords, addrSeeds[i].Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs, seeds, names, passwords
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement `Interface` in sort package.
|
||||||
|
type AddrSeedSlice []AddrSeed
|
||||||
|
|
||||||
|
func (b AddrSeedSlice) Len() int {
|
||||||
|
return len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorts lexographically 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()) {
|
||||||
|
case -1:
|
||||||
|
return true
|
||||||
|
case 0, 1:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
panic("not fail-able with `bytes.Comparable` bounded [-1, 1].")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b AddrSeedSlice) Swap(i, j int) {
|
||||||
|
b[j], b[i] = b[i], b[j]
|
||||||
|
}
|
||||||
|
|
||||||
// InitializeTestLCD starts Tendermint and the LCD in process, listening on
|
// InitializeTestLCD starts Tendermint and the LCD in process, listening on
|
||||||
// their respective sockets where nValidators is the total number of validators
|
// their respective sockets where nValidators is the total number of validators
|
||||||
// and initAddrs are the accounts to initialize with some steak tokens. It
|
// and initAddrs are the accounts to initialize with some steak tokens. It
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bcrypt
|
|
||||||
|
|
||||||
import "encoding/base64"
|
|
||||||
|
|
||||||
const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
||||||
|
|
||||||
var bcEncoding = base64.NewEncoding(alphabet)
|
|
||||||
|
|
||||||
func base64Encode(src []byte) []byte {
|
|
||||||
n := bcEncoding.EncodedLen(len(src))
|
|
||||||
dst := make([]byte, n)
|
|
||||||
bcEncoding.Encode(dst, src)
|
|
||||||
for dst[n-1] == '=' {
|
|
||||||
n--
|
|
||||||
}
|
|
||||||
return dst[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
func base64Decode(src []byte) ([]byte, error) {
|
|
||||||
numOfEquals := 4 - (len(src) % 4)
|
|
||||||
for i := 0; i < numOfEquals; i++ {
|
|
||||||
src = append(src, '=')
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := make([]byte, bcEncoding.DecodedLen(len(src)))
|
|
||||||
n, err := bcEncoding.Decode(dst, src)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dst[:n], nil
|
|
||||||
}
|
|
|
@ -1,297 +0,0 @@
|
||||||
package bcrypt
|
|
||||||
|
|
||||||
// MODIFIED BY TENDERMINT TO EXPOSE NONCE
|
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
|
|
||||||
// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
|
|
||||||
|
|
||||||
// The code is a port of Provos and Mazières's C implementation.
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/blowfish"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// the minimum allowable cost as passed in to GenerateFromPassword
|
|
||||||
MinCost int = 4
|
|
||||||
// the maximum allowable cost as passed in to GenerateFromPassword
|
|
||||||
MaxCost int = 31
|
|
||||||
// the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
|
|
||||||
DefaultCost int = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// The error returned from CompareHashAndPassword when a password and hash do
|
|
||||||
// not match.
|
|
||||||
var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
|
|
||||||
|
|
||||||
// The error returned from CompareHashAndPassword when a hash is too short to
|
|
||||||
// be a bcrypt hash.
|
|
||||||
var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
|
|
||||||
|
|
||||||
// The error returned from CompareHashAndPassword when a hash was created with
|
|
||||||
// a bcrypt algorithm newer than this implementation.
|
|
||||||
type HashVersionTooNewError byte
|
|
||||||
|
|
||||||
func (hv HashVersionTooNewError) Error() string {
|
|
||||||
return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
|
|
||||||
type InvalidHashPrefixError byte
|
|
||||||
|
|
||||||
// Format error
|
|
||||||
func (ih InvalidHashPrefixError) Error() string {
|
|
||||||
return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid bcrypt cost
|
|
||||||
type InvalidCostError int
|
|
||||||
|
|
||||||
func (ic InvalidCostError) Error() string {
|
|
||||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) // nolint: unconvert
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
majorVersion = '2'
|
|
||||||
minorVersion = 'a'
|
|
||||||
maxSaltSize = 16
|
|
||||||
maxCryptedHashSize = 23
|
|
||||||
encodedSaltSize = 22
|
|
||||||
encodedHashSize = 31
|
|
||||||
minHashSize = 59
|
|
||||||
)
|
|
||||||
|
|
||||||
// magicCipherData is an IV for the 64 Blowfish encryption calls in
|
|
||||||
// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
|
|
||||||
var magicCipherData = []byte{
|
|
||||||
0x4f, 0x72, 0x70, 0x68,
|
|
||||||
0x65, 0x61, 0x6e, 0x42,
|
|
||||||
0x65, 0x68, 0x6f, 0x6c,
|
|
||||||
0x64, 0x65, 0x72, 0x53,
|
|
||||||
0x63, 0x72, 0x79, 0x44,
|
|
||||||
0x6f, 0x75, 0x62, 0x74,
|
|
||||||
}
|
|
||||||
|
|
||||||
type hashed struct {
|
|
||||||
hash []byte
|
|
||||||
salt []byte
|
|
||||||
cost int // allowed range is MinCost to MaxCost
|
|
||||||
major byte
|
|
||||||
minor byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateFromPassword returns the bcrypt hash of the password at the given
|
|
||||||
// cost. If the cost given is less than MinCost, the cost will be set to
|
|
||||||
// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
|
|
||||||
// to compare the returned hashed password with its cleartext version.
|
|
||||||
func GenerateFromPassword(salt []byte, password []byte, cost int) ([]byte, error) {
|
|
||||||
if len(salt) != maxSaltSize {
|
|
||||||
return nil, fmt.Errorf("salt len must be %v", maxSaltSize)
|
|
||||||
}
|
|
||||||
p, err := newFromPassword(salt, password, cost)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p.Hash(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompareHashAndPassword compares a bcrypt hashed password with its possible
|
|
||||||
// plaintext equivalent. Returns nil on success, or an error on failure.
|
|
||||||
func CompareHashAndPassword(hashedPassword, password []byte) error {
|
|
||||||
p, err := newFromHash(hashedPassword)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
otherHash, err := bcrypt(password, p.cost, p.salt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
|
|
||||||
if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrMismatchedHashAndPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cost returns the hashing cost used to create the given hashed
|
|
||||||
// password. When, in the future, the hashing cost of a password system needs
|
|
||||||
// to be increased in order to adjust for greater computational power, this
|
|
||||||
// function allows one to establish which passwords need to be updated.
|
|
||||||
func Cost(hashedPassword []byte) (int, error) {
|
|
||||||
p, err := newFromHash(hashedPassword)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return p.cost, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFromPassword(salt []byte, password []byte, cost int) (*hashed, error) {
|
|
||||||
if cost < MinCost {
|
|
||||||
cost = DefaultCost
|
|
||||||
}
|
|
||||||
p := new(hashed)
|
|
||||||
p.major = majorVersion
|
|
||||||
p.minor = minorVersion
|
|
||||||
|
|
||||||
err := checkCost(cost)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.cost = cost
|
|
||||||
|
|
||||||
p.salt = base64Encode(salt)
|
|
||||||
hash, err := bcrypt(password, p.cost, p.salt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.hash = hash
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFromHash(hashedSecret []byte) (*hashed, error) {
|
|
||||||
if len(hashedSecret) < minHashSize {
|
|
||||||
return nil, ErrHashTooShort
|
|
||||||
}
|
|
||||||
p := new(hashed)
|
|
||||||
n, err := p.decodeVersion(hashedSecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hashedSecret = hashedSecret[n:]
|
|
||||||
n, err = p.decodeCost(hashedSecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hashedSecret = hashedSecret[n:]
|
|
||||||
|
|
||||||
// The "+2" is here because we'll have to append at most 2 '=' to the salt
|
|
||||||
// when base64 decoding it in expensiveBlowfishSetup().
|
|
||||||
p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
|
|
||||||
copy(p.salt, hashedSecret[:encodedSaltSize])
|
|
||||||
|
|
||||||
hashedSecret = hashedSecret[encodedSaltSize:]
|
|
||||||
p.hash = make([]byte, len(hashedSecret))
|
|
||||||
copy(p.hash, hashedSecret)
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
|
|
||||||
cipherData := make([]byte, len(magicCipherData))
|
|
||||||
copy(cipherData, magicCipherData)
|
|
||||||
|
|
||||||
c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 24; i += 8 {
|
|
||||||
for j := 0; j < 64; j++ {
|
|
||||||
c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug compatibility with C bcrypt implementations. We only encode 23 of
|
|
||||||
// the 24 bytes encrypted.
|
|
||||||
hsh := base64Encode(cipherData[:maxCryptedHashSize])
|
|
||||||
return hsh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
|
|
||||||
|
|
||||||
csalt, err := base64Decode(salt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug compatibility with C bcrypt implementations. They use the trailing
|
|
||||||
// NULL in the key string during expansion.
|
|
||||||
ckey := append(key, 0)
|
|
||||||
|
|
||||||
c, err := blowfish.NewSaltedCipher(ckey, csalt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var i, rounds uint64
|
|
||||||
rounds = 1 << cost
|
|
||||||
for i = 0; i < rounds; i++ {
|
|
||||||
blowfish.ExpandKey(ckey, c)
|
|
||||||
blowfish.ExpandKey(csalt, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *hashed) Hash() []byte {
|
|
||||||
arr := make([]byte, 60)
|
|
||||||
arr[0] = '$'
|
|
||||||
arr[1] = p.major
|
|
||||||
n := 2
|
|
||||||
if p.minor != 0 {
|
|
||||||
arr[2] = p.minor
|
|
||||||
n = 3
|
|
||||||
}
|
|
||||||
arr[n] = '$'
|
|
||||||
n++
|
|
||||||
copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
|
|
||||||
n += 2
|
|
||||||
arr[n] = '$'
|
|
||||||
n++
|
|
||||||
copy(arr[n:], p.salt)
|
|
||||||
n += encodedSaltSize
|
|
||||||
copy(arr[n:], p.hash)
|
|
||||||
n += encodedHashSize
|
|
||||||
return arr[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
|
|
||||||
if sbytes[0] != '$' {
|
|
||||||
return -1, InvalidHashPrefixError(sbytes[0])
|
|
||||||
}
|
|
||||||
if sbytes[1] > majorVersion {
|
|
||||||
return -1, HashVersionTooNewError(sbytes[1])
|
|
||||||
}
|
|
||||||
p.major = sbytes[1]
|
|
||||||
n := 3
|
|
||||||
if sbytes[2] != '$' {
|
|
||||||
p.minor = sbytes[2]
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sbytes should begin where decodeVersion left off.
|
|
||||||
func (p *hashed) decodeCost(sbytes []byte) (int, error) {
|
|
||||||
cost, err := strconv.Atoi(string(sbytes[0:2]))
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
err = checkCost(cost)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
p.cost = cost
|
|
||||||
return 3, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *hashed) String() string {
|
|
||||||
return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCost(cost int) error {
|
|
||||||
if cost < MinCost || cost > MaxCost {
|
|
||||||
return InvalidCostError(cost)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
package bip39
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bartekn/go-bip39"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidSentenceLen defines the mnemonic sentence lengths supported by this BIP 39 library.
|
|
||||||
type ValidSentenceLen uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// FundRaiser is the sentence length used during the cosmos fundraiser (12 words).
|
|
||||||
FundRaiser ValidSentenceLen = 12
|
|
||||||
// Size of the checksum employed for the fundraiser
|
|
||||||
FundRaiserChecksumSize = 4
|
|
||||||
// FreshKey is the sentence length used for newly created keys (24 words).
|
|
||||||
FreshKey ValidSentenceLen = 24
|
|
||||||
// Size of the checksum employed for new keys
|
|
||||||
FreshKeyChecksumSize = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewMnemonic will return a string consisting of the mnemonic words for
|
|
||||||
// the given sentence length.
|
|
||||||
func NewMnemonic(len ValidSentenceLen) (words []string, err error) {
|
|
||||||
// len = (entropySize + checksum) / 11
|
|
||||||
var entropySize int
|
|
||||||
switch len {
|
|
||||||
case FundRaiser:
|
|
||||||
// entropySize = 128
|
|
||||||
entropySize = int(len)*11 - FundRaiserChecksumSize
|
|
||||||
case FreshKey:
|
|
||||||
// entropySize = 256
|
|
||||||
entropySize = int(len)*11 - FreshKeyChecksumSize
|
|
||||||
}
|
|
||||||
var entropy []byte
|
|
||||||
entropy, err = bip39.NewEntropy(entropySize)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var mnemonic string
|
|
||||||
mnemonic, err = bip39.NewMnemonic(entropy)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
words = strings.Split(mnemonic, " ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MnemonicToSeed creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password).
|
|
||||||
// This method does not validate the mnemonics checksum.
|
|
||||||
func MnemonicToSeed(mne string) (seed []byte) {
|
|
||||||
// we do not checksum here...
|
|
||||||
seed = bip39.NewSeed(mne, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MnemonicToSeedWithErrChecking returns the same seed as MnemonicToSeed.
|
|
||||||
// It creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password).
|
|
||||||
//
|
|
||||||
// Different from MnemonicToSeed it validates the checksum.
|
|
||||||
// For details on the checksum see the BIP 39 spec.
|
|
||||||
func MnemonicToSeedWithErrChecking(mne string) (seed []byte, err error) {
|
|
||||||
seed, err = bip39.NewSeedWithErrorChecking(mne, "")
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package bip39
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWordCodec_NewMnemonic(t *testing.T) {
|
|
||||||
_, err := NewMnemonic(FundRaiser)
|
|
||||||
require.NoError(t, err, "unexpected error generating fundraiser mnemonic")
|
|
||||||
|
|
||||||
_, err = NewMnemonic(FreshKey)
|
|
||||||
require.NoError(t, err, "unexpected error generating new 24-word mnemonic")
|
|
||||||
}
|
|
|
@ -7,9 +7,10 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bartekn/go-bip39"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cosmos/go-bip39"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser.
|
// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser.
|
||||||
|
@ -55,6 +54,77 @@ func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the BIP44 path and unmarshal into the struct.
|
||||||
|
// nolint: gocyclo
|
||||||
|
func NewParamsFromPath(path string) (*BIP44Params, error) {
|
||||||
|
spl := strings.Split(path, "/")
|
||||||
|
if len(spl) != 5 {
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
purpose, err := hardenedInt(spl[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
coinType, err := hardenedInt(spl[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
account, err := hardenedInt(spl[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
change, err := hardenedInt(spl[3])
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BIP44Params{
|
||||||
|
purpose: purpose,
|
||||||
|
coinType: coinType,
|
||||||
|
account: account,
|
||||||
|
change: change > 0,
|
||||||
|
addressIdx: addressIdx,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hardenedInt(field string) (uint32, error) {
|
||||||
|
field = strings.TrimSuffix(field, "'")
|
||||||
|
i, err := strconv.Atoi(field)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if i < 0 {
|
||||||
|
return 0, fmt.Errorf("fields must not be negative. got %d", i)
|
||||||
|
}
|
||||||
|
return uint32(i), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHardened(field string) bool {
|
||||||
|
return strings.HasSuffix(field, "'")
|
||||||
|
}
|
||||||
|
|
||||||
// NewFundraiserParams creates a BIP 44 parameter object from the params:
|
// NewFundraiserParams creates a BIP 44 parameter object from the params:
|
||||||
// m / 44' / 118' / account' / 0 / address_index
|
// m / 44' / 118' / account' / 0 / address_index
|
||||||
// The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser.
|
// The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser.
|
||||||
|
@ -62,6 +132,21 @@ 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.
|
||||||
|
func (p BIP44Params) DerivationPath() []uint32 {
|
||||||
|
change := uint32(0)
|
||||||
|
if p.change {
|
||||||
|
change = 1
|
||||||
|
}
|
||||||
|
return []uint32{
|
||||||
|
p.purpose,
|
||||||
|
p.coinType,
|
||||||
|
p.account,
|
||||||
|
change,
|
||||||
|
p.addressIdx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p BIP44Params) String() string {
|
func (p BIP44Params) String() string {
|
||||||
var changeStr string
|
var changeStr string
|
||||||
if p.change {
|
if p.change {
|
||||||
|
@ -128,10 +213,15 @@ func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, h
|
||||||
data = append([]byte{byte(0)}, privKeyBytes[:]...)
|
data = append([]byte{byte(0)}, privKeyBytes[:]...)
|
||||||
} else {
|
} else {
|
||||||
// this can't return an error:
|
// this can't return an error:
|
||||||
pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey()
|
_, ecPub := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes[:])
|
||||||
|
pubkeyBytes := ecPub.SerializeCompressed()
|
||||||
|
data = pubkeyBytes
|
||||||
|
|
||||||
|
/* By using btcec, we can remove the dependency on tendermint/crypto/secp256k1
|
||||||
|
pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey()
|
||||||
public := pubkey.(secp256k1.PubKeySecp256k1)
|
public := pubkey.(secp256k1.PubKeySecp256k1)
|
||||||
data = public[:]
|
data = public[:]
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
data = append(data, uint32ToBytes(index)...)
|
data = append(data, uint32ToBytes(index)...)
|
||||||
data2, chainCode2 := i64(chainCode[:], data)
|
data2, chainCode2 := i64(chainCode[:], data)
|
||||||
|
|
|
@ -3,9 +3,20 @@ package hd
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bip39"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/cosmos/go-bip39"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultBIP39Passphrase = ""
|
||||||
|
|
||||||
|
// return bip39 seed with empty passphrase
|
||||||
|
func mnemonicToSeed(mnemonic string) []byte {
|
||||||
|
return bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
//nolint
|
//nolint
|
||||||
func ExampleStringifyPathParams() {
|
func ExampleStringifyPathParams() {
|
||||||
path := NewParams(44, 0, 0, false, 0)
|
path := NewParams(44, 0, 0, false, 0)
|
||||||
|
@ -13,10 +24,57 @@ func ExampleStringifyPathParams() {
|
||||||
// Output: 44'/0'/0'/0/0
|
// Output: 44'/0'/0'/0/0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParamsFromPath(t *testing.T) {
|
||||||
|
goodCases := []struct {
|
||||||
|
params *BIP44Params
|
||||||
|
path string
|
||||||
|
}{
|
||||||
|
{&BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"},
|
||||||
|
{&BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"},
|
||||||
|
{&BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"},
|
||||||
|
{&BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"},
|
||||||
|
{&BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"},
|
||||||
|
{&BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"},
|
||||||
|
{&BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range goodCases {
|
||||||
|
params, err := NewParamsFromPath(c.path)
|
||||||
|
errStr := fmt.Sprintf("%d %v", i, c)
|
||||||
|
assert.NoError(t, err, errStr)
|
||||||
|
assert.EqualValues(t, c.params, params, errStr)
|
||||||
|
assert.Equal(t, c.path, c.params.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
badCases := []struct {
|
||||||
|
path string
|
||||||
|
}{
|
||||||
|
{"43'/0'/0'/0/0"}, // doesnt start with 44
|
||||||
|
{"44'/1'/0'/0/0/5"}, // too many fields
|
||||||
|
{"44'/0'/1'/0"}, // too few fields
|
||||||
|
{"44'/0'/0'/2/0"}, // change field can only be 0/1
|
||||||
|
{"44/0'/0'/0/0"}, // first field needs '
|
||||||
|
{"44'/0/0'/0/0"}, // second field needs '
|
||||||
|
{"44'/0'/0/0/0"}, // third field needs '
|
||||||
|
{"44'/0'/0'/0'/0"}, // fourth field must not have '
|
||||||
|
{"44'/0'/0'/0/0'"}, // fifth field must not have '
|
||||||
|
{"44'/-1'/0'/0/0"}, // no negatives
|
||||||
|
{"44'/0'/0'/-1/0"}, // no negatives
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range badCases {
|
||||||
|
params, err := NewParamsFromPath(c.path)
|
||||||
|
errStr := fmt.Sprintf("%d %v", i, c)
|
||||||
|
assert.Nil(t, params, errStr)
|
||||||
|
assert.Error(t, err, errStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//nolint
|
//nolint
|
||||||
func ExampleSomeBIP32TestVecs() {
|
func ExampleSomeBIP32TestVecs() {
|
||||||
|
|
||||||
seed := bip39.MnemonicToSeed("barrel original fuel morning among eternal " +
|
seed := mnemonicToSeed("barrel original fuel morning among eternal " +
|
||||||
"filter ball stove pluck matrix mechanic")
|
"filter ball stove pluck matrix mechanic")
|
||||||
master, ch := ComputeMastersFromSeed(seed)
|
master, ch := ComputeMastersFromSeed(seed)
|
||||||
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
||||||
|
@ -35,14 +93,14 @@ func ExampleSomeBIP32TestVecs() {
|
||||||
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
seed = bip39.MnemonicToSeed(
|
seed = mnemonicToSeed(
|
||||||
"advice process birth april short trust crater change bacon monkey medal garment " +
|
"advice process birth april short trust crater change bacon monkey medal garment " +
|
||||||
"gorilla ranch hour rival razor call lunar mention taste vacant woman sister")
|
"gorilla ranch hour rival razor call lunar mention taste vacant woman sister")
|
||||||
master, ch = ComputeMastersFromSeed(seed)
|
master, ch = ComputeMastersFromSeed(seed)
|
||||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4")
|
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4")
|
||||||
fmt.Println(hex.EncodeToString(priv[:]))
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
|
||||||
seed = bip39.MnemonicToSeed("idea naive region square margin day captain habit " +
|
seed = mnemonicToSeed("idea naive region square margin day captain habit " +
|
||||||
"gun second farm pact pulse someone armed")
|
"gun second farm pact pulse someone armed")
|
||||||
master, ch = ComputeMastersFromSeed(seed)
|
master, ch = ComputeMastersFromSeed(seed)
|
||||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420")
|
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420")
|
||||||
|
@ -53,7 +111,7 @@ func ExampleSomeBIP32TestVecs() {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
// bip32 path: m/0/7
|
// bip32 path: m/0/7
|
||||||
seed = bip39.MnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history")
|
seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history")
|
||||||
master, ch = ComputeMastersFromSeed(seed)
|
master, ch = ComputeMastersFromSeed(seed)
|
||||||
priv, _ = DerivePrivateKeyForPath(master, ch, "0/7")
|
priv, _ = DerivePrivateKeyForPath(master, ch, "0/7")
|
||||||
fmt.Println(hex.EncodeToString(priv[:]))
|
fmt.Println(hex.EncodeToString(priv[:]))
|
||||||
|
|
|
@ -6,11 +6,15 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bip39"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"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/keys/hd"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
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"
|
||||||
|
@ -46,6 +50,14 @@ const (
|
||||||
infoSuffix = "info"
|
infoSuffix = "info"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// used for deriving seed from mnemonic
|
||||||
|
defaultBIP39Passphrase = ""
|
||||||
|
|
||||||
|
// bits of entropy to draw when creating a mnemonic
|
||||||
|
defaultEntropySize = 256
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrUnsupportedSigningAlgo is raised when the caller tries to use a
|
// ErrUnsupportedSigningAlgo is raised when the caller tries to use a
|
||||||
// different signing scheme than secp256k1.
|
// different signing scheme than secp256k1.
|
||||||
|
@ -85,12 +97,17 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string
|
||||||
}
|
}
|
||||||
|
|
||||||
// default number of words (24):
|
// default number of words (24):
|
||||||
mnemonicS, err := bip39.NewMnemonic(bip39.FreshKey)
|
// this generates a mnemonic directly from the number of words by reading system entropy.
|
||||||
|
entropy, err := bip39.NewEntropy(defaultEntropySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mnemonic = strings.Join(mnemonicS, " ")
|
mnemonic, err = bip39.NewMnemonic(entropy)
|
||||||
seed := bip39.MnemonicToSeed(mnemonic)
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
@ -102,7 +119,7 @@ func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err err
|
||||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
|
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -119,7 +136,7 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf
|
||||||
err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words))
|
err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -127,12 +144,12 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) {
|
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
|
||||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err = kb.persistDerivedKey(seed, passwd, name, params.String())
|
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -229,7 +246,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t
|
||||||
err = fmt.Errorf("private key not available")
|
err = fmt.Errorf("private key not available")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -279,7 +296,7 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr
|
||||||
err = fmt.Errorf("private key not available")
|
err = fmt.Errorf("private key not available")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -296,7 +313,7 @@ func (kb dbKeybase) Export(name string) (armor string, err error) {
|
||||||
if bz == nil {
|
if bz == nil {
|
||||||
return "", fmt.Errorf("no key to export with name %s", name)
|
return "", fmt.Errorf("no key to export with name %s", name)
|
||||||
}
|
}
|
||||||
return armorInfoBytes(bz), nil
|
return mintkey.ArmorInfoBytes(bz), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportPubKey returns public keys in ASCII armored format.
|
// ExportPubKey returns public keys in ASCII armored format.
|
||||||
|
@ -311,7 +328,7 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return armorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||||
|
@ -319,7 +336,7 @@ func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||||
if len(bz) > 0 {
|
if len(bz) > 0 {
|
||||||
return errors.New("Cannot overwrite data for name " + name)
|
return errors.New("Cannot overwrite data for name " + name)
|
||||||
}
|
}
|
||||||
infoBytes, err := unarmorInfoBytes(armor)
|
infoBytes, err := mintkey.UnarmorInfoBytes(armor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -335,7 +352,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||||
if len(bz) > 0 {
|
if len(bz) > 0 {
|
||||||
return errors.New("Cannot overwrite data for name " + name)
|
return errors.New("Cannot overwrite data for name " + name)
|
||||||
}
|
}
|
||||||
pubBytes, err := unarmorPubKeyBytes(armor)
|
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -360,7 +377,7 @@ func (kb dbKeybase) Delete(name, passphrase string) error {
|
||||||
switch info.(type) {
|
switch info.(type) {
|
||||||
case localInfo:
|
case localInfo:
|
||||||
linfo := info.(localInfo)
|
linfo := info.(localInfo)
|
||||||
_, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
_, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -394,7 +411,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
|
||||||
switch info.(type) {
|
switch info.(type) {
|
||||||
case localInfo:
|
case localInfo:
|
||||||
linfo := info.(localInfo)
|
linfo := info.(localInfo)
|
||||||
key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -411,7 +428,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
|
||||||
|
|
||||||
func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string) Info {
|
func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string) Info {
|
||||||
// encrypt private key using passphrase
|
// encrypt private key using passphrase
|
||||||
privArmor := encryptArmorPrivKey(priv, passphrase)
|
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
|
||||||
// make Info
|
// make Info
|
||||||
pub := priv.PubKey()
|
pub := priv.PubKey()
|
||||||
info := newLocalInfo(name, pub, privArmor)
|
info := newLocalInfo(name, pub, privArmor)
|
||||||
|
|
|
@ -4,9 +4,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
|
|
||||||
|
@ -15,7 +18,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
BcryptSecurityParameter = 1
|
mintkey.BcryptSecurityParameter = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestKeyManagement makes sure we can manipulate these keys well
|
// TestKeyManagement makes sure we can manipulate these keys well
|
||||||
|
@ -342,7 +345,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, 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())
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package keys
|
package mintkey
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bcrypt"
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/armor"
|
"github.com/tendermint/tendermint/crypto/armor"
|
||||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||||
"github.com/tendermint/tendermint/crypto/xsalsa20symmetric"
|
"github.com/tendermint/tendermint/crypto/xsalsa20symmetric"
|
||||||
|
|
||||||
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -34,11 +35,16 @@ const (
|
||||||
// TODO: Consider increasing default
|
// TODO: Consider increasing default
|
||||||
var BcryptSecurityParameter = 12
|
var BcryptSecurityParameter = 12
|
||||||
|
|
||||||
func armorInfoBytes(bz []byte) string {
|
//-----------------------------------------------------------------
|
||||||
|
// add armor
|
||||||
|
|
||||||
|
// Armor the InfoBytes
|
||||||
|
func ArmorInfoBytes(bz []byte) string {
|
||||||
return armorBytes(bz, blockTypeKeyInfo)
|
return armorBytes(bz, blockTypeKeyInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func armorPubKeyBytes(bz []byte) string {
|
// Armor the PubKeyBytes
|
||||||
|
func ArmorPubKeyBytes(bz []byte) string {
|
||||||
return armorBytes(bz, blockTypePubKey)
|
return armorBytes(bz, blockTypePubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +56,16 @@ func armorBytes(bz []byte, blockType string) string {
|
||||||
return armor.EncodeArmor(blockType, header, bz)
|
return armor.EncodeArmor(blockType, header, bz)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unarmorInfoBytes(armorStr string) (bz []byte, err error) {
|
//-----------------------------------------------------------------
|
||||||
|
// remove armor
|
||||||
|
|
||||||
|
// Unarmor the InfoBytes
|
||||||
|
func UnarmorInfoBytes(armorStr string) (bz []byte, err error) {
|
||||||
return unarmorBytes(armorStr, blockTypeKeyInfo)
|
return unarmorBytes(armorStr, blockTypeKeyInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
|
// Unarmor the PubKeyBytes
|
||||||
|
func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
|
||||||
return unarmorBytes(armorStr, blockTypePubKey)
|
return unarmorBytes(armorStr, blockTypePubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +85,11 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
//-----------------------------------------------------------------
|
||||||
|
// encrypt/decrypt with armor
|
||||||
|
|
||||||
|
// Encrypt and armor the private key.
|
||||||
|
func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
||||||
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
|
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
|
||||||
header := map[string]string{
|
header := map[string]string{
|
||||||
"kdf": "bcrypt",
|
"kdf": "bcrypt",
|
||||||
|
@ -84,7 +99,22 @@ func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
||||||
return armorStr
|
return armorStr
|
||||||
}
|
}
|
||||||
|
|
||||||
func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
|
// encrypt the given privKey with the passphrase using a randomly
|
||||||
|
// generated salt and the xsalsa20 cipher. returns the salt and the
|
||||||
|
// encrypted priv key.
|
||||||
|
func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
|
||||||
|
saltBytes = crypto.CRandBytes(16)
|
||||||
|
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||||
|
if err != nil {
|
||||||
|
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||||||
|
}
|
||||||
|
key = crypto.Sha256(key) // get 32 bytes
|
||||||
|
privKeyBytes := privKey.Bytes()
|
||||||
|
return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unarmor and decrypt the private key.
|
||||||
|
func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
|
||||||
var privKey crypto.PrivKey
|
var privKey crypto.PrivKey
|
||||||
blockType, header, encBytes, err := armor.DecodeArmor(armorStr)
|
blockType, header, encBytes, err := armor.DecodeArmor(armorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -107,17 +137,6 @@ func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey,
|
||||||
return privKey, err
|
return privKey, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
|
|
||||||
saltBytes = crypto.CRandBytes(16)
|
|
||||||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
|
||||||
if err != nil {
|
|
||||||
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
|
||||||
}
|
|
||||||
key = crypto.Sha256(key) // Get 32 bytes
|
|
||||||
privKeyBytes := privKey.Bytes()
|
|
||||||
return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
|
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
|
||||||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -26,8 +26,12 @@ type Keybase interface {
|
||||||
CreateKey(name, mnemonic, passwd string) (info Info, err error)
|
CreateKey(name, mnemonic, passwd string) (info Info, err error)
|
||||||
// CreateFundraiserKey takes a mnemonic and derives, a password
|
// CreateFundraiserKey takes a mnemonic and derives, a password
|
||||||
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
|
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
|
||||||
// Derive derives a key from the passed mnemonic using a BIP44 path.
|
// Compute a BIP39 seed from th mnemonic and bip39Passwd.
|
||||||
Derive(name, mnemonic, passwd string, params hd.BIP44Params) (Info, error)
|
// 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
|
// Create, store, and return a new Ledger key reference
|
||||||
CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error)
|
CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
title: "Cosmos Network",
|
title: "Cosmos Documentation",
|
||||||
description: "Documentation for the Cosmos Network.",
|
description: "Documentation for the Cosmos Network.",
|
||||||
|
ga: "UA-51029217-2",
|
||||||
dest: "./dist/docs",
|
dest: "./dist/docs",
|
||||||
base: "/docs/",
|
base: "/docs/",
|
||||||
markdown: {
|
markdown: {
|
||||||
|
@ -67,6 +68,14 @@ module.exports = {
|
||||||
"/sdk/sdk-by-examples/simple-governance/running-the-application"
|
"/sdk/sdk-by-examples/simple-governance/running-the-application"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Light Client",
|
||||||
|
collapsable: false,
|
||||||
|
children: [
|
||||||
|
"/light/",
|
||||||
|
"/light/getting_started"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Lotion JS",
|
title: "Lotion JS",
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
# Post-0.25/GoS Pre-Release
|
# Post-0.25/GoS Pre-Release
|
||||||
|
|
||||||
## Staking/Slashing/Stability
|
## Staking / Slashing - Stability
|
||||||
|
|
||||||
- Other slashing issues blocking for launch - [#1256](https://github.com/cosmos/cosmos-sdk/issues/1256)
|
- [Prelaunch Issues](https://github.com/cosmos/cosmos-sdk/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Astaking+label%3Aprelaunch-2.0)
|
||||||
- Miscellaneous minor staking issues
|
|
||||||
- [List here](https://github.com/cosmos/cosmos-sdk/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Astaking+label%3Aprelaunch)
|
|
||||||
- Need to figure out scope of work here to estimate time
|
|
||||||
- @rigelrozanski to start next
|
|
||||||
- Consider "tombstone" / "prison" - double-sign and you can never validate again - https://github.com/cosmos/cosmos-sdk/issues/2363
|
|
||||||
|
|
||||||
## Multisig
|
## Multisig
|
||||||
|
|
||||||
|
@ -16,24 +11,29 @@
|
||||||
|
|
||||||
## ABCI Changes
|
## ABCI Changes
|
||||||
|
|
||||||
- Need to update for new ABCI changes when/if they land - error string, tags are list of lists
|
- CheckEvidence/DeliverEvidence
|
||||||
|
- CheckTx/DeliverTx ordering semantics
|
||||||
|
- ABCI Error string update (only on the SDK side)
|
||||||
- Need to verify correct proposer reward semantics
|
- Need to verify correct proposer reward semantics
|
||||||
- CheckEvidence/DeliverEvidence, CheckTx/DeliverTx ordering semantics
|
|
||||||
|
|
||||||
## Gas
|
## Gas
|
||||||
|
|
||||||
- Charge for transaction size
|
- Write specification and explainer document for Gas in Cosmos
|
||||||
- Decide what "one gas" corresponds to (standard hardware benchmarks?)
|
* Charge for transaction size
|
||||||
- More benchmarking
|
* Decide what "one gas" corresponds to (standard hardware benchmarks?)
|
||||||
- Consider charging based on maximum depth of IAVL tree iteration
|
* Consider charging based on maximum depth of IAVL tree iteration
|
||||||
- Test out gas estimation in CLI and LCD and ensure the UX works
|
- Test out gas estimation in CLI and LCD and ensure the UX works
|
||||||
|
|
||||||
## LCD
|
## LCD
|
||||||
|
|
||||||
- Bianje working on implementation of ICS standards
|
- Bianje working with Voyager team (@fedekunze) to complete implementation and documentation.
|
||||||
- Additional PR incoming for ICS 22 and ICS 23
|
|
||||||
- Decide what ought to be ICS-standardized and what ought not to
|
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- gaiad / gaiacli
|
||||||
|
- LCD
|
||||||
|
- Each module
|
||||||
|
- Tags [#1780](https://github.com/cosmos/cosmos-sdk/issues/1780)
|
||||||
# Lower priority
|
# Lower priority
|
||||||
|
|
||||||
## Governance v2
|
## Governance v2
|
||||||
|
@ -41,6 +41,6 @@
|
||||||
- Circuit breaker - https://github.com/cosmos/cosmos-sdk/issues/926
|
- Circuit breaker - https://github.com/cosmos/cosmos-sdk/issues/926
|
||||||
- Parameter change proposals (roughly the same implementation as circuit breaker)
|
- Parameter change proposals (roughly the same implementation as circuit breaker)
|
||||||
|
|
||||||
## Documentation
|
## Staking / Slashing - Stability
|
||||||
|
|
||||||
- gaiad / gaiacli / gaialite documentation!
|
- Consider "tombstone" / "prison" - double-sign and you can never validate again - https://github.com/cosmos/cosmos-sdk/issues/2363
|
||||||
|
|
|
@ -1,961 +0,0 @@
|
||||||
# Cosmos Hub (Gaia-Lite) LCD API
|
|
||||||
|
|
||||||
This document describes the API that is exposed by the specific Light Client Daemon (LCD) implementation of the Cosmos Hub (Gaia). Those APIs are exposed by a REST server and can easily be accessed over HTTP/WS (websocket)
|
|
||||||
connections.
|
|
||||||
|
|
||||||
The complete API is comprised of the sub-APIs of different modules. The modules in the Cosmos Hub (Gaia-Lite) API are:
|
|
||||||
|
|
||||||
- ICS0 ([TendermintAPI](api.md#ics0---tendermintapi))
|
|
||||||
- ICS1 ([KeyAPI](api.md#ics1---keyapi))
|
|
||||||
- ICS20 ([TokenAPI](api.md#ics20---tokenapi))
|
|
||||||
- ICS21 ([StakingAPI](api.md#ics21---stakingapi))
|
|
||||||
- ICS22 ([GovernanceAPI](api.md#ics22---governanceapi))
|
|
||||||
- ICS23 ([SlashingAPI](api.md#ics23---slashingapi))
|
|
||||||
|
|
||||||
Error messages my change and should be only used for display purposes. Error messages should not be
|
|
||||||
used for determining the error type.
|
|
||||||
|
|
||||||
## ICS0 - TendermintAPI
|
|
||||||
|
|
||||||
Exposes the same functionality as the Tendermint RPC from a full node. It aims to have a very similar API.
|
|
||||||
|
|
||||||
### POST /txs
|
|
||||||
|
|
||||||
- **URL**: `/txs`
|
|
||||||
- Query Parameters:
|
|
||||||
- `?return={sync|async|block}`:
|
|
||||||
- `return=sync`: Waits for the transaction to pass `CheckTx`
|
|
||||||
- `return=async`: Returns the request immediately after it is received by the server
|
|
||||||
- `return=block`: waits for for the transaction to be committed in a block
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"transaction": "string",
|
|
||||||
"return": "string",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"code":0,
|
|
||||||
"hash":"0D33F2F03A5234F38706E43004489E061AC40A2E",
|
|
||||||
"data":"",
|
|
||||||
"log":""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ICS1 - KeyAPI
|
|
||||||
|
|
||||||
This API exposes all functionality needed for key creation, signing and management.
|
|
||||||
|
|
||||||
### GET /keys
|
|
||||||
|
|
||||||
- **URL**: `/keys`
|
|
||||||
- **Functionality**: Gets a list of all the keys.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"1.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"account":[
|
|
||||||
{
|
|
||||||
"name":"monkey",
|
|
||||||
"address":"cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd",
|
|
||||||
"pub_key":"cosmospub1zcjduc3q8s8ha96ry4xc5xvjp9tr9w9p0e5lk5y0rpjs5epsfxs4wmf72x3shvus0t"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name":"test",
|
|
||||||
"address":"cosmos1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2",
|
|
||||||
"pub_key":"cosmospub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"block_height":5241
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /keys
|
|
||||||
|
|
||||||
- **URL**: `/keys`
|
|
||||||
- **Functionality**: Create a new key.
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "string",
|
|
||||||
"password": "string",
|
|
||||||
"seed": "string",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"1.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"seed":"crime carpet recycle erase simple prepare moral dentist fee cause pitch trigger when velvet animal abandon"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /keys/{name}
|
|
||||||
|
|
||||||
- **URL** : `/keys/{name}`
|
|
||||||
- **Functionality**: Get the information for the specified key.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"1.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"name":"test",
|
|
||||||
"address":"cosmos1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2",
|
|
||||||
"pub_key":"cosmospub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### PUT /keys/{name}
|
|
||||||
|
|
||||||
- **URL** : `/keys/{name}`
|
|
||||||
- **Functionality**: Change the encryption password for the specified key.
|
|
||||||
- PUT Body:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"old_password": "string",
|
|
||||||
"new_password": "string",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### DELETE /keys/{name}
|
|
||||||
|
|
||||||
- **URL**: `/keys/{name}`
|
|
||||||
- **Functionality**: Delete the specified key.
|
|
||||||
- DELETE Body:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"password": "string",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"1.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /keys/{name}/recover
|
|
||||||
|
|
||||||
- **URL**: `/keys/{name}/recover`
|
|
||||||
- **Functionality**: Recover your key from seed and persist it encrypted with the password.
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"password": "string",
|
|
||||||
"seed": "string",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"1.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"address":"BD607C37147656A507A5A521AA9446EB72B2C907"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /auth/accounts/{address}
|
|
||||||
|
|
||||||
- **URL**: `/auth/accounts/{address}`
|
|
||||||
- **Functionality**: Query the information of an account .
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"1.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"address": "82A57F8575BDFA22F5164C75361A21D2B0E11089",
|
|
||||||
"public_key": "PubKeyEd25519{A0EEEED3C9CE1A6988DEBFE347635834A1C0EBA0B4BB1125896A7072D22E650D}",
|
|
||||||
"coins": [
|
|
||||||
{"atom": 300},
|
|
||||||
{"photon": 15}
|
|
||||||
],
|
|
||||||
"account_number": 1,
|
|
||||||
"sequence": 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /auth/tx/sign
|
|
||||||
|
|
||||||
- **URL**: `/auth/tx/sign`
|
|
||||||
- **Functionality**: Sign a transaction without broadcasting it.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api": "1.0",
|
|
||||||
"code": 200,
|
|
||||||
"error": "",
|
|
||||||
"result": {
|
|
||||||
"type": "auth/StdTx",
|
|
||||||
"value": {
|
|
||||||
"msg": [
|
|
||||||
{
|
|
||||||
"type": "cosmos-sdk/Send",
|
|
||||||
"value": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"address": "cosmos1ql4ekxkujf3xllk8h5ldhhgh4ylpu7kwec6q3d",
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "steak",
|
|
||||||
"amount": "1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"address": "cosmos1dhyqhg4px33ed3erqymls0hc7q2lxw9hhfwklj",
|
|
||||||
"coins": [
|
|
||||||
{
|
|
||||||
"denom": "steak",
|
|
||||||
"amount": "1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fee": {
|
|
||||||
"amount": [
|
|
||||||
{
|
|
||||||
"denom": "",
|
|
||||||
"amount": "0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"gas": "2742"
|
|
||||||
},
|
|
||||||
"signatures": [
|
|
||||||
{
|
|
||||||
"pub_key": {
|
|
||||||
"type": "tendermint/PubKeySecp256k1",
|
|
||||||
"value": "A2A/f2IYnrPUMTMqhwN81oas9jurtfcsvxdeLlNw3gGy"
|
|
||||||
},
|
|
||||||
"signature": "MEQCIGVn73y9QLwBa3vmsAD1bs3ygX75Wo+lAFSAUDs431ZPAiBWAf2amyqTCDXE9J87rL9QF9sd5JvVMt7goGSuamPJwg==",
|
|
||||||
"account_number": "1",
|
|
||||||
"sequence": "0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"memo": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /auth/tx/broadcast
|
|
||||||
|
|
||||||
- **URL**: `/auth/broadcast`
|
|
||||||
- **Functionality**: Broadcast a transaction.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api": "1.0",
|
|
||||||
"code": 200,
|
|
||||||
"error": "",
|
|
||||||
"result":
|
|
||||||
{
|
|
||||||
"check_tx": {
|
|
||||||
"log": "Msg 0: ",
|
|
||||||
"gasWanted": "2742",
|
|
||||||
"gasUsed": "1002"
|
|
||||||
},
|
|
||||||
"deliver_tx": {
|
|
||||||
"log": "Msg 0: ",
|
|
||||||
"gasWanted": "2742",
|
|
||||||
"gasUsed": "2742",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"key": "c2VuZGVy",
|
|
||||||
"value": "Y29zbW9zMXdjNTl6ZXU3MmNjdnp5ZWR6ZGE1N3pzcXh2eXZ2Y3poaHBhdDI4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "cmVjaXBpZW50",
|
|
||||||
"value": "Y29zbW9zMTJ4OTNmY3V2azg3M3o1ejZnejRlNTl2dnlxcXp1eDdzdDcwNWd5"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "784314784503582AC885BD6FB0D2A5B79FF703A7",
|
|
||||||
"height": "5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ICS20 - TokenAPI
|
|
||||||
|
|
||||||
The TokenAPI exposes all functionality needed to query account balances and send transactions.
|
|
||||||
|
|
||||||
### GET /bank/balance/{account}
|
|
||||||
|
|
||||||
- **URL**: `/bank/balance/{account}`
|
|
||||||
- **Functionality**: Query the specified account's balance.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result": {
|
|
||||||
"atom":1000,
|
|
||||||
"photon":500,
|
|
||||||
"ether":20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /bank/transfers
|
|
||||||
|
|
||||||
- **URL**: `/bank/transfers`
|
|
||||||
- **Functionality**: Create a transfer in the bank module.
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"amount": [
|
|
||||||
{
|
|
||||||
"denom": "string",
|
|
||||||
"amount": 64,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "string",
|
|
||||||
"password": "string",
|
|
||||||
"chain_id": "string",
|
|
||||||
"account_number": 64,
|
|
||||||
"sequence": 64,
|
|
||||||
"gas": 64,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.0",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO:<JSON sign bytes for the transaction>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ICS21 - StakingAPI
|
|
||||||
|
|
||||||
The StakingAPI exposes all functionality needed for validation and delegation in Proof-of-Stake.
|
|
||||||
|
|
||||||
### GET /stake/delegators/{delegatorAddr}
|
|
||||||
|
|
||||||
- **URL**: `/stake/delegators/{delegatorAddr}`
|
|
||||||
- **Functionality**: Get all delegations (delegation, undelegation) from a delegator.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result": {
|
|
||||||
"atom":1000,
|
|
||||||
"photon":500,
|
|
||||||
"ether":20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/delegators/{delegatorAddr}/validators
|
|
||||||
|
|
||||||
- **URL**: `/stake/delegators/{delegatorAddr}/validators`
|
|
||||||
- **Functionality**: Query all validators that a delegator is bonded to.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/delegators/{delegatorAddr}/validators/{validatorAddr}
|
|
||||||
|
|
||||||
- **URL**: `/stake/delegators/{delegatorAddr}/validators/{validatorAddr}`
|
|
||||||
- **Functionality**: Query a validator that a delegator is bonded to
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/delegators/{delegatorAddr}/txs
|
|
||||||
|
|
||||||
- **URL**: `/stake/delegators/{delegatorAddr}/txs`
|
|
||||||
- **Functionality**: Get all staking txs (i.e msgs) from a delegator.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /stake/delegators/{delegatorAddr}/delegations
|
|
||||||
|
|
||||||
- **URL**: `/stake/delegators/{delegatorAddr}/delegations`
|
|
||||||
- **Functionality**: Submit or edit a delegation.
|
|
||||||
<!--NOTE Should this be a PUT instead of a POST? the code indicates that this is an edit operation-->
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "string",
|
|
||||||
"password": "string",
|
|
||||||
"chain_id": "string",
|
|
||||||
"account_number": 64,
|
|
||||||
"sequence": 64,
|
|
||||||
"gas": 64,
|
|
||||||
"delegations": [
|
|
||||||
{
|
|
||||||
"delegator_addr": "string",
|
|
||||||
"validator_addr": "string",
|
|
||||||
"delegation": {
|
|
||||||
"denom": "string",
|
|
||||||
"amount": 1234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"begin_unbondings": [
|
|
||||||
{
|
|
||||||
"delegator_addr": "string",
|
|
||||||
"validator_addr": "string",
|
|
||||||
"shares": "string",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complete_unbondings": [
|
|
||||||
{
|
|
||||||
"delegator_addr": "string",
|
|
||||||
"validator_addr": "string",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"begin_redelegates": [
|
|
||||||
{
|
|
||||||
"delegator_addr": "string",
|
|
||||||
"validator_src_addr": "string",
|
|
||||||
"validator_dst_addr": "string",
|
|
||||||
"shares": "string",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complete_redelegates": [
|
|
||||||
{
|
|
||||||
"delegator_addr": "string",
|
|
||||||
"validator_src_addr": "string",
|
|
||||||
"validator_dst_addr": "string",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/delegators/{delegatorAddr}/delegations/{validatorAddr}
|
|
||||||
|
|
||||||
- **URL**: `/stake/delegators/{delegatorAddr}/delegations/{validatorAddr}`
|
|
||||||
- **Functionality**: Query the current delegation status between a delegator and a validator.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}
|
|
||||||
|
|
||||||
- **URL**: `/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}`
|
|
||||||
- **Functionality**: Query all unbonding delegations between a delegator and a validator.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/validators
|
|
||||||
|
|
||||||
- **URL**: `/stake/validators`
|
|
||||||
- **Functionality**: Get all validator candidates.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/validators/{validatorAddr}
|
|
||||||
|
|
||||||
- **URL**: `/stake/validators/{validatorAddr}`
|
|
||||||
- **Functionality**: Query the information from a single validator.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/parameters
|
|
||||||
|
|
||||||
- **URL**: `/stake/parameters`
|
|
||||||
- **Functionality**: Get the current value of staking parameters.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"inflation_rate_change": 1300000000,
|
|
||||||
"inflation_max": 2000000000,
|
|
||||||
"inflation_min": 700000000,
|
|
||||||
"goal_bonded": 6700000000,
|
|
||||||
"unbonding_time": "72h0m0s",
|
|
||||||
"max_validators": 100,
|
|
||||||
"bond_denom": "atom"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /stake/pool
|
|
||||||
|
|
||||||
- **URL**: `/stake/pool`
|
|
||||||
- **Functionality**: Get the current value of the dynamic parameters of the current state (*i.e* `Pool`).
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.1",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"loose_tokens": 0,
|
|
||||||
"bonded_tokens": 0,
|
|
||||||
"inflation_last_time": "1970-01-01 01:00:00 +0100 CET",
|
|
||||||
"inflation": 700000000,
|
|
||||||
"date_last_commission_reset": 0,
|
|
||||||
"prev_bonded_shares": 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ICS22 - GovernanceAPI
|
|
||||||
|
|
||||||
The GovernanceAPI exposes all functionality needed for casting votes on plain text, software upgrades and parameter change proposals.
|
|
||||||
|
|
||||||
### GET /gov/proposals
|
|
||||||
|
|
||||||
- **URL**: `/gov/proposals`
|
|
||||||
- **Functionality**: Query all submited proposals
|
|
||||||
- Response on Success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"proposals":[
|
|
||||||
"TODO"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /gov/proposals
|
|
||||||
|
|
||||||
- **URL**: `/gov/proposals`
|
|
||||||
- **Functionality**: Submit a proposal
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"base_req": {
|
|
||||||
// Name of key to use
|
|
||||||
"name": "string",
|
|
||||||
// Password for that key
|
|
||||||
"password": "string",
|
|
||||||
"chain_id": "string",
|
|
||||||
"account_number": 64,
|
|
||||||
"sequence": 64,
|
|
||||||
"gas": 64
|
|
||||||
},
|
|
||||||
// Title of the proposal
|
|
||||||
"title": "string",
|
|
||||||
// Description of the proposal
|
|
||||||
"description": "string",
|
|
||||||
// PlainTextProposal supported now. SoftwareUpgradeProposal and other types may be supported soon
|
|
||||||
"proposal_type": "string",
|
|
||||||
// A cosmos address
|
|
||||||
"proposer": "string",
|
|
||||||
"initial_deposit": [
|
|
||||||
{
|
|
||||||
"denom": "string",
|
|
||||||
"amount": 64,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"TODO": "TODO",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /gov/proposals/{proposal-id}
|
|
||||||
|
|
||||||
- **URL**: `/gov/proposals/{proposal-id}`
|
|
||||||
- **Functionality**: Query a proposal
|
|
||||||
- Response on Success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"proposal_id": 1,
|
|
||||||
"title": "Example title",
|
|
||||||
"description": "a larger description with the details of the proposal",
|
|
||||||
"proposal_type": "Text",
|
|
||||||
"proposal_status": "DepositPeriod",
|
|
||||||
"tally_result": {
|
|
||||||
"yes": 0,
|
|
||||||
"abstain": 0,
|
|
||||||
"no": 0,
|
|
||||||
"no_with_veto": 0
|
|
||||||
},
|
|
||||||
"submit_block": 5238512,
|
|
||||||
"total_deposit": {"atom": 50},
|
|
||||||
"voting_start_block": -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /gov/proposals/{proposal-id}/deposits
|
|
||||||
|
|
||||||
- **URL**: `/gov/proposals/{proposal-id}/deposits`
|
|
||||||
- **Functionality**: Submit or rise a deposit to a proposal in order to make it active
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"base_req": {
|
|
||||||
"name": "string",
|
|
||||||
"password": "string",
|
|
||||||
"chain_id": "string",
|
|
||||||
"account_number": 0,
|
|
||||||
"sequence": 0,
|
|
||||||
"gas": "simulate"
|
|
||||||
},
|
|
||||||
"depositer": "string",
|
|
||||||
"amount": 0,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"TODO": "TODO",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /gov/proposals/{proposal-id}/deposits/{address}
|
|
||||||
|
|
||||||
- **URL**: `/gov/proposals/{proposal-id}/deposits/{address}`
|
|
||||||
- **Functionality**: Query a validator's deposit to submit a proposal
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"amount": {"atom": 150},
|
|
||||||
"depositer": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd",
|
|
||||||
"proposal-id": 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /gov/proposals/{proposal-id}/tally
|
|
||||||
|
|
||||||
- **URL**: `/gov/proposals/{proposal-id}/tally`
|
|
||||||
- **Functionality**: Get the tally of a given proposal.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result": {
|
|
||||||
"yes": 0,
|
|
||||||
"abstain": 0,
|
|
||||||
"no": 0,
|
|
||||||
"no_with_veto": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### GET /gov/proposals/{proposal-id}/votes
|
|
||||||
|
|
||||||
- **URL**: `/gov/proposals/{proposal-id}/votes`
|
|
||||||
- **Functionality**: Query all votes from a specific proposal
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result": [
|
|
||||||
{
|
|
||||||
"proposal-id": 1,
|
|
||||||
"voter": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd",
|
|
||||||
"option": "no_with_veto"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"proposal-id": 1,
|
|
||||||
"voter": "cosmos1849m9wncrqp6v4tkss6a3j8uzvuv0cp7f75lrq",
|
|
||||||
"option": "yes"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### POST /gov/proposals/{proposal-id}/votes
|
|
||||||
|
|
||||||
- **URL**: `/gov/proposals/{proposal-id}/votes`
|
|
||||||
- **Functionality**: Vote for a specific proposal
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"base_req": {
|
|
||||||
"name": "string",
|
|
||||||
"password": "string",
|
|
||||||
"chain_id": "string",
|
|
||||||
"account_number": 0,
|
|
||||||
"sequence": 0,
|
|
||||||
"gas": "simulate"
|
|
||||||
},
|
|
||||||
// A cosmos address
|
|
||||||
"voter": "string",
|
|
||||||
// Value of the vote option `Yes`, `No` `Abstain`, `NoWithVeto`
|
|
||||||
"option": "string",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"TODO": "TODO",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /gov/proposals/{proposal-id}/votes/{address}
|
|
||||||
|
|
||||||
- **URL** : `/gov/proposals/{proposal-id}/votes/{address}`
|
|
||||||
- **Functionality**: Get the current `Option` submited by an address
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.2",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"proposal-id": 1,
|
|
||||||
"voter": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd",
|
|
||||||
"option": "no_with_veto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ICS23 - SlashingAPI
|
|
||||||
|
|
||||||
The SlashingAPI exposes all functionalities needed to slash (*i.e* penalize) validators and delegators in Proof-of-Stake. The penalization is a fine of the staking coin and jail time, defined by governance parameters. During the jail period, the penalized validator is "jailed".
|
|
||||||
|
|
||||||
### GET /slashing/validator/{validatorAddr}/signing-info
|
|
||||||
|
|
||||||
- **URL**: `/slashing/validator/{validatorAddr}/signing-info`
|
|
||||||
- **Functionality**: Query the information from a single validator.
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.3",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /slashing/validators/{validatorAddr}/unjail
|
|
||||||
|
|
||||||
- **URL**: `/slashing/validators/{validatorAddr}/unjail`
|
|
||||||
- **Functionality**: Submit a message to unjail a validator after it has been penalized.
|
|
||||||
- POST Body:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
// Name of key to use
|
|
||||||
"name": "string",
|
|
||||||
// Password for that key
|
|
||||||
"password": "string",
|
|
||||||
"chain_id": "string",
|
|
||||||
"account_number": 64,
|
|
||||||
"sequence": 64,
|
|
||||||
"gas": 64,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Returns on success:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rest api":"2.3",
|
|
||||||
"code":200,
|
|
||||||
"error":"",
|
|
||||||
"result":{
|
|
||||||
"transaction":"TODO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,203 +0,0 @@
|
||||||
# Load Balancing Module - WIP
|
|
||||||
|
|
||||||
The LCD will be an important bridge between service providers and cosmos blockchain network. Suppose
|
|
||||||
a service provider wants to monitor token information for millions of accounts. Then it has to keep
|
|
||||||
sending a large mount of requests to LCD to query token information. As a result, LCD will send huge
|
|
||||||
requests to full node to get token information and necessary proof which will cost full node much
|
|
||||||
computing and bandwidth resource. Too many requests to a single full node may result in some bad
|
|
||||||
situations:
|
|
||||||
|
|
||||||
```text
|
|
||||||
1. The full node crash possibility increases.
|
|
||||||
2. The reply delay increases.
|
|
||||||
3. The system reliability will decrease.
|
|
||||||
4. As the full node may belong to other people or associates, they may deny too frequent access from a single client.
|
|
||||||
```
|
|
||||||
|
|
||||||
It is very urgent to solve this problems. Here we consider to import load balancing into LCD. By the
|
|
||||||
help of load balancing, LCD can distribute millions of requests to a set of full nodes. Thus the
|
|
||||||
load of each full node won't be too heavy and the unavailable full nodes will be wiped out of query
|
|
||||||
list. In addition, the system reliability will increase.
|
|
||||||
|
|
||||||
## Design
|
|
||||||
|
|
||||||
This module need combine with client to realize the real load balancing. It can embed the
|
|
||||||
[HTTP Client](https://github.com/tendermint/tendermint/rpc/lib/client/httpclient.go). In other
|
|
||||||
words,we realise the new httpclient based on `HTTP`.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type HTTPLoadBalancer struct {
|
|
||||||
rpcs map[string]*rpcclient.JSONRPCClient
|
|
||||||
*WSEvents
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## The Diagram of LCD RPC WorkFlow with LoadBalance
|
|
||||||
|
|
||||||
![The Diagram of LCD RPC WorkFlow](pics/loadbalanceDiagram.png)
|
|
||||||
|
|
||||||
In the above sequence diagram, application calls the `Request()`, and LCD finally call the
|
|
||||||
`HTTP.Request()` through the SecureClient `Wrapper`. In every `HTTP.Request()`, `Getclient()`
|
|
||||||
selects the current working rpcclient by the load balancing algorithm,then run the
|
|
||||||
`JSONRPCClient.Call()` to request from the Full Node, finally `UpdateClient()` updates the weight of
|
|
||||||
the current rpcclient according to the status that is returned by the full node. The `GetAddr()`
|
|
||||||
and `UpdateAddrWeight()` are realized in the load balancing module.
|
|
||||||
|
|
||||||
There are some abilities to do:
|
|
||||||
|
|
||||||
* Add the Remote Address
|
|
||||||
* Delete the Remote Address
|
|
||||||
* Update the weights of the addresses
|
|
||||||
|
|
||||||
## Load balancing Strategies
|
|
||||||
|
|
||||||
We can design some strategies like nginx to combine the different load balancing algorithms to get
|
|
||||||
the final remote. We can also get the status of the remote server to add or delete the addresses and
|
|
||||||
update weights of the addresses.
|
|
||||||
|
|
||||||
In a word,it can make the entire LCD work more effective in actual conditions.
|
|
||||||
We are working this module independently in this [Github Repository](https://github.com/MrXJC/GoLoadBalance).
|
|
||||||
|
|
||||||
## Interface And Type
|
|
||||||
|
|
||||||
### Balancer
|
|
||||||
|
|
||||||
This interface `Balancer`is the core of the package. Every load balancing algorithm should realize
|
|
||||||
it,and it defined two interfaces.
|
|
||||||
|
|
||||||
* `init` initialize the balancer, assigns the variables which `DoBalance` needs.
|
|
||||||
* `DoBalance` load balance the full node addresses according to the current situation.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package balance
|
|
||||||
|
|
||||||
type Balancer interface {
|
|
||||||
init(NodeAddrs)
|
|
||||||
DoBalance(NodeAddrs) (*NodeAddr,int,error)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### NodeAddr
|
|
||||||
|
|
||||||
* host: ip address
|
|
||||||
* port: the number of port
|
|
||||||
* weight: the weight of this full node address,default:1
|
|
||||||
|
|
||||||
This NodeAddr is the base struct of the address.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type NodeAddr struct{
|
|
||||||
host string
|
|
||||||
port int
|
|
||||||
weight int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NodeAddr) GetHost() string
|
|
||||||
|
|
||||||
func (p *NodeAddr) GetPort() int
|
|
||||||
|
|
||||||
func (p *NodeAddr) GetWeight() int
|
|
||||||
|
|
||||||
func (p *NodeAddr) updateWeight(weight int)
|
|
||||||
```
|
|
||||||
|
|
||||||
The `weight` is the important factor that schedules which full node the LCD calls. The weight can be
|
|
||||||
changed by the information from the full node. So we have the function `updateWegiht`.
|
|
||||||
|
|
||||||
### NodeAddrs
|
|
||||||
|
|
||||||
>in `balance/types.go`
|
|
||||||
|
|
||||||
`NodeAddrs` is the list of the full node address. This is the member variable in the
|
|
||||||
BalanceManager(`BalancerMgr`).
|
|
||||||
|
|
||||||
```go
|
|
||||||
type NodeAddrs []*NodeAddr
|
|
||||||
```
|
|
||||||
|
|
||||||
## Load Balancing Algorithm
|
|
||||||
|
|
||||||
### Random
|
|
||||||
|
|
||||||
>in `balance/random.go`
|
|
||||||
|
|
||||||
Random algorithm selects a remote address randomly to process the request. The probability of them
|
|
||||||
being selected is the same.
|
|
||||||
|
|
||||||
### RandomWeight
|
|
||||||
|
|
||||||
>in `balance/random.go`
|
|
||||||
|
|
||||||
RandomWeight Algorithm also selects a remote address randomly to process the request. But the higher
|
|
||||||
the weight, the greater the probability.
|
|
||||||
|
|
||||||
### RoundRobin
|
|
||||||
|
|
||||||
>in `balance/roundrobin.go`
|
|
||||||
|
|
||||||
RoundRobin Algorithm selects a remote address orderly. Every remote address have the same
|
|
||||||
probability to be selected.
|
|
||||||
|
|
||||||
### RoundRobinWeight
|
|
||||||
|
|
||||||
>in `balance/roundrobin.go`
|
|
||||||
|
|
||||||
RoundRobinWeight Algorthm selects a remote address orderly. But every remote address have different
|
|
||||||
probability to be selected which are determined by their weight.
|
|
||||||
|
|
||||||
### Hash
|
|
||||||
|
|
||||||
//TODO
|
|
||||||
|
|
||||||
## Load Balancing Manager
|
|
||||||
|
|
||||||
### BalanceMgr
|
|
||||||
|
|
||||||
>in `balance/manager.go`
|
|
||||||
|
|
||||||
* addrs: the set of the remote full node addresses
|
|
||||||
* balancers: map the string of balancer name to the specific balancer
|
|
||||||
* change: record whether the machine reinitialize after the `addrs` changes
|
|
||||||
|
|
||||||
`BalanceMgr` is the manager of many balancer. It is the access of load balancing. Its main function
|
|
||||||
is to maintain the `NodeAddrs` and to call the specific load balancing algorithm above.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type BalanceMgr struct{
|
|
||||||
addrs NodeAddrs
|
|
||||||
balancers map[string]Balancer
|
|
||||||
change map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *BalanceMgr) RegisterBalancer(name string,balancer Balancer)
|
|
||||||
|
|
||||||
func (p *BalanceMgr) updateBalancer(name string)
|
|
||||||
|
|
||||||
func (p *BalanceMgr) AddNodeAddr(addr *NodeAddr)
|
|
||||||
|
|
||||||
func (p *BalanceMgr) DeleteNodeAddr(i int)
|
|
||||||
|
|
||||||
func (p *BalanceMgr) UpdateWeightNodeAddr(i int,weight int)
|
|
||||||
|
|
||||||
func (p *BalanceMgr) GetAddr(name string)(*NodeAddr,int,error) {
|
|
||||||
// if addrs change,update the balancer which we use.
|
|
||||||
if p.change[name]{
|
|
||||||
p.updateBalancer(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the balancer by name
|
|
||||||
balancer := p.balancers[name]
|
|
||||||
|
|
||||||
// use the load balancing algorithm
|
|
||||||
addr,index,err := balancer.DoBalance(p.addrs)
|
|
||||||
|
|
||||||
return addr,index,err
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* `RegisterBalancer`: register the basic balancer implementing the `Balancer` interface and initialize them.
|
|
||||||
* `updateBalancer`: update the specific balancer after the `addrs` change.
|
|
||||||
* `AddNodeAddr`: add the remote address and set all the values of the `change` to true.
|
|
||||||
* `DeleteNodeAddr`: delete the remote address and set all the values of the `change` to true.
|
|
||||||
* `UpdateWeightNodeAddr`: update the weight of the remote address and set all the values of the `change` to true.
|
|
||||||
* `GetAddr`:select the address by the balancer the `name` decides.
|
|
|
@ -1,16 +0,0 @@
|
||||||
# TODO
|
|
||||||
|
|
||||||
This document is a place to gather all points for future development.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
* finalise ICS0 - TendermintAPI
|
|
||||||
* make sure that the explorer and voyager can use it
|
|
||||||
* add ICS21 - StakingAPI
|
|
||||||
* add ICS22 - GovernanceAPI
|
|
||||||
* split Gaia Light into reusable components that other zones can leverage
|
|
||||||
* it should be possible to register extra standards on the light client
|
|
||||||
* the setup should be similar to how the app is currently started
|
|
||||||
* implement Gaia light and the general light client in Rust
|
|
||||||
* export the API as a C interface
|
|
||||||
* write thin wrappers around the C interface in JS, Swift and Kotlin/Java
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
To start a REST server, we need to specify the following parameters:
|
To start a REST server, we need to specify the following parameters:
|
||||||
|
|
||||||
| Parameter | Type | Default | Required | Description |
|
| Parameter | Type | Default | Required | Description |
|
||||||
| ----------- | --------- | ----------------------- | -------- | ---------------------------------------------------- |
|
| ----------- | --------- | ----------------------- | -------- | ---------------------------------------------------- |
|
||||||
| chain-id | string | null | true | chain id of the full node to connect |
|
| chain-id | string | null | true | chain id of the full node to connect |
|
||||||
|
@ -9,12 +10,12 @@ To start a REST server, we need to specify the following parameters:
|
||||||
| trust-node | bool | "false" | true | Whether this LCD is connected to a trusted full node |
|
| trust-node | bool | "false" | true | Whether this LCD is connected to a trusted full node |
|
||||||
| trust-store | DIRECTORY | "$HOME/.lcd" | false | directory for save checkpoints and validator sets |
|
| trust-store | DIRECTORY | "$HOME/.lcd" | false | directory for save checkpoints and validator sets |
|
||||||
|
|
||||||
Sample command:
|
For example::
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gaiacli rest-server --chain-id=test \
|
gaiacli rest-server --chain-id=test \
|
||||||
--laddr=tcp://localhost:1317 \
|
--laddr=tcp://localhost:1317 \
|
||||||
--node tcp://localhost:46657 \
|
--node tcp://localhost:26657 \
|
||||||
--trust-node=false
|
--trust-node=false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ The server listens on HTTPS by default. You can set the SSL certificate to be us
|
||||||
```bash
|
```bash
|
||||||
gaiacli rest-server --chain-id=test \
|
gaiacli rest-server --chain-id=test \
|
||||||
--laddr=tcp://localhost:1317 \
|
--laddr=tcp://localhost:1317 \
|
||||||
--node tcp://localhost:46657 \
|
--node tcp://localhost:26657 \
|
||||||
--trust-node=false \
|
--trust-node=false \
|
||||||
--certfile=mycert.pem --keyfile=mykey.key
|
--certfile=mycert.pem --keyfile=mykey.key
|
||||||
```
|
```
|
||||||
|
@ -31,26 +32,4 @@ gaiacli rest-server --chain-id=test \
|
||||||
If no certificate/keyfile pair is supplied, a self-signed certificate will be generated and its fingerprint printed out.
|
If no certificate/keyfile pair is supplied, a self-signed certificate will be generated and its fingerprint printed out.
|
||||||
Append `--insecure` to the command line if you want to disable the secure layer and listen on an insecure HTTP port.
|
Append `--insecure` to the command line if you want to disable the secure layer and listen on an insecure HTTP port.
|
||||||
|
|
||||||
## Gaia Light Use Cases
|
For more information about the Gaia-Lite RPC, see the [swagger documentation](https://cosmos.network/rpc/)
|
||||||
|
|
||||||
LCD could be very helpful for related service providers. For a wallet service provider, LCD could
|
|
||||||
make transaction faster and more reliable in the following cases.
|
|
||||||
|
|
||||||
### Create an account
|
|
||||||
|
|
||||||
![deposit](pics/create-account.png)
|
|
||||||
|
|
||||||
First you need to get a new seed phrase :[get-seed](api.md#keysseed---get)
|
|
||||||
|
|
||||||
After having new seed, you could generate a new account with it : [keys](api.md#keys---post)
|
|
||||||
|
|
||||||
### Transfer a token
|
|
||||||
|
|
||||||
![transfer](pics/transfer-tokens.png)
|
|
||||||
|
|
||||||
The first step is to build an asset transfer transaction. Here we can post all necessary parameters
|
|
||||||
to /create_transfer to get the unsigned transaction byte array. Refer to this link for detailed
|
|
||||||
operation: [build transaction](api.md#create_transfer---post)
|
|
||||||
|
|
||||||
Then sign the returned transaction byte array with users' private key. Finally broadcast the signed
|
|
||||||
transaction. Refer to this link for how to broadcast the signed transaction: [broadcast transaction](api.md#create_transfer---post)
|
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
@ -1,30 +1,20 @@
|
||||||
# Cosmos-Sdk Light Client
|
# Overview
|
||||||
|
|
||||||
|
**See the Cosmos SDK lite Client RPC documentation [here](https://cosmos.network/rpc/)**
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
A light client allows clients, such as mobile phones, to receive proofs of the state of the
|
A lite client allows clients, such as mobile phones, to receive proofs of the state of the
|
||||||
blockchain from any full node. Light clients do not have to trust any full node, since they are able
|
blockchain from any full node. lite clients do not have to trust any full node, since they are able
|
||||||
to verify any proof they receive and hence full nodes cannot lie about the state of the network.
|
to verify any proof they receive and hence full nodes cannot lie about the state of the network.
|
||||||
|
|
||||||
A light client can provide the same security as a full node with the minimal requirements on
|
A lite client can provide the same security as a full node with the minimal requirements on
|
||||||
bandwidth, computing and storage resource. Besides, it can also provide modular functionality
|
bandwidth, computing and storage resource. As well, it can also provide modular functionality
|
||||||
according to users' configuration. These fantastic features allow developers to build fully secure,
|
according to users' configuration. These fantastic features allow developers to build fully secure,
|
||||||
efficient and usable mobile apps, websites or any other applications without deploying or
|
efficient and usable mobile apps, websites or any other applications without deploying or
|
||||||
maintaining any full blockchain nodes.
|
maintaining any full blockchain nodes.
|
||||||
|
|
||||||
LCD will be used in the Cosmos Hub, the first Hub in the Cosmos network.
|
### What is a lite Client
|
||||||
|
|
||||||
## Contents
|
|
||||||
|
|
||||||
1. [**Overview**](##Overview)
|
|
||||||
2. [**Get Started**](getting_started.md)
|
|
||||||
3. [**API**](api.md)
|
|
||||||
4. [**Specifications**](specification.md)
|
|
||||||
4. [**Update API docs To Swagger-UI**](update_API_docs.md)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
### What is a Light Client
|
|
||||||
|
|
||||||
The LCD is split into two separate components. The first component is generic for any Tendermint
|
The LCD is split into two separate components. The first component is generic for any Tendermint
|
||||||
based application. It handles the security and connectivity aspects of following the header chain
|
based application. It handles the security and connectivity aspects of following the header chain
|
||||||
|
@ -54,7 +44,7 @@ that offers stability guarantees around the zone API.
|
||||||
|
|
||||||
### Comparision
|
### Comparision
|
||||||
|
|
||||||
A full node of ABCI is different from its light client in the following ways:
|
A full node of ABCI is different from its lite client in the following ways:
|
||||||
|
|
||||||
|| Full Node | LCD | Description|
|
|| Full Node | LCD | Description|
|
||||||
|-| ------------- | ----- | -------------- |
|
|-| ------------- | ----- | -------------- |
|
||||||
|
@ -71,22 +61,22 @@ A full node of ABCI is different from its light client in the following ways:
|
||||||
According to the above table, LCD can meet all users' functionality and security requirements, but
|
According to the above table, LCD can meet all users' functionality and security requirements, but
|
||||||
only requires little resource on bandwidth, computing, storage and power.
|
only requires little resource on bandwidth, computing, storage and power.
|
||||||
|
|
||||||
## How does LCD achieve high security?
|
## Achieving Security
|
||||||
|
|
||||||
### Trusted validator set
|
### Trusted Validator Set
|
||||||
|
|
||||||
The base design philosophy of lcd follows the two rules:
|
The base design philosophy of the LCD follows two rules:
|
||||||
|
|
||||||
1. **Doesn't trust any blockchain nodes, including validator nodes and other full nodes**
|
1. **Doesn't trust any blockchain nodes, including validator nodes and other full nodes**
|
||||||
2. **Only trusts the whole validator set**
|
2. **Only trusts the whole validator set**
|
||||||
|
|
||||||
The original trusted validator set should be prepositioned into its trust store, usually this
|
The original trusted validator set should be prepositioned into its trust store, usually this
|
||||||
validator set comes from genesis file. During running time, if LCD detects different validator set,
|
validator set comes from genesis file. During runtime, if LCD detects a different validator set,
|
||||||
it will verify it and save new validated validator set to trust store.
|
it will verify it and save new validated validator set to the trust store.
|
||||||
|
|
||||||
![validator-set-change](pics/validatorSetChange.png)
|
![validator-set-change](pics/validatorSetChange.png)
|
||||||
|
|
||||||
### Trust propagation
|
### Trust Propagation
|
||||||
|
|
||||||
From the above section, we come to know how to get trusted validator set and how lcd keeps track of
|
From the above section, we come to know how to get trusted validator set and how lcd keeps track of
|
||||||
validator set evolution. Validator set is the foundation of trust, and the trust can propagate to
|
validator set evolution. Validator set is the foundation of trust, and the trust can propagate to
|
||||||
|
@ -97,5 +87,4 @@ follows:
|
||||||
|
|
||||||
In general, by trusted validator set, LCD can verify each block commit which contains all pre-commit
|
In general, by trusted validator set, LCD can verify each block commit which contains all pre-commit
|
||||||
data and block header data. Then the block hash, data hash and appHash are trusted. Based on this
|
data and block header data. Then the block hash, data hash and appHash are trusted. Based on this
|
||||||
and merkle proof, all transactions data and ABCI states can be verified too. Detailed implementation
|
and merkle proof, all transactions data and ABCI states can be verified too.
|
||||||
will be posted on technical specification.
|
|
|
@ -207,150 +207,3 @@ For instance:
|
||||||
* Update to 10000,tooMuchChangeErr
|
* Update to 10000,tooMuchChangeErr
|
||||||
* Update to 7525, Success
|
* Update to 7525, Success
|
||||||
* Update to 10000, Success
|
* Update to 10000, Success
|
||||||
|
|
||||||
## Load Balancing
|
|
||||||
|
|
||||||
To improve LCD reliability and TPS, we recommend to connect LCD to more than one fullnode. But the
|
|
||||||
complexity will increase a lot. So load balancing module will be imported as the adapter. Please
|
|
||||||
refer to this link for detailed description: [load balancer](load_balancer.md)
|
|
||||||
|
|
||||||
## ICS1 (KeyAPI)
|
|
||||||
|
|
||||||
### [/keys - GET](api.md#keys---get)
|
|
||||||
|
|
||||||
Load the key store:
|
|
||||||
|
|
||||||
```go
|
|
||||||
db, err := dbm.NewGoLevelDB(KeyDBName, filepath.Join(rootDir, "keys"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
keybase = client.GetKeyBase(db)
|
|
||||||
```
|
|
||||||
|
|
||||||
Iterate through the key store.
|
|
||||||
|
|
||||||
```go
|
|
||||||
var res []Info
|
|
||||||
iter := kb.db.Iterator(nil, nil)
|
|
||||||
defer iter.Close()
|
|
||||||
|
|
||||||
for ; iter.Valid(); iter.Next() {
|
|
||||||
// key := iter.Key()
|
|
||||||
info, err := readInfo(iter.Value())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res = append(res, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
```
|
|
||||||
|
|
||||||
Encode the addresses and public keys in bech32.
|
|
||||||
|
|
||||||
```go
|
|
||||||
bechAccount, err := sdk.Bech32ifyAcc(sdk.Address(info.PubKey.Address().Bytes()))
|
|
||||||
if err != nil {
|
|
||||||
return KeyOutput{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bechPubKey, err := sdk.Bech32ifyAccPub(info.PubKey)
|
|
||||||
if err != nil {
|
|
||||||
return KeyOutput{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return KeyOutput{
|
|
||||||
Name: info.Name,
|
|
||||||
Address: bechAccount,
|
|
||||||
PubKey: bechPubKey,
|
|
||||||
}, nil
|
|
||||||
```
|
|
||||||
|
|
||||||
### [/keys/recover - POST](api.md#keys/recover---get)
|
|
||||||
|
|
||||||
1. Load the key store.
|
|
||||||
2. Parameter checking. Name, password and seed should not be empty.
|
|
||||||
3. Check for keys with the same name.
|
|
||||||
4. Build the key from the name, password and seed.
|
|
||||||
5. Persist the key to key store.
|
|
||||||
|
|
||||||
### [/keys/create - GET](api.md#keys/create---get)**
|
|
||||||
|
|
||||||
1. Load the key store.
|
|
||||||
2. Create a new key in the key store.
|
|
||||||
3. Save the key to disk.
|
|
||||||
4. Return the seed.
|
|
||||||
|
|
||||||
### [/keys/{name} - GET](api.md#keysname---get)
|
|
||||||
|
|
||||||
1. Load the key store.
|
|
||||||
2. Iterate the whole key store to find the key by name.
|
|
||||||
3. Encode address and public key in bech32.
|
|
||||||
|
|
||||||
### [/keys/{name} - PUT](api.md#keysname---put)
|
|
||||||
|
|
||||||
1. Load the key store.
|
|
||||||
2. Iterate the whole key store to find the key by name.
|
|
||||||
3. Verify if that the old-password matches the current key password.
|
|
||||||
4. Re-persist the key with the new password.
|
|
||||||
|
|
||||||
### [/keys/{name} - DELETE](api.md#keysname---delete)
|
|
||||||
|
|
||||||
1. Load the key store.
|
|
||||||
2. Iterate the whole key store to find the key by name.
|
|
||||||
3. Verify that the specified password matches the current key password.
|
|
||||||
4. Delete the key from the key store.
|
|
||||||
|
|
||||||
## ICS20 (TokenAPI)
|
|
||||||
|
|
||||||
### [/bank/balance/{account}](api.md#bankbalanceaccount---get)
|
|
||||||
|
|
||||||
1. Decode the address from bech32 to hex.
|
|
||||||
2. Send a query request to a full node. Ask for proof if required by Gaia Light.
|
|
||||||
3. Verify the proof against the root of trust.
|
|
||||||
|
|
||||||
### [/bank/create_transfer](api.md#bankcreate_transfer---post)
|
|
||||||
|
|
||||||
1. Check the parameters.
|
|
||||||
2. Build the transaction with the specified parameters.
|
|
||||||
3. Serialise the transaction and return the JSON encoded sign bytes.
|
|
||||||
|
|
||||||
## ICS21 (StakingAPI)
|
|
||||||
|
|
||||||
### [/stake/delegators/{delegatorAddr}](api.md#stakedelegatorsdelegatorAddr---get)
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### [/stake/delegators/{delegatorAddr}/validators](api.md#stakedelegatorsdelegatorAddrvalidators---get)
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### [/stake/delegators/{delegatorAddr}/validators/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrvalidatorsvalidatorAddr---get)
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### [/stake/delegators/{delegatorAddr}/txs](api.md#stakedelegatorsdelegatorAddrtxs---get)
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### [/stake/delegators/{delegatorAddr}/delegations](api.md#stakedelegatorsdelegatorAddrdelegations---post)
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### [/stake/delegators/{delegatorAddr}/delegations/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrdelegationsvalidatorAddr---get)
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### [/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrunbonding_delegationsvalidatorAddr---get)
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### [/stake/validators](api.md#stakevalidators---get)
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### [/stake/validators/{validatorAddr}](api.md#stakevalidatorsvalidatorAddr---get)
|
|
||||||
|
|
||||||
TODO
|
|
|
@ -2,6 +2,8 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
)
|
)
|
||||||
|
@ -34,6 +36,24 @@ func MustSortJSON(toSortJSON []byte) []byte {
|
||||||
return js
|
return js
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Slight modification of the RFC3339Nano but it right pads all zeros and drops the time zone info
|
||||||
|
const SortableTimeFormat = "2006-01-02T15:04:05.000000000"
|
||||||
|
|
||||||
|
// Formats a time.Time into a []byte that can be sorted
|
||||||
|
func FormatTimeBytes(t time.Time) []byte {
|
||||||
|
return []byte(t.UTC().Round(0).Format(SortableTimeFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a []byte encoded using FormatTimeKey back into a time.Time
|
||||||
|
func ParseTimeBytes(bz []byte) (time.Time, error) {
|
||||||
|
str := string(bz)
|
||||||
|
t, err := time.Parse(SortableTimeFormat, str)
|
||||||
|
if err != nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
return t.UTC().Round(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultChainID returns the chain ID from the genesis file if present. An
|
// DefaultChainID returns the chain ID from the genesis file if present. An
|
||||||
// error is returned if the file cannot be read or parsed.
|
// error is returned if the file cannot be read or parsed.
|
||||||
//
|
//
|
||||||
|
|
|
@ -2,6 +2,7 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -43,3 +44,23 @@ func TestSortJSON(t *testing.T) {
|
||||||
require.Equal(t, string(got), tc.want)
|
require.Equal(t, string(got), tc.want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimeFormatAndParse(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
RFC3339NanoStr string
|
||||||
|
SDKSortableTimeStr string
|
||||||
|
Equal bool
|
||||||
|
}{
|
||||||
|
{"2009-11-10T23:00:00Z", "2009-11-10T23:00:00.000000000", true},
|
||||||
|
{"2011-01-10T23:10:05.758230235Z", "2011-01-10T23:10:05.758230235", true},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
timeFromRFC, err := time.Parse(time.RFC3339Nano, tc.RFC3339NanoStr)
|
||||||
|
require.Nil(t, err)
|
||||||
|
timeFromSDKFormat, err := time.Parse(SortableTimeFormat, tc.SDKSortableTimeStr)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
require.True(t, timeFromRFC.Equal(timeFromSDKFormat))
|
||||||
|
require.Equal(t, timeFromRFC.Format(SortableTimeFormat), tc.SDKSortableTimeStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -84,9 +84,9 @@ func (msg MsgSubmitProposal) GetSigners() []sdk.AccAddress {
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
// MsgDeposit
|
// MsgDeposit
|
||||||
type MsgDeposit struct {
|
type MsgDeposit struct {
|
||||||
ProposalID int64 `json:"proposalID"` // ID of the proposal
|
ProposalID int64 `json:"proposal_id"` // ID of the proposal
|
||||||
Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer
|
Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer
|
||||||
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
|
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMsgDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins) MsgDeposit {
|
func NewMsgDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins) MsgDeposit {
|
||||||
|
@ -145,9 +145,9 @@ func (msg MsgDeposit) GetSigners() []sdk.AccAddress {
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
// MsgVote
|
// MsgVote
|
||||||
type MsgVote struct {
|
type MsgVote struct {
|
||||||
ProposalID int64 // proposalID of the proposal
|
ProposalID int64 `json:"proposal_id"` // ID of the proposal
|
||||||
Voter sdk.AccAddress // address of the voter
|
Voter sdk.AccAddress `json:"voter"` // address of the voter
|
||||||
Option VoteOption // option from OptionSet chosen by the voter
|
Option VoteOption `json:"option"` // option from OptionSet chosen by the voter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMsgVote(voter sdk.AccAddress, proposalID int64, option VoteOption) MsgVote {
|
func NewMsgVote(voter sdk.AccAddress, proposalID int64, option VoteOption) MsgVote {
|
||||||
|
|
|
@ -68,10 +68,10 @@ type TextProposal struct {
|
||||||
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
|
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
|
||||||
TallyResult TallyResult `json:"tally_result"` // Result of Tallys
|
TallyResult TallyResult `json:"tally_result"` // Result of Tallys
|
||||||
|
|
||||||
SubmitTime time.Time `json:"submit_block"` // Height of the block where TxGovSubmitProposal was included
|
SubmitTime time.Time `json:"submit_time"` // Height of the block where TxGovSubmitProposal was included
|
||||||
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
|
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
|
||||||
|
|
||||||
VotingStartTime time.Time `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
|
VotingStartTime time.Time `json:"voting_start_time"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements Proposal Interface
|
// Implements Proposal Interface
|
||||||
|
|
|
@ -96,7 +96,7 @@ func getValidatorPowerRank(validator types.Validator) []byte {
|
||||||
|
|
||||||
// gets the prefix for all unbonding delegations from a delegator
|
// gets the prefix for all unbonding delegations from a delegator
|
||||||
func GetValidatorQueueTimeKey(timestamp time.Time) []byte {
|
func GetValidatorQueueTimeKey(timestamp time.Time) []byte {
|
||||||
bz := types.MsgCdc.MustMarshalBinary(timestamp)
|
bz := sdk.FormatTimeBytes(timestamp)
|
||||||
return append(ValidatorQueueKey, bz...)
|
return append(ValidatorQueueKey, bz...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ func GetUBDsByValIndexKey(valAddr sdk.ValAddress) []byte {
|
||||||
|
|
||||||
// gets the prefix for all unbonding delegations from a delegator
|
// gets the prefix for all unbonding delegations from a delegator
|
||||||
func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte {
|
func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte {
|
||||||
bz := types.MsgCdc.MustMarshalBinary(timestamp)
|
bz := sdk.FormatTimeBytes(timestamp)
|
||||||
return append(UnbondingQueueKey, bz...)
|
return append(UnbondingQueueKey, bz...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte {
|
||||||
|
|
||||||
// gets the prefix for all unbonding delegations from a delegator
|
// gets the prefix for all unbonding delegations from a delegator
|
||||||
func GetRedelegationTimeKey(timestamp time.Time) []byte {
|
func GetRedelegationTimeKey(timestamp time.Time) []byte {
|
||||||
bz, _ := timestamp.MarshalBinary()
|
bz := sdk.FormatTimeBytes(timestamp)
|
||||||
return append(RedelegationQueueKey, bz...)
|
return append(RedelegationQueueKey, bz...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|