diff --git a/Gopkg.lock b/Gopkg.lock index 169893877..3817fd238 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -48,12 +48,19 @@ revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[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" packages = ["spew"] pruneopts = "UT" - revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" - version = "v1.1.1" + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" [[projects]] digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b" @@ -390,8 +397,7 @@ version = "v1.2.1" [[projects]] - branch = "master" - digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5" + digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -408,7 +414,7 @@ "leveldb/util", ] pruneopts = "UT" - revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd" + revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" [[projects]] digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f" @@ -524,10 +530,10 @@ version = "v0.1.0" [[projects]] - branch = "master" - digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a" + digest = "1:aaff04fa01d9b824fde6799759cc597b3ac3671b9ad31924c28b6557d0ee5284" name = "golang.org/x/crypto" packages = [ + "bcrypt", "blowfish", "chacha20poly1305", "curve25519", @@ -544,7 +550,8 @@ "salsa20/salsa", ] pruneopts = "UT" - revision = "e3636079e1a4c1f337f212cc5cd2aca108f6c900" + revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" + source = "https://github.com/tendermint/crypto" [[projects]] digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" @@ -563,15 +570,14 @@ revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" [[projects]] - branch = "master" - digest = "1:8bc8ecef1d63576cfab4d08b44a1f255dd67e5b019b7a44837d62380f266a91c" + digest = "1:4bd75b1a219bc590b05c976bbebf47f4e993314ebb5c7cbf2efe05a09a184d54" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "e4b3c5e9061176387e7cea65e4dc5853801f3fb7" + revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -597,12 +603,11 @@ version = "v0.3.0" [[projects]] - branch = "master" - digest = "1:1e6b0176e8c5dd8ff551af65c76f8b73a99bcf4d812cedff1b91711b7df4804c" + digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "UT" - revision = "c7e5094acea1ca1b899e2259d80a6b0f882f81f8" + revision = "383e8b2c3b9e36c4076b235b32537292176bae20" [[projects]] digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" @@ -653,6 +658,7 @@ "github.com/bartekn/go-bip39", "github.com/bgentry/speakeasy", "github.com/btcsuite/btcd/btcec", + "github.com/cosmos/go-bip39", "github.com/golang/protobuf/proto", "github.com/gorilla/mux", "github.com/mattn/go-isatty", @@ -699,7 +705,7 @@ "github.com/tendermint/tendermint/types", "github.com/tendermint/tendermint/version", "github.com/zondax/ledger-goclient", - "golang.org/x/crypto/blowfish", + "golang.org/x/crypto/bcrypt", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index b63234f46..05140857d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -59,22 +59,51 @@ name = "github.com/tendermint/tendermint" version = "=0.25.0" +## deps without releases: + [[constraint]] - name = "github.com/bartekn/go-bip39" - revision = "a05967ea095d81c8fe4833776774cfaff8e5036c" + name = "golang.org/x/crypto" + source = "https://github.com/tendermint/crypto" + revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" + +[[constraint]] + name = "github.com/cosmos/go-bip39" + revision = "52158e4697b87de16ed390e1bdaf813e581008fa" [[constraint]] name = "github.com/zondax/ledger-goclient" version = "=v0.1.0" +## transitive deps, with releases: + +[[override]] + name = "github.com/davecgh/go-spew" + version = "=v1.1.0" + [[constraint]] name = "github.com/rakyll/statik" 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] go-tests = true unused-packages = true -[[constraint]] - name = "github.com/mitchellh/go-homedir" - version = "1.0.0" diff --git a/PENDING.md b/PENDING.md index d6d645798..1b29082fe 100644 --- a/PENDING.md +++ b/PENDING.md @@ -142,6 +142,7 @@ IMPROVEMENTS * Gaia REST API (`gaiacli advanced rest-server`) * [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`) * [cli] #2060 removed `--select` from `block` command diff --git a/client/input.go b/client/input.go index a456f1b92..46c838e2e 100644 --- a/client/input.go +++ b/client/input.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/bgentry/speakeasy" - isatty "github.com/mattn/go-isatty" + "github.com/mattn/go-isatty" "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 // leading/trailing spaces -func GetSeed(prompt string, buf *bufio.Reader) (seed string, err error) { - if inputIsTty() { - fmt.Println(prompt) - } - seed, err = readLineFromBuf(buf) - seed = strings.TrimSpace(seed) - return +func GetSeed(prompt string, buf *bufio.Reader) (string, error) { + return GetString(prompt, buf) } // 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. func PrintPrefixed(msg string) { - fmt.Printf("> %s\n", msg) + msg = fmt.Sprintf("> %s\n", msg) + fmt.Fprint(os.Stderr, msg) } diff --git a/client/keys/mnemonic.go b/client/keys/mnemonic.go new file mode 100644 index 000000000..33270a087 --- /dev/null +++ b/client/keys/mnemonic.go @@ -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 +} diff --git a/client/keys/new.go b/client/keys/new.go new file mode 100644 index 000000000..3408eb9d0 --- /dev/null +++ b/client/keys/new.go @@ -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("-------------------------------------") +} diff --git a/client/keys/root.go b/client/keys/root.go index a7a7d2e6f..b10cd2b55 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -19,6 +19,8 @@ func Commands() *cobra.Command { needs to sign with a private key.`, } cmd.AddCommand( + mnemonicKeyCommand(), + newKeyCommand(), addKeyCommand(), listKeysCmd, showKeysCmd(), diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index f675afe14..7aea16d86 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -10,20 +10,20 @@ import ( "testing" "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/stretchr/testify/assert" "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" client "github.com/cosmos/cosmos-sdk/client" 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" + cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" version "github.com/cosmos/cosmos-sdk/version" @@ -35,7 +35,7 @@ import ( ) func init() { - cryptoKeys.BcryptSecurityParameter = 1 + mintkey.BcryptSecurityParameter = 1 version.Version = os.Getenv("VERSION") } @@ -717,55 +717,53 @@ func TestUnjail(t *testing.T) { } func TestProposalsQuery(t *testing.T) { - name, password1 := "test", "1234567890" - name2, password2 := "test2", "1234567890" - addr, seed := CreateAddr(t, "test", password1, GetKeyBase(t)) - addr2, seed2 := CreateAddr(t, "test2", password2, GetKeyBase(t)) - cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr, addr2}) + addrs, seeds, names, passwords := CreateAddrs(t, GetKeyBase(t), 2) + + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addrs[0], addrs[1]}) defer cleanup() // 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 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1) 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 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2) tests.WaitForHeight(resultTx.Height+1, port) // 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 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // 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) - 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) // check deposits match proposal and individual deposits deposits := getDeposits(t, port, proposalID1) require.Len(t, deposits, 1) - deposit := getDeposit(t, port, proposalID1, addr) + deposit := getDeposit(t, port, proposalID1, addrs[0]) require.Equal(t, deposit, deposits[0]) deposits = getDeposits(t, port, proposalID2) require.Len(t, deposits, 2) - deposit = getDeposit(t, port, proposalID2, addr) - require.Equal(t, deposit, deposits[0]) - deposit = getDeposit(t, port, proposalID2, addr2) - require.Equal(t, deposit, deposits[1]) + deposit = getDeposit(t, port, proposalID2, addrs[0]) + require.True(t, deposit.Equals(deposits[0])) + deposit = getDeposit(t, port, proposalID2, addrs[1]) + require.True(t, deposit.Equals(deposits[1])) deposits = getDeposits(t, port, proposalID3) require.Len(t, deposits, 1) - deposit = getDeposit(t, port, proposalID3, addr2) + deposit = getDeposit(t, port, proposalID3, addrs[1]) require.Equal(t, deposit, deposits[0]) // 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) deposits = getDeposits(t, port, proposalID1) @@ -782,13 +780,13 @@ func TestProposalsQuery(t *testing.T) { require.Equal(t, proposalID3, proposals[1].GetProposalID()) // 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) - 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) // 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) // Test query all proposals @@ -798,37 +796,37 @@ func TestProposalsQuery(t *testing.T) { require.Equal(t, proposalID3, (proposals[2]).GetProposalID()) // Test query deposited by addr1 - proposals = getProposalsFilterDepositer(t, port, addr) + proposals = getProposalsFilterDepositer(t, port, addrs[0]) require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) // 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, proposalID3, (proposals[1]).GetProposalID()) // 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, proposalID3, (proposals[1]).GetProposalID()) // Test query voted by addr2 - proposals = getProposalsFilterVoter(t, port, addr2) + proposals = getProposalsFilterVoter(t, port, addrs[1]) require.Equal(t, proposalID3, (proposals[0]).GetProposalID()) // 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()) // Test query votes on Proposal 2 votes := getVotes(t, port, proposalID2) 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 votes = getVotes(t, port, proposalID3) require.Len(t, votes, 2) - require.True(t, addr.String() == votes[0].Voter.String() || addr.String() == votes[1].Voter.String()) - require.True(t, addr2.String() == votes[0].Voter.String() || addr2.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, addrs[1].String() == votes[0].Voter.String() || addrs[1].String() == votes[1].Voter.String()) } //_____________________________________________________________________________ diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index b7fb70ff3..cde32050d 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -400,6 +400,7 @@ paths: description: 16 word Seed schema: 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: post: summary: Recover a account from a seed diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index cd48b89ea..3585023cd 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "path/filepath" + "sort" "strings" "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 } +// 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 // their respective sockets where nValidators is the total number of validators // and initAddrs are the accounts to initialize with some steak tokens. It diff --git a/client/utils/rest.go b/client/utils/rest.go index 07b258ce9..effb7bb73 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -1,6 +1,5 @@ package utils -import "C" import ( "fmt" "io/ioutil" diff --git a/crypto/keys/bcrypt/base64.go b/crypto/keys/bcrypt/base64.go deleted file mode 100644 index fc3116090..000000000 --- a/crypto/keys/bcrypt/base64.go +++ /dev/null @@ -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 -} diff --git a/crypto/keys/bcrypt/bcrypt.go b/crypto/keys/bcrypt/bcrypt.go deleted file mode 100644 index e24120bfb..000000000 --- a/crypto/keys/bcrypt/bcrypt.go +++ /dev/null @@ -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 -} diff --git a/crypto/keys/bip39/wordcodec.go b/crypto/keys/bip39/wordcodec.go deleted file mode 100644 index 074d1393c..000000000 --- a/crypto/keys/bip39/wordcodec.go +++ /dev/null @@ -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 -} diff --git a/crypto/keys/bip39/wordcodec_test.go b/crypto/keys/bip39/wordcodec_test.go deleted file mode 100644 index a821239d7..000000000 --- a/crypto/keys/bip39/wordcodec_test.go +++ /dev/null @@ -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") -} diff --git a/crypto/keys/hd/fundraiser_test.go b/crypto/keys/hd/fundraiser_test.go index 84de09758..5e3cf06f3 100644 --- a/crypto/keys/hd/fundraiser_test.go +++ b/crypto/keys/hd/fundraiser_test.go @@ -7,9 +7,10 @@ import ( "io/ioutil" "testing" - "github.com/bartekn/go-bip39" "github.com/stretchr/testify/require" + "github.com/cosmos/go-bip39" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/secp256k1" ) diff --git a/crypto/keys/hd/hdpath.go b/crypto/keys/hd/hdpath.go index ef2e6f783..112abe0b6 100644 --- a/crypto/keys/hd/hdpath.go +++ b/crypto/keys/hd/hdpath.go @@ -22,7 +22,6 @@ import ( "strings" "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. @@ -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: // m / 44' / 118' / account' / 0 / address_index // 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 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 { var changeStr string if p.change { @@ -128,10 +213,15 @@ func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, h data = append([]byte{byte(0)}, privKeyBytes[:]...) } else { // 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) data = public[:] + */ } data = append(data, uint32ToBytes(index)...) data2, chainCode2 := i64(chainCode[:], data) diff --git a/crypto/keys/hd/hdpath_test.go b/crypto/keys/hd/hdpath_test.go index 58398655f..f310fc355 100644 --- a/crypto/keys/hd/hdpath_test.go +++ b/crypto/keys/hd/hdpath_test.go @@ -3,9 +3,20 @@ package hd import ( "encoding/hex" "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 func ExampleStringifyPathParams() { path := NewParams(44, 0, 0, false, 0) @@ -13,10 +24,57 @@ func ExampleStringifyPathParams() { // 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 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") master, ch := ComputeMastersFromSeed(seed) 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() - seed = bip39.MnemonicToSeed( + seed = mnemonicToSeed( "advice process birth april short trust crater change bacon monkey medal garment " + "gorilla ranch hour rival razor call lunar mention taste vacant woman sister") master, ch = ComputeMastersFromSeed(seed) priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") 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") master, ch = ComputeMastersFromSeed(seed) priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") @@ -53,7 +111,7 @@ func ExampleSomeBIP32TestVecs() { fmt.Println() // 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) priv, _ = DerivePrivateKeyForPath(master, ch, "0/7") fmt.Println(hex.EncodeToString(priv[:])) diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 99632e764..ddcd0357e 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -6,11 +6,15 @@ import ( "os" "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/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" "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/secp256k1" @@ -46,6 +50,14 @@ const ( infoSuffix = "info" ) +const ( + // used for deriving seed from mnemonic + defaultBIP39Passphrase = "" + + // bits of entropy to draw when creating a mnemonic + defaultEntropySize = 256 +) + var ( // ErrUnsupportedSigningAlgo is raised when the caller tries to use a // different signing scheme than secp256k1. @@ -85,12 +97,17 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string } // 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 { return } - mnemonic = strings.Join(mnemonicS, " ") - seed := bip39.MnemonicToSeed(mnemonic) + mnemonic, err = bip39.NewMnemonic(entropy) + if err != nil { + return + } + + seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase) info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) 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)) return } - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase) if err != nil { 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)) return } - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase) if err != nil { return } @@ -127,12 +144,12 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf return } -func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) { - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) +func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) { + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) if err != nil { return } - info, err = kb.persistDerivedKey(seed, passwd, name, params.String()) + info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String()) 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") return } - priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return nil, nil, err } @@ -279,7 +296,7 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr err = fmt.Errorf("private key not available") return nil, err } - priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return nil, err } @@ -296,7 +313,7 @@ func (kb dbKeybase) Export(name string) (armor string, err error) { if bz == nil { 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. @@ -311,7 +328,7 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { if err != nil { return } - return armorPubKeyBytes(info.GetPubKey().Bytes()), nil + return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil } 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 { return errors.New("Cannot overwrite data for name " + name) } - infoBytes, err := unarmorInfoBytes(armor) + infoBytes, err := mintkey.UnarmorInfoBytes(armor) if err != nil { return } @@ -335,7 +352,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { if len(bz) > 0 { return errors.New("Cannot overwrite data for name " + name) } - pubBytes, err := unarmorPubKeyBytes(armor) + pubBytes, err := mintkey.UnarmorPubKeyBytes(armor) if err != nil { return } @@ -360,7 +377,7 @@ func (kb dbKeybase) Delete(name, passphrase string) error { switch info.(type) { case localInfo: linfo := info.(localInfo) - _, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return err } @@ -394,7 +411,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro switch info.(type) { case localInfo: linfo := info.(localInfo) - key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) + key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) if err != nil { 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 { // encrypt private key using passphrase - privArmor := encryptArmorPrivKey(priv, passphrase) + privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase) // make Info pub := priv.PubKey() info := newLocalInfo(name, pub, privArmor) diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 3273c229a..cafa2382a 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -4,9 +4,12 @@ import ( "fmt" "testing" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/stretchr/testify/assert" "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/ed25519" @@ -15,7 +18,7 @@ import ( ) func init() { - BcryptSecurityParameter = 1 + mintkey.BcryptSecurityParameter = 1 } // 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 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.Equal(t, n2, newInfo.GetName()) require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) diff --git a/crypto/keys/mintkey.go b/crypto/keys/mintkey/mintkey.go similarity index 79% rename from crypto/keys/mintkey.go rename to crypto/keys/mintkey/mintkey.go index 70e1bc44e..80377920f 100644 --- a/crypto/keys/mintkey.go +++ b/crypto/keys/mintkey/mintkey.go @@ -1,16 +1,17 @@ -package keys +package mintkey import ( "encoding/hex" "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/armor" "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/xsalsa20symmetric" + + cmn "github.com/tendermint/tendermint/libs/common" ) const ( @@ -34,11 +35,16 @@ const ( // TODO: Consider increasing default var BcryptSecurityParameter = 12 -func armorInfoBytes(bz []byte) string { +//----------------------------------------------------------------- +// add armor + +// Armor the InfoBytes +func ArmorInfoBytes(bz []byte) string { return armorBytes(bz, blockTypeKeyInfo) } -func armorPubKeyBytes(bz []byte) string { +// Armor the PubKeyBytes +func ArmorPubKeyBytes(bz []byte) string { return armorBytes(bz, blockTypePubKey) } @@ -50,11 +56,16 @@ func armorBytes(bz []byte, blockType string) string { 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) } -func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) { +// Unarmor the PubKeyBytes +func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) { return unarmorBytes(armorStr, blockTypePubKey) } @@ -74,7 +85,11 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, err error) { 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) header := map[string]string{ "kdf": "bcrypt", @@ -84,7 +99,22 @@ func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { 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 blockType, header, encBytes, err := armor.DecodeArmor(armorStr) if err != nil { @@ -107,17 +137,6 @@ func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, 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) { key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) if err != nil { diff --git a/crypto/keys/types.go b/crypto/keys/types.go index c5e97d5fb..f5194748a 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -26,8 +26,12 @@ type Keybase interface { CreateKey(name, mnemonic, passwd string) (info Info, err error) // CreateFundraiserKey takes a mnemonic and derives, a password CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) - // Derive derives a key from the passed mnemonic using a BIP44 path. - Derive(name, mnemonic, passwd string, params hd.BIP44Params) (Info, error) + // Compute a BIP39 seed from th mnemonic and bip39Passwd. + // Derive private key from the seed using the BIP44 params. + // Encrypt the key to disk using encryptPasswd. + // See https://github.com/cosmos/cosmos-sdk/issues/2095 + Derive(name, mnemonic, bip39Passwd, + encryptPasswd string, params hd.BIP44Params) (Info, error) // Create, store, and return a new Ledger key reference CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 7925697f5..5f09b5bb0 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,6 +1,7 @@ module.exports = { - title: "Cosmos Network", + title: "Cosmos Documentation", description: "Documentation for the Cosmos Network.", + ga: "UA-51029217-2", dest: "./dist/docs", base: "/docs/", markdown: { @@ -67,6 +68,14 @@ module.exports = { "/sdk/sdk-by-examples/simple-governance/running-the-application" ] }, + { + title: "Light Client", + collapsable: false, + children: [ + "/light/", + "/light/getting_started" + ] + }, { title: "Lotion JS", collapsable: false, diff --git a/docs/PRIORITIES.md b/docs/PRIORITIES.md index 20eb20e1a..02aa495bb 100644 --- a/docs/PRIORITIES.md +++ b/docs/PRIORITIES.md @@ -1,13 +1,8 @@ # 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) -- 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 +- [Prelaunch Issues](https://github.com/cosmos/cosmos-sdk/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Astaking+label%3Aprelaunch-2.0) ## Multisig @@ -16,24 +11,29 @@ ## 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 -- CheckEvidence/DeliverEvidence, CheckTx/DeliverTx ordering semantics ## Gas -- Charge for transaction size -- Decide what "one gas" corresponds to (standard hardware benchmarks?) -- More benchmarking -- Consider charging based on maximum depth of IAVL tree iteration +- Write specification and explainer document for Gas in Cosmos + * Charge for transaction size + * Decide what "one gas" corresponds to (standard hardware benchmarks?) + * Consider charging based on maximum depth of IAVL tree iteration - Test out gas estimation in CLI and LCD and ensure the UX works ## LCD -- Bianje working on implementation of ICS standards -- Additional PR incoming for ICS 22 and ICS 23 -- Decide what ought to be ICS-standardized and what ought not to +- Bianje working with Voyager team (@fedekunze) to complete implementation and documentation. +## Documentation + +- gaiad / gaiacli +- LCD +- Each module +- Tags [#1780](https://github.com/cosmos/cosmos-sdk/issues/1780) # Lower priority ## Governance v2 @@ -41,6 +41,6 @@ - Circuit breaker - https://github.com/cosmos/cosmos-sdk/issues/926 - 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 diff --git a/docs/light/api.md b/docs/light/api.md deleted file mode 100644 index 7fbf9fbe1..000000000 --- a/docs/light/api.md +++ /dev/null @@ -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:" - } -} -``` - -## 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. - -- 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" - } -} -``` diff --git a/docs/light/load_balancer.md b/docs/light/load_balancer.md deleted file mode 100644 index 0cc280827..000000000 --- a/docs/light/load_balancer.md +++ /dev/null @@ -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. diff --git a/docs/light/todo.md b/docs/light/todo.md deleted file mode 100644 index ce1f8508a..000000000 --- a/docs/light/todo.md +++ /dev/null @@ -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 diff --git a/docs/light/getting_started.md b/docs/lite/getting_started.md similarity index 59% rename from docs/light/getting_started.md rename to docs/lite/getting_started.md index 21497477a..b602bcc47 100644 --- a/docs/light/getting_started.md +++ b/docs/lite/getting_started.md @@ -1,6 +1,7 @@ # Getting Started To start a REST server, we need to specify the following parameters: + | Parameter | Type | Default | Required | Description | | ----------- | --------- | ----------------------- | -------- | ---------------------------------------------------- | | 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-store | DIRECTORY | "$HOME/.lcd" | false | directory for save checkpoints and validator sets | -Sample command: +For example:: ```bash gaiacli rest-server --chain-id=test \ --laddr=tcp://localhost:1317 \ - --node tcp://localhost:46657 \ + --node tcp://localhost:26657 \ --trust-node=false ``` @@ -23,7 +24,7 @@ The server listens on HTTPS by default. You can set the SSL certificate to be us ```bash gaiacli rest-server --chain-id=test \ --laddr=tcp://localhost:1317 \ - --node tcp://localhost:46657 \ + --node tcp://localhost:26657 \ --trust-node=false \ --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. 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 - -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) +For more information about the Gaia-Lite RPC, see the [swagger documentation](https://cosmos.network/rpc/) diff --git a/docs/light/pics/C2H.png b/docs/lite/pics/C2H.png similarity index 100% rename from docs/light/pics/C2H.png rename to docs/lite/pics/C2H.png diff --git a/docs/light/pics/H2C.png b/docs/lite/pics/H2C.png similarity index 100% rename from docs/light/pics/H2C.png rename to docs/lite/pics/H2C.png diff --git a/docs/light/pics/MA.png b/docs/lite/pics/MA.png similarity index 100% rename from docs/light/pics/MA.png rename to docs/lite/pics/MA.png diff --git a/docs/light/pics/absence1.png b/docs/lite/pics/absence1.png similarity index 100% rename from docs/light/pics/absence1.png rename to docs/lite/pics/absence1.png diff --git a/docs/light/pics/absence2.png b/docs/lite/pics/absence2.png similarity index 100% rename from docs/light/pics/absence2.png rename to docs/lite/pics/absence2.png diff --git a/docs/light/pics/absence3.png b/docs/lite/pics/absence3.png similarity index 100% rename from docs/light/pics/absence3.png rename to docs/lite/pics/absence3.png diff --git a/docs/light/pics/architecture.png b/docs/lite/pics/architecture.png similarity index 100% rename from docs/light/pics/architecture.png rename to docs/lite/pics/architecture.png diff --git a/docs/light/pics/changeProcess.png b/docs/lite/pics/changeProcess.png similarity index 100% rename from docs/light/pics/changeProcess.png rename to docs/lite/pics/changeProcess.png diff --git a/docs/light/pics/commitValidation.png b/docs/lite/pics/commitValidation.png similarity index 100% rename from docs/light/pics/commitValidation.png rename to docs/lite/pics/commitValidation.png diff --git a/docs/light/pics/create-account.png b/docs/lite/pics/create-account.png similarity index 100% rename from docs/light/pics/create-account.png rename to docs/lite/pics/create-account.png diff --git a/docs/light/pics/deposit.png b/docs/lite/pics/deposit.png similarity index 100% rename from docs/light/pics/deposit.png rename to docs/lite/pics/deposit.png diff --git a/docs/light/pics/existProof.png b/docs/lite/pics/existProof.png similarity index 100% rename from docs/light/pics/existProof.png rename to docs/lite/pics/existProof.png diff --git a/docs/light/pics/high-level.png b/docs/lite/pics/high-level.png similarity index 100% rename from docs/light/pics/high-level.png rename to docs/lite/pics/high-level.png diff --git a/docs/light/pics/light-client-architecture.png b/docs/lite/pics/light-client-architecture.png similarity index 100% rename from docs/light/pics/light-client-architecture.png rename to docs/lite/pics/light-client-architecture.png diff --git a/docs/light/pics/loadbalanceDiagram.png b/docs/lite/pics/loadbalanceDiagram.png similarity index 100% rename from docs/light/pics/loadbalanceDiagram.png rename to docs/lite/pics/loadbalanceDiagram.png diff --git a/docs/light/pics/simpleMerkleTree.png b/docs/lite/pics/simpleMerkleTree.png similarity index 100% rename from docs/light/pics/simpleMerkleTree.png rename to docs/lite/pics/simpleMerkleTree.png diff --git a/docs/light/pics/substoreProof.png b/docs/lite/pics/substoreProof.png similarity index 100% rename from docs/light/pics/substoreProof.png rename to docs/lite/pics/substoreProof.png diff --git a/docs/light/pics/transfer-tokens.png b/docs/lite/pics/transfer-tokens.png similarity index 100% rename from docs/light/pics/transfer-tokens.png rename to docs/lite/pics/transfer-tokens.png diff --git a/docs/light/pics/transfer.png b/docs/lite/pics/transfer.png similarity index 100% rename from docs/light/pics/transfer.png rename to docs/lite/pics/transfer.png diff --git a/docs/light/pics/trustPropagate.png b/docs/lite/pics/trustPropagate.png similarity index 100% rename from docs/light/pics/trustPropagate.png rename to docs/lite/pics/trustPropagate.png diff --git a/docs/light/pics/updateValidatorToHeight.png b/docs/lite/pics/updateValidatorToHeight.png similarity index 100% rename from docs/light/pics/updateValidatorToHeight.png rename to docs/lite/pics/updateValidatorToHeight.png diff --git a/docs/light/pics/validatorSetChange.png b/docs/lite/pics/validatorSetChange.png similarity index 100% rename from docs/light/pics/validatorSetChange.png rename to docs/lite/pics/validatorSetChange.png diff --git a/docs/light/pics/withdraw.png b/docs/lite/pics/withdraw.png similarity index 100% rename from docs/light/pics/withdraw.png rename to docs/lite/pics/withdraw.png diff --git a/docs/light/readme.md b/docs/lite/readme.md similarity index 81% rename from docs/light/readme.md rename to docs/lite/readme.md index 55e0c72ac..215bc25f7 100644 --- a/docs/light/readme.md +++ b/docs/lite/readme.md @@ -1,30 +1,20 @@ -# Cosmos-Sdk Light Client +# Overview + +**See the Cosmos SDK lite Client RPC documentation [here](https://cosmos.network/rpc/)** ## Introduction -A light 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 +A lite client allows clients, such as mobile phones, to receive proofs of the state of the +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. -A light 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 +A lite client can provide the same security as a full node with the minimal requirements on +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, efficient and usable mobile apps, websites or any other applications without deploying or maintaining any full blockchain nodes. -LCD will be used in the Cosmos Hub, the first Hub in the Cosmos network. - -## 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 +### What is a lite Client 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 @@ -54,7 +44,7 @@ that offers stability guarantees around the zone API. ### 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| |-| ------------- | ----- | -------------- | @@ -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 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** 2. **Only trusts the whole validator set** 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, -it will verify it and save new validated validator set to trust store. +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 the trust store. ![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 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 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 -will be posted on technical specification. +and merkle proof, all transactions data and ABCI states can be verified too. diff --git a/docs/light/specification.md b/docs/lite/specification.md similarity index 67% rename from docs/light/specification.md rename to docs/lite/specification.md index 15f36b014..4feac508c 100644 --- a/docs/light/specification.md +++ b/docs/lite/specification.md @@ -207,150 +207,3 @@ For instance: * Update to 10000,tooMuchChangeErr * Update to 7525, 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 diff --git a/types/utils.go b/types/utils.go index b196acb23..10ec85472 100644 --- a/types/utils.go +++ b/types/utils.go @@ -2,6 +2,8 @@ package types import ( "encoding/json" + "time" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tmtypes "github.com/tendermint/tendermint/types" ) @@ -34,6 +36,24 @@ func MustSortJSON(toSortJSON []byte) []byte { 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 // error is returned if the file cannot be read or parsed. // diff --git a/types/utils_test.go b/types/utils_test.go index 05bc622e7..dbdd08c0a 100644 --- a/types/utils_test.go +++ b/types/utils_test.go @@ -2,6 +2,7 @@ package types import ( "testing" + "time" "github.com/stretchr/testify/require" ) @@ -43,3 +44,23 @@ func TestSortJSON(t *testing.T) { 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) + } +} diff --git a/x/gov/msgs.go b/x/gov/msgs.go index a5a68ea21..ac2b2c170 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -84,9 +84,9 @@ func (msg MsgSubmitProposal) GetSigners() []sdk.AccAddress { //----------------------------------------------------------- // MsgDeposit type MsgDeposit struct { - ProposalID int64 `json:"proposalID"` // ID of the proposal - Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer - Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit + ProposalID int64 `json:"proposal_id"` // ID of the proposal + Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer + Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } func NewMsgDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins) MsgDeposit { @@ -145,9 +145,9 @@ func (msg MsgDeposit) GetSigners() []sdk.AccAddress { //----------------------------------------------------------- // MsgVote type MsgVote struct { - ProposalID int64 // proposalID of the proposal - Voter sdk.AccAddress // address of the voter - Option VoteOption // option from OptionSet chosen by the voter + ProposalID int64 `json:"proposal_id"` // ID of the proposal + Voter sdk.AccAddress `json:"voter"` // address of the voter + Option VoteOption `json:"option"` // option from OptionSet chosen by the voter } func NewMsgVote(voter sdk.AccAddress, proposalID int64, option VoteOption) MsgVote { diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 37e29df70..9d1ba860a 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -68,10 +68,10 @@ type TextProposal struct { Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} 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 - 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 diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index aef685cb9..d5f2fc9d9 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -96,7 +96,7 @@ func getValidatorPowerRank(validator types.Validator) []byte { // gets the prefix for all unbonding delegations from a delegator func GetValidatorQueueTimeKey(timestamp time.Time) []byte { - bz := types.MsgCdc.MustMarshalBinary(timestamp) + bz := sdk.FormatTimeBytes(timestamp) return append(ValidatorQueueKey, bz...) } @@ -154,7 +154,7 @@ func GetUBDsByValIndexKey(valAddr sdk.ValAddress) []byte { // gets the prefix for all unbonding delegations from a delegator func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte { - bz := types.MsgCdc.MustMarshalBinary(timestamp) + bz := sdk.FormatTimeBytes(timestamp) return append(UnbondingQueueKey, bz...) } @@ -228,7 +228,7 @@ func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte { // gets the prefix for all unbonding delegations from a delegator func GetRedelegationTimeKey(timestamp time.Time) []byte { - bz, _ := timestamp.MarshalBinary() + bz := sdk.FormatTimeBytes(timestamp) return append(RedelegationQueueKey, bz...) }