Browse Source

Merge pull request #19 from tendermint/feature/cli-improvements

cli improvements
pull/1782/head
Ethan Frey 8 years ago
committed by GitHub
parent
commit
8bdb5ceda4
35 changed files with 9679 additions and 140 deletions
  1. +1
    -0
      .gitignore
  2. +20
    -5
      Makefile
  3. +1
    -1
      circle.yml
  4. +49
    -0
      cmd/delete.go
  5. +13
    -15
      cmd/get.go
  6. +3
    -0
      cmd/keys/main.go
  7. +7
    -9
      cmd/list.go
  8. +46
    -8
      cmd/new.go
  9. +53
    -0
      cmd/recover.go
  10. +11
    -19
      cmd/root.go
  11. +13
    -9
      cmd/serve.go
  12. +3
    -8
      cmd/update.go
  13. +72
    -6
      cmd/utils.go
  14. +5
    -10
      glide.lock
  15. +0
    -22
      keys/Makefile
  16. +41
    -7
      keys/cryptostore/holder.go
  17. +41
    -3
      keys/cryptostore/holder_test.go
  18. +141
    -0
      keys/ecc.go
  19. +65
    -0
      keys/ecc_test.go
  20. +3
    -2
      keys/server/keys.go
  21. +11
    -8
      keys/server/keys_test.go
  22. +7
    -0
      keys/server/types/keys.go
  23. +4
    -1
      keys/transactions.go
  24. +3
    -2
      keys/tx/multi_test.go
  25. +3
    -2
      keys/tx/one_test.go
  26. +5
    -3
      keys/tx/reader_test.go
  27. +199
    -0
      keys/wordcodec.go
  28. +180
    -0
      keys/wordcodec_test.go
  29. +68
    -0
      keys/wordcodecbench_test.go
  30. +2048
    -0
      keys/wordlist/chinese_simplified.txt
  31. +2048
    -0
      keys/wordlist/english.txt
  32. +2048
    -0
      keys/wordlist/japanese.txt
  33. +2048
    -0
      keys/wordlist/spanish.txt
  34. +308
    -0
      keys/wordlist/wordlist.go
  35. +111
    -0
      tests/keys.sh

+ 1
- 0
.gitignore View File

@ -1,3 +1,4 @@
*.swp
*.swo
vendor
shunit2

+ 20
- 5
Makefile View File

@ -1,20 +1,32 @@
.PHONEY: all docs test install get_vendor_deps ensure_tools codegen
.PHONEY: all docs test install get_vendor_deps ensure_tools codegen wordlist
GOTOOLS = \
github.com/Masterminds/glide
github.com/Masterminds/glide \
github.com/jteeuwen/go-bindata/go-bindata
REPO:=github.com/tendermint/go-crypto
docs:
@go get github.com/davecheney/godoc2md
godoc2md $(REPO) > README.md
all: install test
all: get_vendor_deps install test
install:
go install ./cmd/keys
test:
test: test_unit test_cli
test_unit:
go test `glide novendor`
#go run tests/tendermint/*.go
test_cli: tests/shunit2
# sudo apt-get install jq
@./tests/keys.sh
tests/shunit2:
wget "https://raw.githubusercontent.com/kward/shunit2/master/source/2.1/src/shunit2" \
-q -O tests/shunit2
get_vendor_deps: ensure_tools
@rm -rf vendor/
@ -24,13 +36,16 @@ get_vendor_deps: ensure_tools
ensure_tools:
go get $(GOTOOLS)
wordlist:
go-bindata -ignore ".*\.go" -o keys/wordlist/wordlist.go -pkg "wordlist" keys/wordlist/...
prepgen: install
go install ./vendor/github.com/btcsuite/btcutil/base58
go install ./vendor/github.com/stretchr/testify/assert
go install ./vendor/github.com/stretchr/testify/require
go install ./vendor/golang.org/x/crypto/bcrypt
codegen:
codegen:
@echo "--> regenerating all interface wrappers"
@gen
@echo "Done!"

+ 1
- 1
circle.yml View File

@ -18,4 +18,4 @@ dependencies:
test:
override:
- "go version"
- "cd $PROJECT_PATH && make get_vendor_deps && make test"
- "cd $PROJECT_PATH && make all"

+ 49
- 0
cmd/delete.go View File

@ -0,0 +1,49 @@
// Copyright © 2017 Ethan Frey
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// deleteCmd represents the delete command
var deleteCmd = &cobra.Command{
Use: "delete [name]",
Short: "DANGER: Delete a private key from your system",
RunE: runDeleteCmd,
}
func runDeleteCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
oldpass, err := getPassword("DANGER - enter password to permanently delete key:")
if err != nil {
return err
}
err = GetKeyManager().Delete(name, oldpass)
if err != nil {
return err
}
fmt.Println("Password deleted forever (uh oh!)")
return nil
}

+ 13
- 15
cmd/get.go View File

@ -22,23 +22,21 @@ import (
// getCmd represents the get command
var getCmd = &cobra.Command{
Use: "get <name>",
Use: "get [name]",
Short: "Get details of one key",
Long: `Return public details of one local key.`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
info, err := GetKeyManager().Get(name)
if err == nil {
printInfo(info)
}
return err
},
RunE: runGetCmd,
}
func init() {
RootCmd.AddCommand(getCmd)
func runGetCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
info, err := GetKeyManager().Get(name)
if err == nil {
printInfo(info)
}
return err
}

+ 3
- 0
cmd/keys/main.go View File

@ -22,6 +22,9 @@ import (
)
func main() {
// for demos, we enable the key server, probably don't want this
// in most binaries we embed the key management into
cmd.RegisterServer()
root := cli.PrepareMainCmd(cmd.RootCmd, "TM", os.ExpandEnv("$HOME/.tlc"))
root.Execute()
}

+ 7
- 9
cmd/list.go View File

@ -22,15 +22,13 @@ var listCmd = &cobra.Command{
Short: "List all keys",
Long: `Return a list of all public keys stored by this key manager
along with their associated name and address.`,
RunE: func(cmd *cobra.Command, args []string) error {
infos, err := GetKeyManager().List()
if err == nil {
printInfos(infos)
}
return err
},
RunE: runListCmd,
}
func init() {
RootCmd.AddCommand(listCmd)
func runListCmd(cmd *cobra.Command, args []string) error {
infos, err := GetKeyManager().List()
if err == nil {
printInfos(infos)
}
return err
}

+ 46
- 8
cmd/new.go View File

@ -15,42 +15,80 @@
package cmd
import (
"fmt"
"github.com/pkg/errors"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
flagType = "type"
flagNoBackup = "no-backup"
)
// newCmd represents the new command
var newCmd = &cobra.Command{
Use: "new <name>",
Use: "new [name]",
Short: "Create a new public/private key pair",
Long: `Add a public/private key pair to the key store.
The password muts be entered in the terminal and not
passed as a command line argument for security.`,
RunE: newPassword,
RunE: runNewCmd,
}
func init() {
RootCmd.AddCommand(newCmd)
newCmd.Flags().StringP("type", "t", "ed25519", "Type of key (ed25519|secp256k1)")
newCmd.Flags().StringP(flagType, "t", "ed25519", "Type of key (ed25519|secp256k1)")
newCmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
}
func newPassword(cmd *cobra.Command, args []string) error {
func runNewCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
algo := viper.GetString("type")
algo := viper.GetString(flagType)
pass, err := getCheckPassword("Enter a passphrase:", "Repeat the passphrase:")
if err != nil {
return err
}
info, err := GetKeyManager().Create(name, pass, algo)
info, seed, err := GetKeyManager().Create(name, pass, algo)
if err == nil {
printInfo(info)
printCreate(info, seed)
}
return err
}
type NewOutput struct {
Key keys.Info `json:"key"`
Seed string `json:"seed"`
}
func printCreate(info keys.Info, seed string) {
switch viper.Get(cli.OutputFlag) {
case "text":
printInfo(info)
// print seed unless requested not to.
if !viper.GetBool(flagNoBackup) {
fmt.Println("**Important** write this seed phrase in a safe place.")
fmt.Println("It is the only way to recover your account if you ever forget your password.\n")
fmt.Println(seed)
}
case "json":
out := NewOutput{Key: info}
if !viper.GetBool(flagNoBackup) {
out.Seed = seed
}
json, err := data.ToJSON(out)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}

+ 53
- 0
cmd/recover.go View File

@ -0,0 +1,53 @@
// Copyright © 2017 Ethan Frey
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// recoverCmd represents the recover command
var recoverCmd = &cobra.Command{
Use: "recover [name]",
Short: "Change the password for a private key",
RunE: runRecoverCmd,
}
func runRecoverCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
pass, err := getPassword("Enter the new passphrase:")
if err != nil {
return err
}
// not really a password... huh?
seed, err := getSeed("Enter your recovery seed phrase:")
if err != nil {
return err
}
info, err := GetKeyManager().Recover(name, pass, seed)
if err != nil {
return err
}
printInfo(info)
return nil
}

+ 11
- 19
cmd/root.go View File

@ -15,14 +15,8 @@
package cmd
import (
"path/filepath"
"github.com/spf13/cobra"
"github.com/spf13/viper"
keys "github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/filestorage"
"github.com/tendermint/tmlibs/cli"
)
const KeySubdir = "keys"
@ -42,17 +36,15 @@ used by light-clients, full nodes, or any other application that
needs to sign with a private key.`,
}
// GetKeyManager initializes a key manager based on the configuration
func GetKeyManager() keys.Manager {
if manager == nil {
// store the keys directory
rootDir := viper.GetString(cli.HomeFlag)
keyDir := filepath.Join(rootDir, KeySubdir)
// and construct the key manager
manager = cryptostore.New(
cryptostore.SecretBox,
filestorage.New(keyDir),
)
}
return manager
func init() {
RootCmd.AddCommand(getCmd)
RootCmd.AddCommand(listCmd)
RootCmd.AddCommand(newCmd)
RootCmd.AddCommand(updateCmd)
RootCmd.AddCommand(deleteCmd)
RootCmd.AddCommand(recoverCmd)
}
func RegisterServer() {
RootCmd.AddCommand(serveCmd)
}

+ 13
- 9
cmd/serve.go View File

@ -28,6 +28,11 @@ import (
"github.com/tendermint/go-crypto/keys/server"
)
const (
flagPort = "port"
flagSocket = "socket"
)
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
@ -36,27 +41,26 @@ var serveCmd = &cobra.Command{
private keys much more in depth than the cli can perform.
In particular, this will allow you to sign transactions with
the private keys in the store.`,
RunE: serveHTTP,
RunE: runServeCmd,
}
func init() {
RootCmd.AddCommand(serveCmd)
serveCmd.Flags().IntP("port", "p", 8118, "TCP Port for listen for http server")
serveCmd.Flags().StringP("socket", "s", "", "UNIX socket for more secure http server")
serveCmd.Flags().StringP("type", "t", "ed25519", "Default key type (ed25519|secp256k1)")
serveCmd.Flags().IntP(flagPort, "p", 8118, "TCP Port for listen for http server")
serveCmd.Flags().StringP(flagSocket, "s", "", "UNIX socket for more secure http server")
serveCmd.Flags().StringP(flagType, "t", "ed25519", "Default key type (ed25519|secp256k1)")
}
func serveHTTP(cmd *cobra.Command, args []string) error {
func runServeCmd(cmd *cobra.Command, args []string) error {
var l net.Listener
var err error
socket := viper.GetString("socket")
socket := viper.GetString(flagSocket)
if socket != "" {
l, err = createSocket(socket)
if err != nil {
return errors.Wrap(err, "Cannot create socket")
}
} else {
port := viper.GetInt("port")
port := viper.GetInt(flagPort)
l, err = net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return errors.Errorf("Cannot listen on port %d", port)
@ -64,7 +68,7 @@ func serveHTTP(cmd *cobra.Command, args []string) error {
}
router := mux.NewRouter()
ks := server.New(GetKeyManager(), viper.GetString("type"))
ks := server.New(GetKeyManager(), viper.GetString(flagType))
ks.Register(router)
// only set cors for tcp listener


+ 3
- 8
cmd/update.go View File

@ -24,17 +24,12 @@ import (
// updateCmd represents the update command
var updateCmd = &cobra.Command{
Use: "update <name>",
Use: "update [name]",
Short: "Change the password for a private key",
Long: `Change the password for a private key.`,
RunE: updatePassword,
RunE: runUpdateCmd,
}
func init() {
RootCmd.AddCommand(updateCmd)
}
func updatePassword(cmd *cobra.Command, args []string) error {
func runUpdateCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}


+ 72
- 6
cmd/utils.go View File

@ -1,30 +1,96 @@
package cmd
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/bgentry/speakeasy"
"github.com/mattn/go-isatty"
"github.com/pkg/errors"
"github.com/spf13/viper"
keys "github.com/tendermint/go-crypto/keys"
data "github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/cli"
keys "github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/filestorage"
)
const PassLength = 10
const MinPassLength = 10
// GetKeyManager initializes a key manager based on the configuration
func GetKeyManager() keys.Manager {
if manager == nil {
// store the keys directory
rootDir := viper.GetString(cli.HomeFlag)
keyDir := filepath.Join(rootDir, KeySubdir)
// TODO: smarter loading??? with language and fallback?
codec := keys.MustLoadCodec("english")
// and construct the key manager
manager = cryptostore.New(
cryptostore.SecretBox,
filestorage.New(keyDir),
codec,
)
}
return manager
}
// if we read from non-tty, we just need to init the buffer reader once,
// in case we try to read multiple passwords (eg. update)
var buf *bufio.Reader
func inputIsTty() bool {
return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
}
func stdinPassword() (string, error) {
if buf == nil {
buf = bufio.NewReader(os.Stdin)
}
pass, err := buf.ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(pass), nil
}
func getPassword(prompt string) (string, error) {
pass, err := speakeasy.Ask(prompt)
func getPassword(prompt string) (pass string, err error) {
if inputIsTty() {
pass, err = speakeasy.Ask(prompt)
} else {
pass, err = stdinPassword()
}
if err != nil {
return "", err
}
if len(pass) < PassLength {
return "", errors.Errorf("Password must be at least %d characters", PassLength)
if len(pass) < MinPassLength {
return "", errors.Errorf("Password must be at least %d characters", MinPassLength)
}
return pass, nil
}
func getSeed(prompt string) (seed string, err error) {
if inputIsTty() {
fmt.Println(prompt)
}
seed, err = stdinPassword()
seed = strings.TrimSpace(seed)
return
}
func getCheckPassword(prompt, prompt2 string) (string, error) {
// simple read on no-tty
if !inputIsTty() {
return getPassword(prompt)
}
// TODO: own function???
pass, err := getPassword(prompt)
if err != nil {


+ 5
- 10
glide.lock View File

@ -1,5 +1,5 @@
hash: 3bcee9fbccf29d21217b24b6a83ec51e1514f37b2ae5d8718cf6c5df80f4fb2c
updated: 2017-05-15T09:40:53.073691731-04:00
updated: 2017-06-19T17:16:58.037568333+02:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
@ -17,8 +17,6 @@ imports:
- hdkeychain
- name: github.com/btcsuite/fastsha256
version: 637e656429416087660c84436a2a035d69d54e2e
- name: github.com/clipperhouse/typewriter
version: c1a48da378ebb7db1db9f35981b5cc24bf2e5b85
- name: github.com/fsnotify/fsnotify
version: 4da3e2cfbabc9f751898f250b49f2439785783a1
- name: github.com/go-kit/kit
@ -60,6 +58,8 @@ imports:
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/magiconair/properties
version: 51463bfca2576e06c62a8504b5c0f06d61312647
- name: github.com/mattn/go-isatty
version: 9622e0cc9d8f9be434ca605520ff9a16808fee47
- name: github.com/mitchellh/mapstructure
version: cc8532a8e9a55ea36402aa21efdf403a60d34096
- name: github.com/pelletier/go-buffruneio
@ -88,12 +88,12 @@ imports:
- edwards25519
- extra25519
- name: github.com/tendermint/go-wire
version: 97beaedf0f4dbc035309157c92be3b30cc6e5d74
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
subpackages:
- data
- data/base58
- name: github.com/tendermint/tmlibs
version: 8f5a175ff4c869fedde710615a11f5745ff69bf3
version: bd9d0d1637dadf1330e167189d5e5031aadcda6f
subpackages:
- cli
- common
@ -119,11 +119,6 @@ imports:
subpackages:
- transform
- unicode/norm
- name: golang.org/x/tools
version: 144c6642b5d832d6c44a53dad6ee61665dd432ce
subpackages:
- go/ast/astutil
- imports
- name: gopkg.in/go-playground/validator.v9
version: 6d8c18553ea1ac493d049edd6f102f52e618f085
- name: gopkg.in/yaml.v2


+ 0
- 22
keys/Makefile View File

@ -1,22 +0,0 @@
GOTOOLS = \
github.com/mitchellh/gox \
github.com/Masterminds/glide
.PHONEY: all test install get_vendor_deps ensure_tools
all: install test
test:
go test `glide novendor`
install:
go install ./cmd/keys
get_vendor_deps: ensure_tools
@rm -rf vendor/
@echo "--> Running glide install"
@glide install
ensure_tools:
go get $(GOTOOLS)

+ 41
- 7
keys/cryptostore/holder.go View File

@ -1,19 +1,26 @@
package cryptostore
import keys "github.com/tendermint/go-crypto/keys"
import (
"strings"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
// Manager combines encyption and storage implementation to provide
// a full-featured key manager
type Manager struct {
es encryptedStorage
es encryptedStorage
codec keys.Codec
}
func New(coder Encoder, store keys.Storage) Manager {
func New(coder Encoder, store keys.Storage, codec keys.Codec) Manager {
return Manager{
es: encryptedStorage{
coder: coder,
store: store,
},
codec: codec,
}
}
@ -30,15 +37,42 @@ func (s Manager) assertKeyManager() keys.Manager {
// Create adds a new key to the storage engine, returning error if
// another key already stored under this name
//
// algo must be a supported go-crypto algorithm:
//
func (s Manager) Create(name, passphrase, algo string) (keys.Info, error) {
// algo must be a supported go-crypto algorithm: ed25519, secp256k1
func (s Manager) Create(name, passphrase, algo string) (keys.Info, string, error) {
gen, err := getGenerator(algo)
if err != nil {
return keys.Info{}, err
return keys.Info{}, "", err
}
key := gen.Generate()
err = s.es.Put(name, passphrase, key)
if err != nil {
return keys.Info{}, "", err
}
seed, err := s.codec.BytesToWords(key.Bytes())
phrase := strings.Join(seed, " ")
return info(name, key), phrase, err
}
// Recover takes a seed phrase and tries to recover the private key.
//
// If the seed phrase is valid, it will create the private key and store
// it under name, protected by passphrase.
//
// Result similar to New(), except it doesn't return the seed again...
func (s Manager) Recover(name, passphrase, seedphrase string) (keys.Info, error) {
words := strings.Split(strings.TrimSpace(seedphrase), " ")
data, err := s.codec.WordsToBytes(words)
if err != nil {
return keys.Info{}, err
}
key, err := crypto.PrivKeyFromBytes(data)
if err != nil {
return keys.Info{}, err
}
// d00d, it worked! create the bugger....
err = s.es.Put(name, passphrase, key)
return info(name, key), err
}


+ 41
- 3
keys/cryptostore/holder_test.go View File

@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/memstorage"
)
@ -18,6 +19,7 @@ func TestKeyManagement(t *testing.T) {
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
algo := crypto.NameEd25519
@ -32,10 +34,10 @@ func TestKeyManagement(t *testing.T) {
// create some keys
_, err = cstore.Get(n1)
assert.NotNil(err)
i, err := cstore.Create(n1, p1, algo)
i, _, err := cstore.Create(n1, p1, algo)
require.Equal(n1, i.Name)
require.Nil(err)
_, err = cstore.Create(n2, p2, algo)
_, _, err = cstore.Create(n2, p2, algo)
require.Nil(err)
// we can get these keys
@ -154,6 +156,7 @@ func TestAdvancedKeyManagement(t *testing.T) {
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
algo := crypto.NameSecp256k1
@ -161,7 +164,7 @@ func TestAdvancedKeyManagement(t *testing.T) {
p1, p2, p3, pt := "1234", "foobar", "ding booms!", "really-secure!@#$"
// make sure key works with initial password
_, err := cstore.Create(n1, p1, algo)
_, _, err := cstore.Create(n1, p1, algo)
require.Nil(err, "%+v", err)
assertPassword(assert, cstore, n1, p1, p2)
@ -199,6 +202,41 @@ func TestAdvancedKeyManagement(t *testing.T) {
assertPassword(assert, cstore, n2, p3, pt)
}
// TestSeedPhrase verifies restoring from a seed phrase
func TestSeedPhrase(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// make the storage with reasonable defaults
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
algo := crypto.NameEd25519
n1, n2 := "lost-key", "found-again"
p1, p2 := "1234", "foobar"
// make sure key works with initial password
info, seed, err := cstore.Create(n1, p1, algo)
require.Nil(err, "%+v", err)
assert.Equal(n1, info.Name)
assert.NotEmpty(seed)
// now, let us delete this key
err = cstore.Delete(n1, p1)
require.Nil(err, "%+v", err)
_, err = cstore.Get(n1)
require.NotNil(err)
// let us re-create it from the seed-phrase
newInfo, err := cstore.Recover(n2, p2, seed)
require.Nil(err, "%+v", err)
assert.Equal(n2, newInfo.Name)
assert.Equal(info.Address, newInfo.Address)
assert.Equal(info.PubKey, newInfo.PubKey)
}
// func ExampleStore() {
// // Select the encryption and storage for your cryptostore
// cstore := cryptostore.New(


+ 141
- 0
keys/ecc.go View File

@ -0,0 +1,141 @@
package keys
import (
"encoding/binary"
"errors"
"hash/crc32"
"hash/crc64"
)
// ECC is used for anything that calculates an error-correcting code
type ECC interface {
// AddECC calculates an error-correcting code for the input
// returns an output with the code appended
AddECC([]byte) []byte
// CheckECC verifies if the ECC is proper on the input and returns
// the data with the code removed, or an error
CheckECC([]byte) ([]byte, error)
}
// NoECC is a no-op placeholder, kind of useless... except for tests
type NoECC struct{}
var _ ECC = NoECC{}
func (_ NoECC) AddECC(input []byte) []byte { return input }
func (_ NoECC) CheckECC(input []byte) ([]byte, error) { return input, nil }
// CRC32 does the ieee crc32 polynomial check
type CRC32 struct {
Poly uint32
table *crc32.Table
}
var _ ECC = &CRC32{}
func NewIEEECRC32() *CRC32 {
return &CRC32{Poly: crc32.IEEE}
}
func NewCastagnoliCRC32() *CRC32 {
return &CRC32{Poly: crc32.Castagnoli}
}
func NewKoopmanCRC32() *CRC32 {
return &CRC32{Poly: crc32.Koopman}
}
func (c *CRC32) AddECC(input []byte) []byte {
table := c.getTable()
// get crc and convert to some bytes...
crc := crc32.Checksum(input, table)
check := make([]byte, crc32.Size)
binary.BigEndian.PutUint32(check, crc)
// append it to the input
output := append(input, check...)
return output
}
func (c *CRC32) CheckECC(input []byte) ([]byte, error) {
table := c.getTable()
if len(input) <= crc32.Size {
return nil, errors.New("input too short, no checksum present")
}
cut := len(input) - crc32.Size
data, check := input[:cut], input[cut:]
crc := binary.BigEndian.Uint32(check)
calc := crc32.Checksum(data, table)
if crc != calc {
return nil, errors.New("Checksum does not match")
}
return data, nil
}
func (c *CRC32) getTable() *crc32.Table {
if c.table == nil {
if c.Poly == 0 {
c.Poly = crc32.IEEE
}
c.table = crc32.MakeTable(c.Poly)
}
return c.table
}
// CRC64 does the ieee crc64 polynomial check
type CRC64 struct {
Poly uint64
table *crc64.Table
}
var _ ECC = &CRC64{}
func NewISOCRC64() *CRC64 {
return &CRC64{Poly: crc64.ISO}
}
func NewECMACRC64() *CRC64 {
return &CRC64{Poly: crc64.ECMA}
}
func (c *CRC64) AddECC(input []byte) []byte {
table := c.getTable()
// get crc and convert to some bytes...
crc := crc64.Checksum(input, table)
check := make([]byte, crc64.Size)
binary.BigEndian.PutUint64(check, crc)
// append it to the input
output := append(input, check...)
return output
}
func (c *CRC64) CheckECC(input []byte) ([]byte, error) {
table := c.getTable()
if len(input) <= crc64.Size {
return nil, errors.New("input too short, no checksum present")
}
cut := len(input) - crc64.Size
data, check := input[:cut], input[cut:]
crc := binary.BigEndian.Uint64(check)
calc := crc64.Checksum(data, table)
if crc != calc {
return nil, errors.New("Checksum does not match")
}
return data, nil
}
func (c *CRC64) getTable() *crc64.Table {
if c.table == nil {
if c.Poly == 0 {
c.Poly = crc64.ISO
}
c.table = crc64.MakeTable(c.Poly)
}
return c.table
}

+ 65
- 0
keys/ecc_test.go View File

@ -0,0 +1,65 @@
package keys
import (
"testing"
"github.com/stretchr/testify/assert"
cmn "github.com/tendermint/tmlibs/common"
)
// TestECCPasses makes sure that the AddECC/CheckECC methods are symetric
func TestECCPasses(t *testing.T) {
assert := assert.New(t)
checks := []ECC{
NoECC{},
NewIEEECRC32(),
NewCastagnoliCRC32(),
NewKoopmanCRC32(),
NewISOCRC64(),
NewECMACRC64(),
}
for _, check := range checks {
for i := 0; i < 2000; i++ {
numBytes := cmn.RandInt()%60 + 1
data := cmn.RandBytes(numBytes)
checked := check.AddECC(data)
res, err := check.CheckECC(checked)
if assert.Nil(err, "%#v: %+v", check, err) {
assert.Equal(data, res, "%v", check)
}
}
}
}
// TestECCFails makes sure random data will (usually) fail the checksum
func TestECCFails(t *testing.T) {
assert := assert.New(t)
checks := []ECC{
NewIEEECRC32(),
NewCastagnoliCRC32(),
NewKoopmanCRC32(),
NewISOCRC64(),
NewECMACRC64(),
}
attempts := 2000
for _, check := range checks {
failed := 0
for i := 0; i < attempts; i++ {
numBytes := cmn.RandInt()%60 + 1
data := cmn.RandBytes(numBytes)
_, err := check.CheckECC(data)
if err != nil {
failed += 1
}
}
// we allow up to 1 falsely accepted checksums, as there are random matches
assert.InDelta(attempts, failed, 1, "%v", check)
}
}

+ 3
- 2
keys/server/keys.go View File

@ -31,13 +31,14 @@ func (k Keys) GenerateKey(w http.ResponseWriter, r *http.Request) {
return
}
key, err := k.manager.Create(req.Name, req.Passphrase, req.Algo)
key, seed, err := k.manager.Create(req.Name, req.Passphrase, req.Algo)
if err != nil {
writeError(w, err)
return
}
writeSuccess(w, &key)
res := types.CreateKeyResponse{key, seed}
writeSuccess(w, &res)
}
func (k Keys) GetKey(w http.ResponseWriter, r *http.Request) {


+ 11
- 8
keys/server/keys_test.go View File

@ -40,13 +40,15 @@ func TestKeyServer(t *testing.T) {
key, code, err := createKey(r, n1, p1, algo)
require.Nil(err, "%+v", err)
require.Equal(http.StatusOK, code)
require.Equal(key.Name, n1)
require.Equal(n1, key.Key.Name)
require.NotEmpty(n1, key.Seed)
// the other one works
key2, code, err := createKey(r, n2, p2, algo)
require.Nil(err, "%+v", err)
require.Equal(http.StatusOK, code)
require.Equal(key2.Name, n2)
require.Equal(key2.Key.Name, n2)
require.NotEmpty(n2, key.Seed)
// let's abstract this out a bit....
keys, code, err = listKeys(r)
@ -62,9 +64,9 @@ func TestKeyServer(t *testing.T) {
k, code, err := getKey(r, n1)
require.Nil(err, "%+v", err)
require.Equal(http.StatusOK, code)
assert.Equal(k.Name, n1)
assert.Equal(n1, k.Name)
assert.NotNil(k.Address)
assert.Equal(k.Address, key.Address)
assert.Equal(key.Key.Address, k.Address)
// delete with proper key
_, code, err = deleteKey(r, n1, p1)
@ -89,6 +91,7 @@ func setupServer() http.Handler {
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
// build your http server
@ -134,7 +137,7 @@ func getKey(h http.Handler, name string) (*keys.Info, int, error) {
return &data, rr.Code, err
}
func createKey(h http.Handler, name, passphrase, algo string) (*keys.Info, int, error) {
func createKey(h http.Handler, name, passphrase, algo string) (*types.CreateKeyResponse, int, error) {
rr := httptest.NewRecorder()
post := types.CreateKeyRequest{
Name: name,
@ -157,9 +160,9 @@ func createKey(h http.Handler, name, passphrase, algo string) (*keys.Info, int,
return nil, rr.Code, nil
}
data := keys.Info{}
err = json.Unmarshal(rr.Body.Bytes(), &data)
return &data, rr.Code, err
data := new(types.CreateKeyResponse)
err = json.Unmarshal(rr.Body.Bytes(), data)
return data, rr.Code, err
}
func deleteKey(h http.Handler, name, passphrase string) (*types.ErrorResponse, int, error) {


+ 7
- 0
keys/server/types/keys.go View File

@ -1,5 +1,7 @@
package types
import "github.com/tendermint/go-crypto/keys"
// CreateKeyRequest is sent to create a new key
type CreateKeyRequest struct {
Name string `json:"name" validate:"required,min=4,printascii"`
@ -26,3 +28,8 @@ type ErrorResponse struct {
Error string `json:"error"` // error message if Success is false
Code int `json:"code"` // error code if Success is false
}
type CreateKeyResponse struct {
Key keys.Info `json:"key"`
Seed string `json:"seed_phrase"`
}

+ 4
- 1
keys/transactions.go View File

@ -63,7 +63,10 @@ type Signer interface {
// Manager allows simple CRUD on a keystore, as an aid to signing
type Manager interface {
Signer
Create(name, passphrase, algo string) (Info, error)
// Create also returns a seed phrase for cold-storage
Create(name, passphrase, algo string) (Info, string, error)
// Recover takes a seedphrase and loads in the private key
Recover(name, passphrase, seedphrase string) (Info, error)
List() (Infos, error)
Get(name string) (Info, error)
Update(name, oldpass, newpass string) error


+ 3
- 2
keys/tx/multi_test.go View File

@ -18,13 +18,14 @@ func TestMultiSig(t *testing.T) {
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
n, p := "foo", "bar"
n2, p2 := "other", "thing"
acct, err := cstore.Create(n, p, algo)
acct, _, err := cstore.Create(n, p, algo)
require.Nil(err, "%+v", err)
acct2, err := cstore.Create(n2, p2, algo)
acct2, _, err := cstore.Create(n2, p2, algo)
require.Nil(err, "%+v", err)
type signer struct {


+ 3
- 2
keys/tx/one_test.go View File

@ -18,13 +18,14 @@ func TestOneSig(t *testing.T) {
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
n, p := "foo", "bar"
n2, p2 := "other", "thing"
acct, err := cstore.Create(n, p, algo)
acct, _, err := cstore.Create(n, p, algo)
require.Nil(err, "%+v", err)
acct2, err := cstore.Create(n2, p2, algo)
acct2, _, err := cstore.Create(n2, p2, algo)
require.Nil(err, "%+v", err)
cases := []struct {


+ 5
- 3
keys/tx/reader_test.go View File

@ -6,9 +6,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-wire/data"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/memstorage"
data "github.com/tendermint/go-wire/data"
)
func TestReader(t *testing.T) {
@ -18,14 +19,15 @@ func TestReader(t *testing.T) {
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
type sigs struct{ name, pass string }
u := sigs{"alice", "1234"}
u2 := sigs{"bob", "foobar"}
_, err := cstore.Create(u.name, u.pass, algo)
_, _, err := cstore.Create(u.name, u.pass, algo)
require.Nil(err, "%+v", err)
_, err = cstore.Create(u2.name, u2.pass, algo)
_, _, err = cstore.Create(u2.name, u2.pass, algo)
require.Nil(err, "%+v", err)
cases := []struct {


+ 199
- 0
keys/wordcodec.go View File

@ -0,0 +1,199 @@
package keys
import (
"math/big"
"strings"
"github.com/pkg/errors"
"github.com/tendermint/go-crypto/keys/wordlist"
)
const BankSize = 2048
// TODO: add error-checking codecs for invalid phrases
type Codec interface {
BytesToWords([]byte) ([]string, error)
WordsToBytes([]string) ([]byte, error)
}
type WordCodec struct {
words []string
bytes map[string]int
check ECC
}
var _ Codec = &WordCodec{}
func NewCodec(words []string) (codec *WordCodec, err error) {
if len(words) != BankSize {
return codec, errors.Errorf("Bank must have %d words, found %d", BankSize, len(words))
}
res := &WordCodec{
words: words,
// TODO: configure this outside???
check: NewIEEECRC32(),
}
return res, nil
}
// LoadCodec loads a pre-compiled language file
func LoadCodec(bank string) (codec *WordCodec, err error) {
words, err := loadBank(bank)
if err != nil {
return codec, err
}
return NewCodec(words)
}
// MustLoadCodec panics if word bank is missing, only for tests
func MustLoadCodec(bank string) *WordCodec {
codec, err := LoadCodec(bank)
if err != nil {
panic(err)
}
return codec
}
// loadBank opens a wordlist file and returns all words inside
func loadBank(bank string) ([]string, error) {
filename := "keys/wordlist/" + bank + ".txt"
words, err := wordlist.Asset(filename)
if err != nil {
return nil, err
}
wordsAll := strings.Split(strings.TrimSpace(string(words)), "\n")
return wordsAll, nil
}
// // TODO: read from go-bind assets
// func getData(filename string) (string, error) {
// f, err := os.Open(filename)
// if err != nil {
// return "", errors.WithStack(err)
// }
// defer f.Close()
// data, err := ioutil.ReadAll(f)
// if err != nil {
// return "", errors.WithStack(err)
// }
// return string(data), nil
// }
// given this many bytes, we will produce this many words
func wordlenFromBytes(numBytes int) int {
// 2048 words per bank, which is 2^11.
// 8 bits per byte, and we add +10 so it rounds up
return (8*numBytes + 10) / 11
}
// given this many words, we will produce this many bytes.
// sometimes there are two possibilities.
// if maybeShorter is true, then represents len OR len-1 bytes
func bytelenFromWords(numWords int) (length int, maybeShorter bool) {
// calculate the max number of complete bytes we could store in this word
length = 11 * numWords / 8
// if one less byte would also generate this length, set maybeShorter
if wordlenFromBytes(length-1) == numWords {
maybeShorter = true
}
return
}
// TODO: add checksum
func (c *WordCodec) BytesToWords(raw []byte) (words []string, err error) {
// always add a checksum to the data
data := c.check.AddECC(raw)
numWords := wordlenFromBytes(len(data))
n2048 := big.NewInt(2048)
nData := big.NewInt(0).SetBytes(data)
nRem := big.NewInt(0)
// Alternative, use condition "nData.BitLen() > 0"
// to allow for shorter words when data has leading 0's
for i := 0; i < numWords; i++ {
nData.DivMod(nData, n2048, nRem)
rem := nRem.Int64()
w := c.words[rem]
// double-check bank on generation...
_, err := c.GetIndex(w)
if err != nil {
return nil, err
}
words = append(words, w)
}
return words, nil
}
func (c *WordCodec) WordsToBytes(words []string) ([]byte, error) {
l := len(words)
if l == 0 {
return nil, errors.New("Didn't provide any words")
}
n2048 := big.NewInt(2048)
nData := big.NewInt(0)
// since we output words based on the remainder, the first word has the lowest
// value... we must load them in reverse order
for i := 1; i <= l; i++ {
rem, err := c.GetIndex(words[l-i])
if err != nil {
return nil, err
}
nRem := big.NewInt(int64(rem))
nData.Mul(nData, n2048)
nData.Add(nData, nRem)
}
// we copy into a slice of the expected size, so it is not shorter if there
// are lots of leading 0s
dataBytes := nData.Bytes()
// copy into the container we have with the expected size
outLen, flex := bytelenFromWords(len(words))
toCheck := make([]byte, outLen)
if len(dataBytes) > outLen {
return nil, errors.New("Invalid data, could not have been generated by this codec")
}
copy(toCheck[outLen-len(dataBytes):], dataBytes)
// validate the checksum...
output, err := c.check.CheckECC(toCheck)
if flex && err != nil {
// if flex, try again one shorter....
toCheck = toCheck[1:]
output, err = c.check.CheckECC(toCheck)
}
return output, err
}
// GetIndex finds the index of the words to create bytes
// Generates a map the first time it is loaded, to avoid needless
// computation when list is not used.
func (c *WordCodec) GetIndex(word string) (int, error) {
// generate the first time
if c.bytes == nil {
b := map[string]int{}
for i, w := range c.words {
if _, ok := b[w]; ok {
return -1, errors.Errorf("Duplicate word in list: %s", w)
}
b[w] = i
}
c.bytes = b
}
// get the index, or an error
rem, ok := c.bytes[word]
if !ok {
return -1, errors.Errorf("Unrecognized word: %s", word)
}
return rem, nil
}

+ 180
- 0
keys/wordcodec_test.go View File

@ -0,0 +1,180 @@
package keys
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tmlibs/common"
)
func TestLengthCalc(t *testing.T) {
assert := assert.New(t)
cases := []struct {
bytes, words int
flexible bool
}{
{1, 1, false},
{2, 2, false},
// bytes pairs with same word count
{3, 3, true},
{4, 3, true},
{5, 4, false},
// bytes pairs with same word count
{10, 8, true},
{11, 8, true},
{12, 9, false},
{13, 10, false},
{20, 15, false},
// bytes pairs with same word count
{21, 16, true},
{32, 24, true},
}
for _, tc := range cases {
wl := wordlenFromBytes(tc.bytes)
assert.Equal(tc.words, wl, "%d", tc.bytes)
bl, flex := bytelenFromWords(tc.words)
assert.Equal(tc.flexible, flex, "%d", tc.words)
if !flex {
assert.Equal(tc.bytes, bl, "%d", tc.words)
} else {
// check if it is either tc.bytes or tc.bytes +1
choices := []int{tc.bytes, tc.bytes + 1}
assert.Contains(choices, bl, "%d", tc.words)
}
}
}
func TestEncodeDecode(t *testing.T) {
assert, require := assert.New(t), require.New(t)
codec, err := LoadCodec("english")
require.Nil(err, "%+v", err)
cases := [][]byte{
{7, 8, 9}, // TODO: 3 words -> 3 or 4 bytes
{12, 54, 99, 11}, // TODO: 3 words -> 3 or 4 bytes
{0, 54, 99, 11}, // TODO: 3 words -> 3 or 4 bytes, detect leading 0
{1, 2, 3, 4, 5}, // normal
{0, 0, 0, 0, 122, 23, 82, 195}, // leading 0s (8 chars, unclear)
{0, 0, 0, 0, 5, 22, 123, 55, 22}, // leading 0s (9 chars, clear)
{22, 44, 55, 1, 13, 0, 0, 0, 0}, // trailing 0s (9 chars, clear)
{0, 5, 253, 2, 0}, // leading and trailing zeros
{255, 196, 172, 234, 192, 255}, // big numbers
{255, 196, 172, 1, 234, 192, 255}, // big numbers, two length choices
// others?
}
for i, tc := range cases {
w, err := codec.BytesToWords(tc)
if assert.Nil(err, "%d: %v", i, err) {
b, err := codec.WordsToBytes(w)
if assert.Nil(err, "%d: %v", i, err) {
assert.Equal(len(tc), len(b))
assert.Equal(tc, b)
}
}
}
}
func TestCheckInvalidLists(t *testing.T) {
assert := assert.New(t)
trivial := []string{"abc", "def"}
short := make([]string, 1234)
long := make([]string, BankSize+1)
right := make([]string, BankSize)
dups := make([]string, BankSize)
for _, list := range [][]string{short, long, right, dups} {
for i := range list {
list[i] = cmn.RandStr(8)
}
}
// create one single duplicate
dups[192] = dups[782]
cases := []struct {
words []string
loadable bool
valid bool
}{
{trivial, false, false},
{short, false, false},
{long, false, false},
{dups, true, false}, // we only check dups on first use...
{right, true, true},
}
for i, tc := range cases {
codec, err := NewCodec(tc.words)
if !tc.loadable {
assert.NotNil(err, "%d", i)
} else if assert.Nil(err, "%d: %+v", i, err) {
data := cmn.RandBytes(32)
w, err := codec.BytesToWords(data)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
b, err := codec.WordsToBytes(w)
assert.Nil(err, "%d: %+v", i, err)
assert.Equal(data, b)
} else {
assert.NotNil(err, "%d", i)
}
}
}
}
func getRandWord(c *WordCodec) string {
idx := cmn.RandInt() % BankSize
return c.words[idx]
}
func getDiffWord(c *WordCodec, not string) string {
w := getRandWord(c)
if w == not {
w = getRandWord(c)
}
return w
}
func TestCheckTypoDetection(t *testing.T) {
assert, require := assert.New(t), require.New(t)
banks := []string{"english", "spanish", "japanese", "chinese_simplified"}
for _, bank := range banks {
codec, err := LoadCodec(bank)
require.Nil(err, "%s: %+v", bank, err)
for i := 0; i < 1000; i++ {
numBytes := cmn.RandInt()%60 + 1
data := cmn.RandBytes(numBytes)
words, err := codec.BytesToWords(data)
assert.Nil(err, "%s: %+v", bank, err)
good, err := codec.WordsToBytes(words)
assert.Nil(err, "%s: %+v", bank, err)
assert.Equal(data, good, bank)
// now try some tweaks...
cut := words[1:]
_, err = codec.WordsToBytes(cut)
assert.NotNil(err, "%s: %s", bank, words)
// swap a word within the bank, should fails
words[3] = getDiffWord(codec, words[3])
_, err = codec.WordsToBytes(words)
assert.NotNil(err, "%s: %s", bank, words)
// put a random word here, must fail
words[3] = cmn.RandStr(10)
_, err = codec.WordsToBytes(words)
assert.NotNil(err, "%s: %s", bank, words)
}
}
}

+ 68
- 0
keys/wordcodecbench_test.go View File

@ -0,0 +1,68 @@
package keys
import (
"testing"
cmn "github.com/tendermint/tmlibs/common"
)
func warmupCodec(bank string) *WordCodec {
codec, err := LoadCodec(bank)
if err != nil {
panic(err)
}
_, err = codec.GetIndex(codec.words[123])
if err != nil {
panic(err)
}
return codec
}
func BenchmarkCodec(b *testing.B) {
banks := []string{"english", "spanish", "japanese", "chinese_simplified"}
for _, bank := range banks {
b.Run(bank, func(sub *testing.B) {
codec := warmupCodec(bank)
sub.ResetTimer()
benchSuite(sub, codec)
})
}
}
func benchSuite(b *testing.B, codec *WordCodec) {
b.Run("to_words", func(sub *testing.B) {
benchMakeWords(sub, codec)
})
b.Run("to_bytes", func(sub *testing.B) {
benchParseWords(sub, codec)
})
}
func benchMakeWords(b *testing.B, codec *WordCodec) {
numBytes := 32
data := cmn.RandBytes(numBytes)
for i := 1; i <= b.N; i++ {
_, err := codec.BytesToWords(data)
if err != nil {
panic(err)
}
}
}
func benchParseWords(b *testing.B, codec *WordCodec) {
// generate a valid test string to parse
numBytes := 32
data := cmn.RandBytes(numBytes)
words, err := codec.BytesToWords(data)
if err != nil {
panic(err)
}
for i := 1; i <= b.N; i++ {
_, err := codec.WordsToBytes(words)
if err != nil {
panic(err)
}
}
}

+ 2048
- 0
keys/wordlist/chinese_simplified.txt
File diff suppressed because it is too large
View File


+ 2048
- 0
keys/wordlist/english.txt
File diff suppressed because it is too large
View File


+ 2048
- 0
keys/wordlist/japanese.txt
File diff suppressed because it is too large
View File


+ 2048
- 0
keys/wordlist/spanish.txt
File diff suppressed because it is too large
View File


+ 308
- 0
keys/wordlist/wordlist.go
File diff suppressed because it is too large
View File


+ 111
- 0
tests/keys.sh View File

@ -0,0 +1,111 @@
#!/bin/bash
EXE=keys
oneTimeSetUp() {
PASS=qwertyuiop
export TM_HOME=$HOME/.keys_test
rm -rf $TM_HOME
assertTrue $?
}
newKey(){
assertNotNull "keyname required" "$1"
KEYPASS=${2:-qwertyuiop}
KEY=$(echo $KEYPASS | ${EXE} new $1 -o json)
if ! assertTrue "created $1" $?; then return 1; fi
assertEquals "$1" $(echo $KEY | jq .key.name | tr -d \")
return $?
}
# updateKey <name> <oldkey> <newkey>
updateKey() {
(echo $2; echo $3) | keys update $1 > /dev/null
return $?
}
test00MakeKeys() {
USER=demouser
assertFalse "already user $USER" "${EXE} get $USER"
newKey $USER
assertTrue "no user $USER" "${EXE} get $USER"
# make sure bad password not accepted
assertFalse "accepts short password" "echo 123 | keys new badpass"
}
test01ListKeys() {
# one line plus the number of keys
assertEquals "2" $(keys list | wc -l)
newKey foobar
assertEquals "3" $(keys list | wc -l)
# we got the proper name here...
assertEquals "foobar" $(keys list -o json | jq .[1].name | tr -d \" )
# we get all names in normal output
EXPECTEDNAMES=$(echo demouser; echo foobar)
TEXTNAMES=$(keys list | tail -n +2 | cut -f1)
assertEquals "$EXPECTEDNAMES" "$TEXTNAMES"
# let's make sure the addresses match!
assertEquals "text and json addresses don't match" $(keys list | tail -1 | cut -f3) $(keys list -o json | jq .[1].address | tr -d \")
}
test02updateKeys() {
USER=changer
PASS1=awsedrftgyhu
PASS2=S4H.9j.D9S7hso
PASS3=h8ybO7GY6d2
newKey $USER $PASS1
assertFalse "accepts invalid pass" "updateKey $USER $PASS2 $PASS2"
assertTrue "doesn't update" "updateKey $USER $PASS1 $PASS2"
assertTrue "takes new key after update" "updateKey $USER $PASS2 $PASS3"
}
test03recoverKeys() {
USER=sleepy
PASS1=S4H.9j.D9S7hso
USER2=easy
PASS2=1234567890
# make a user and check they exist
echo "create..."
KEY=$(echo $PASS1 | ${EXE} new $USER -o json)
if ! assertTrue "created $USER" $?; then return 1; fi
if [ -n "$DEBUG" ]; then echo $KEY; echo; fi
SEED=$(echo $KEY | jq .seed | tr -d \")
ADDR=$(echo $KEY | jq .key.address | tr -d \")
PUBKEY=$(echo $KEY | jq .key.pubkey | tr -d \")
assertTrue "${EXE} get $USER > /dev/null"
# let's delete this key
echo "delete..."
assertFalse "echo foo | ${EXE} delete $USER > /dev/null"
assertTrue "echo $PASS1 | ${EXE} delete $USER > /dev/null"
assertFalse "${EXE} get $USER > /dev/null"
# fails on short password
echo "recover..."
assertFalse "echo foo; echo $SEED | ${EXE} recover $USER2 -o json > /dev/null"
# fails on bad seed
assertFalse "echo $PASS2; echo \"silly white whale tower bongo\" | ${EXE} recover $USER2 -o json > /dev/null"
# now we got it
KEY2=$((echo $PASS2; echo $SEED) | ${EXE} recover $USER2 -o json)
if ! assertTrue "recovery failed: $KEY2" $?; then return 1; fi
if [ -n "$DEBUG" ]; then echo $KEY2; echo; fi
# make sure it looks the same
NAME2=$(echo $KEY2 | jq .name | tr -d \")
ADDR2=$(echo $KEY2 | jq .address | tr -d \")
PUBKEY2=$(echo $KEY2 | jq .pubkey | tr -d \")
assertEquals "wrong username" "$USER2" "$NAME2"
assertEquals "address doesn't match" "$ADDR" "$ADDR2"
assertEquals "pubkey doesn't match" "$PUBKEY" "$PUBKEY2"
# and we can find the info
assertTrue "${EXE} get $USER2 > /dev/null"
}
# load and run these tests with shunit2!
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
. $DIR/shunit2

Loading…
Cancel
Save