Browse Source

Merge pull request #73 from tendermint/develop-pre-wire

Develop pre wire
pull/1782/head
Ethan Buchman 7 years ago
committed by GitHub
parent
commit
c3e19f3ea2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1410 additions and 1955 deletions
  1. +18
    -0
      CHANGELOG.md
  2. +75
    -44
      Makefile
  3. +7
    -7
      _nano/keys.go
  4. +7
    -7
      _nano/keys_test.go
  5. +0
    -0
      _nano/sign.go
  6. +5
    -4
      _nano/sign_test.go
  7. +1
    -1
      circle.yml
  8. +1
    -3
      encode_test.go
  9. +42
    -26
      glide.lock
  10. +9
    -2
      glide.yaml
  11. +0
    -0
      keys/bcrypt/base64.go
  12. +0
    -0
      keys/bcrypt/bcrypt.go
  13. +0
    -25
      keys/cryptostore/docs.go
  14. +0
    -49
      keys/cryptostore/enc_storage.go
  15. +0
    -60
      keys/cryptostore/encoder.go
  16. +0
    -105
      keys/cryptostore/encoder_test.go
  17. +0
    -88
      keys/cryptostore/generator.go
  18. +0
    -169
      keys/cryptostore/holder.go
  19. +0
    -378
      keys/cryptostore/holder_test.go
  20. +0
    -48
      keys/cryptostore/storage_test.go
  21. +100
    -48
      keys/hd/address.go
  22. +0
    -0
      keys/hd/address_test.go
  23. +2
    -10
      keys/hd/hd_test.go
  24. +0
    -0
      keys/hd/test.json
  25. +202
    -0
      keys/keybase.go
  26. +380
    -0
      keys/keybase_test.go
  27. +32
    -0
      keys/keys.go
  28. +99
    -0
      keys/mintkey.go
  29. +0
    -177
      keys/storage/filestorage/main.go
  30. +0
    -106
      keys/storage/filestorage/main_test.go
  31. +0
    -68
      keys/storage/memstorage/main.go
  32. +0
    -69
      keys/storage/memstorage/main_test.go
  33. +34
    -112
      keys/types.go
  34. +0
    -308
      keys/wordlist/wordlist.go
  35. +1
    -1
      keys/words/ecc.go
  36. +4
    -4
      keys/words/ecc_test.go
  37. +3
    -3
      keys/words/wordcodec.go
  38. +7
    -7
      keys/words/wordcodec_test.go
  39. +1
    -1
      keys/words/wordcodecbench_test.go
  40. +0
    -0
      keys/words/wordlist/chinese_simplified.txt
  41. +0
    -0
      keys/words/wordlist/english.txt
  42. +0
    -0
      keys/words/wordlist/japanese.txt
  43. +0
    -0
      keys/words/wordlist/spanish.txt
  44. +310
    -0
      keys/words/wordlist/wordlist.go
  45. +7
    -3
      priv_key.go
  46. +21
    -13
      pub_key.go
  47. +9
    -1
      pub_key_test.go
  48. +4
    -1
      random.go
  49. +2
    -2
      signature.go
  50. +26
    -4
      signature_test.go
  51. +1
    -1
      version.go

+ 18
- 0
CHANGELOG.md View File

@ -1,5 +1,23 @@
# Changelog
## 0.5.0 (March 2, 2017)
BREAKING CHANGES
- nano: moved to `_nano` now while we're having build issues
- bcrypt: moved to `keys/bcrypt`
- hd: moved to `keys/hd`; `BTC` added to some function names; other function cleanup
- keys/cryptostore: moved to `keys`, renamed to `keybase`, and completely refactored
- keys: moved BIP39 related code to `keys/words`
FEATURE
- `Address` is a type alias for `cmn.HexBytes`
BUG FIX
- PrivKey comparisons done in constant time
## 0.4.1 (October 27, 2017)
This release removes support for bcrypt as it was merged too soon without an upgrade plan


+ 75
- 44
Makefile View File

@ -1,69 +1,100 @@
.PHONEY: all test install get_vendor_deps ensure_tools codegen wordlist
GOTOOLS = \
github.com/Masterminds/glide \
github.com/jteeuwen/go-bindata/go-bindata \
github.com/alecthomas/gometalinter
github.com/jteeuwen/go-bindata/go-bindata
# gopkg.in/alecthomas/gometalinter.v2 \
#
GOTOOLS_CHECK = glide go-bindata #gometalinter.v2
REPO:=github.com/tendermint/go-crypto
all: check get_vendor_deps build test install
all: get_vendor_deps metalinter_test test
check: check_tools
test:
go test -p 1 `glide novendor`
get_vendor_deps: ensure_tools
########################################
### Build
wordlist:
# Generating wordlist.go...
go-bindata -ignore ".*\.go" -o keys/words/wordlist/wordlist.go -pkg "wordlist" keys/words/wordlist/...
build: wordlist
# Nothing else to build!
install:
# Nothing to install!
########################################
### Tools & dependencies
check_tools:
@# https://stackoverflow.com/a/25668869
@echo "Found tools: $(foreach tool,$(GOTOOLS_CHECK),\
$(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH")))"
get_tools:
@echo "--> Installing tools"
go get -u -v $(GOTOOLS)
#@gometalinter.v2 --install
update_tools:
@echo "--> Updating tools"
@go get -u $(GOTOOLS)
get_vendor_deps:
@rm -rf vendor/
@echo "--> Running glide install"
@glide install
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:
@echo "--> regenerating all interface wrappers"
@gen
@echo "Done!"
metalinter: ensure_tools
@gometalinter --install
gometalinter --vendor --deadline=600s --enable-all --disable=lll ./...
metalinter_test: ensure_tools
@gometalinter --install
gometalinter --vendor --deadline=600s --disable-all \
########################################
### Testing
test:
go test -p 1 `glide novendor`
########################################
### Formatting, linting, and vetting
fmt:
@go fmt ./...
metalinter:
@echo "==> Running linter"
gometalinter.v2 --vendor --deadline=600s --disable-all \
--enable=maligned \
--enable=deadcode \
--enable=gas \
--enable=goconst \
--enable=gocyclo \
--enable=goimports \
--enable=gosimple \
--enable=ineffassign \
--enable=interfacer \
--enable=maligned \
--enable=ineffassign \
--enable=megacheck \
--enable=misspell \
--enable=safesql \
--enable=misspell \
--enable=staticcheck \
--enable=safesql \
--enable=structcheck \
--enable=unconvert \
--enable=unconvert \
--enable=unused \
--enable=vetshadow \
--enable=vet \
--enable=varcheck \
--enable=vetshadow \
./...
#--enable=gas \
#--enable=dupl \
#--enable=errcheck \
#--enable=goimports \
#--enable=gocyclo \
#--enable=golint \ <== comments on anything exported
#--enable=gotype \
#--enable=interfacer \
#--enable=unparam \
#--enable=vet \
metalinter_all:
protoc $(INCLUDE) --lint_out=. types/*.proto
gometalinter.v2 --vendor --deadline=600s --enable-all --disable=lll ./...
# To avoid unintended conflicts with file names, always add to .PHONY
# unless there is a reason not to.
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONEY: check wordlist build install check_tools get_tools update_tools get_vendor_deps test fmt metalinter metalinter_all

nano/keys.go → _nano/keys.go View File


nano/keys_test.go → _nano/keys_test.go View File


nano/sign.go → _nano/sign.go View File


nano/sign_test.go → _nano/sign_test.go View File


+ 1
- 1
circle.yml View File

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

+ 1
- 3
encode_test.go View File

@ -145,8 +145,6 @@ func (s SigMessage) Bytes() []byte {
}
func TestEmbededWireEncodings(t *testing.T) {
assert := assert.New(t)
cases := []struct {
privKey PrivKey
keyType byte
@ -171,7 +169,7 @@ func TestEmbededWireEncodings(t *testing.T) {
for i, tc := range cases {
pubKey := tc.privKey.PubKey()
sig := tc.privKey.Sign(payload)
assert.True(pubKey.VerifyBytes(payload, sig), "%d", i)
assert.True(t, pubKey.VerifyBytes(payload, sig), "%d", i)
msg := SigMessage{
Key: pubKey,


+ 42
- 26
glide.lock View File

@ -1,57 +1,75 @@
hash: 6e06a42eafe0aeff112cee86aef6b2cab0e2f62c2e6bfccfb629aa22f6b62773
updated: 2017-10-27T18:45:18.350198941+02:00
hash: 2b121bf7364ed02c60faff6d619a22acb0489d59843be669be3f8823b8658b75
updated: 2018-03-02T10:39:45.076725737-05:00
imports:
- name: github.com/btcsuite/btcd
version: c7588cbf7690cd9f047a28efa2dcd8f2435a4e5e
version: 2be2f12b358dc57d70b8f501b00be450192efbc3
subpackages:
- btcec
- name: github.com/btcsuite/btcutil
version: 66871daeb12123ece012a9628d2798d01195c4b3
version: 501929d3d046174c3d39f0ea54ece471aa17238c
subpackages:
- base58
- name: github.com/ethanfrey/hid
version: 660bb717bd4e7cbcdf0f7cd5cadf1cb2e4be452a
- name: github.com/ethanfrey/ledger
version: 23a7bb9d74bc83a862fcb4bddde24215b2295ad9
version: f7f2f056357db77db845a79aa1abdadc3ae66369
- name: github.com/go-kit/kit
version: e2b298466b32c7cd5579a9b9b07e968fc9d9452c
version: 4dc7be5d2d12881735283bcab7352178e190fc71
subpackages:
- log
- log/level
- log/term
- name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-playground/locales
version: e4cbcb5d0652150d40ad0646651076b6bd2be4f6
subpackages:
- currency
- name: github.com/go-playground/universal-translator
version: 71201497bace774495daed26a3874fd339e0b538
- name: github.com/go-stack/stack
version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf
version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc
- name: github.com/gogo/protobuf
version: 1adfc126b41513cc696b209667c8656ea7aac67c
subpackages:
- gogoproto
- proto
- protoc-gen-gogo/descriptor
- name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9
- name: github.com/howeyc/crc16
version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f
version: 2b2a61e366a66d3efb279e46176e7291001e0354
- name: github.com/jmhodges/levigo
version: c42d9e0ca023e2198120196f842701bb4c55d7b9
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d
version: 30136e27e2ac8d167177e8a583aa4c3fea5be833
- name: github.com/syndtr/goleveldb
version: c7a14d4b00e222eab6111b4cd1af829c13f53ec2
subpackages:
- leveldb
- leveldb/cache
- leveldb/comparer
- leveldb/errors
- leveldb/filter
- leveldb/iterator
- leveldb/journal
- leveldb/memdb
- leveldb/opt
- leveldb/storage
- leveldb/table
- leveldb/util
- name: github.com/tendermint/ed25519
version: d8387025d2b9d158cf4efb07e7ebf814bcce2057
subpackages:
- edwards25519
- extra25519
- name: github.com/tendermint/go-wire
version: 8ee84b5b2581530168daf66fc89c548d27403c57
version: fa721242b042ecd4c6ed1a934ee740db4f74e45c
subpackages:
- data
- data/base58
- name: github.com/tendermint/tmlibs
version: 092eb701c7276907cdbed258750e22ce895b6735
version: 1b9b5652a199ab0be2e781393fb275b66377309d
subpackages:
- common
- db
- log
- name: golang.org/x/crypto
version: edd5e9b0879d13ee6970a50153d85b8fec9f7686
version: 91a49db82a88618983a78a06c1cbd4e00ab749ab
subpackages:
- bcrypt
- blowfish
@ -62,25 +80,23 @@ imports:
- poly1305
- ripemd160
- salsa20/salsa
- name: gopkg.in/go-playground/validator.v9
version: 1304298bf10d085adec514b076772a79c9cadb6b
testImports:
- name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
version: 8991bc29aa16c548c550c7ff78260e27b9ab7c73
subpackages:
- spew
- name: github.com/mndrix/btcutil
version: d3a63a5752ecf3fbc06bd97365da752111c263df
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
version: 792786c7400a136282c1664665ae0a8db921c6c2
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
version: 12b6f73e6084dad08a7c6e575284b177ecafbc71
subpackages:
- assert
- require
- name: github.com/tyler-smith/go-bip32
version: eb790af526c30f23a7c8b00a48e342f9d0bd6386
version: 2c9cfd17756470a0b7c3e4b7954bae7d11035504
- name: github.com/tyler-smith/go-bip39
version: 8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc

+ 9
- 2
glide.yaml View File

@ -6,13 +6,20 @@ import:
- package: github.com/btcsuite/btcutil
subpackages:
- base58
- package: github.com/syndtr/goleveldb
subpackages:
- leveldb
- leveldb/errors
- leveldb/iterator
- leveldb/opt
- leveldb/util
- package: github.com/tendermint/ed25519
subpackages:
- extra25519
- package: github.com/tendermint/tmlibs
version: develop
version: v0.7.0
- package: github.com/tendermint/go-wire
version: develop
version: v0.7.3
subpackages:
- data
- data/base58


bcrypt/base64.go → keys/bcrypt/base64.go View File


bcrypt/bcrypt.go → keys/bcrypt/bcrypt.go View File


+ 0
- 25
keys/cryptostore/docs.go View File

@ -1,25 +0,0 @@
/*
package cryptostore maintains everything needed for doing public-key signing and
key management in software, based on the go-crypto library from tendermint.
It is flexible, and allows the user to provide a key generation algorithm
(currently Ed25519 or Secp256k1), an encoder to passphrase-encrypt our keys
when storing them (currently SecretBox from NaCl), and a method to persist
the keys (currently FileStorage like ssh, or MemStorage for tests).
It should be relatively simple to write your own implementation of these
interfaces to match your specific security requirements.
Note that the private keys are never exposed outside the package, and the
interface of Manager could be implemented by an HSM in the future for
enhanced security. It would require a completely different implementation
however.
This Manager aims to implement Signer and KeyManager interfaces, along
with some extensions to allow importing/exporting keys and updating the
passphrase.
Encoder and Generator implementations are currently in this package,
keys.Storage implementations exist as subpackages of
keys/storage
*/
package cryptostore

+ 0
- 49
keys/cryptostore/enc_storage.go View File

@ -1,49 +0,0 @@
package cryptostore
import (
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
// encryptedStorage needs passphrase to get private keys
type encryptedStorage struct {
coder Encoder
store keys.Storage
}
func (es encryptedStorage) Put(name, pass string, key crypto.PrivKey) error {
secret, err := es.coder.Encrypt(key, pass)
if err != nil {
return err
}
ki := info(name, key)
return es.store.Put(name, secret, ki)
}
func (es encryptedStorage) Get(name, pass string) (crypto.PrivKey, keys.Info, error) {
secret, info, err := es.store.Get(name)
if err != nil {
return crypto.PrivKey{}, info, err
}
key, err := es.coder.Decrypt(secret, pass)
return key, info, err
}
func (es encryptedStorage) List() (keys.Infos, error) {
return es.store.List()
}
func (es encryptedStorage) Delete(name string) error {
return es.store.Delete(name)
}
// info hardcodes the encoding of keys
func info(name string, key crypto.PrivKey) keys.Info {
pub := key.PubKey()
return keys.Info{
Name: name,
Address: pub.Address(),
PubKey: pub,
}
}

+ 0
- 60
keys/cryptostore/encoder.go View File

@ -1,60 +0,0 @@
package cryptostore
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
)
var (
// SecretBox uses the algorithm from NaCL to store secrets securely
SecretBox Encoder = secretbox{}
// Noop doesn't do any encryption, should only be used in test code
Noop Encoder = noop{}
)
// Encoder is used to encrypt any key with a passphrase for storage.
//
// This should use a well-designed symetric encryption algorithm
type Encoder interface {
Encrypt(key crypto.PrivKey, pass string) ([]byte, error)
Decrypt(data []byte, pass string) (crypto.PrivKey, error)
}
func secret(passphrase string) []byte {
// TODO: Sha256(Bcrypt(passphrase))
return crypto.Sha256([]byte(passphrase))
}
type secretbox struct{}
func (e secretbox) Encrypt(key crypto.PrivKey, pass string) ([]byte, error) {
if pass == "" {
return key.Bytes(), nil
}
s := secret(pass)
cipher := crypto.EncryptSymmetric(key.Bytes(), s)
return cipher, nil
}
func (e secretbox) Decrypt(data []byte, pass string) (key crypto.PrivKey, err error) {
private := data
if pass != "" {
s := secret(pass)
private, err = crypto.DecryptSymmetric(data, s)
if err != nil {
return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase")
}
}
key, err = crypto.PrivKeyFromBytes(private)
return key, errors.Wrap(err, "Invalid Passphrase")
}
type noop struct{}
func (n noop) Encrypt(key crypto.PrivKey, pass string) ([]byte, error) {
return key.Bytes(), nil
}
func (n noop) Decrypt(data []byte, pass string) (crypto.PrivKey, error) {
return crypto.PrivKeyFromBytes(data)
}

+ 0
- 105
keys/cryptostore/encoder_test.go View File

@ -1,105 +0,0 @@
package cryptostore_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/go-crypto/keys/cryptostore"
)
func TestNoopEncoder(t *testing.T) {
assert, require := assert.New(t), require.New(t)
noop := cryptostore.Noop
key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
require.NoError(err)
key2, err := cryptostore.GenSecp256k1.Generate(cmn.RandBytes(16))
require.NoError(err)
b, err := noop.Encrypt(key, "encode")
require.Nil(err)
assert.NotEmpty(b)
b2, err := noop.Encrypt(key2, "encode")
require.Nil(err)
assert.NotEmpty(b2)
assert.NotEqual(b, b2)
// note the decode with a different password works - not secure!
pk, err := noop.Decrypt(b, "decode")
require.Nil(err)
require.NotNil(pk)
assert.Equal(key, pk)
pk2, err := noop.Decrypt(b2, "kggugougp")
require.Nil(err)
require.NotNil(pk2)
assert.Equal(key2, pk2)
}
func TestSecretBox(t *testing.T) {
assert, require := assert.New(t), require.New(t)
enc := cryptostore.SecretBox
key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
require.NoError(err)
pass := "some-special-secret"
b, err := enc.Encrypt(key, pass)
require.Nil(err)
assert.NotEmpty(b)
// decoding with a different pass is an error
pk, err := enc.Decrypt(b, "decode")
require.NotNil(err)
require.True(pk.Empty())
// but decoding with the same passphrase gets us our key
pk, err = enc.Decrypt(b, pass)
require.Nil(err)
assert.Equal(key, pk)
}
func TestSecretBoxNoPass(t *testing.T) {
assert, require := assert.New(t), require.New(t)
enc := cryptostore.SecretBox
key, rerr := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
require.NoError(rerr)
cases := []struct {
encode string
decode string
valid bool
}{
{"foo", "foo", true},
{"foo", "food", false},
{"", "", true},
{"", "a", false},
{"a", "", false},
}
for i, tc := range cases {
b, err := enc.Encrypt(key, tc.encode)
require.Nil(err, "%d: %+v", i, err)
assert.NotEmpty(b, "%d", i)
pk, err := enc.Decrypt(b, tc.decode)
if tc.valid {
require.Nil(err, "%d: %+v", i, err)
assert.Equal(key, pk, "%d", i)
} else {
require.NotNil(err, "%d", i)
}
}
// now let's make sure raw bytes also work...
b := key.Bytes()
pk, err := enc.Decrypt(b, "")
require.Nil(err, "%+v", err)
assert.Equal(key, pk)
}

+ 0
- 88
keys/cryptostore/generator.go View File

@ -1,88 +0,0 @@
package cryptostore
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/nano"
)
var (
// GenEd25519 produces Ed25519 private keys
GenEd25519 Generator = GenFunc(genEd25519)
// GenSecp256k1 produces Secp256k1 private keys
GenSecp256k1 Generator = GenFunc(genSecp256)
// GenLedgerEd25519 used Ed25519 keys stored on nano ledger s with cosmos app
GenLedgerEd25519 Generator = GenFunc(genLedgerEd25519)
)
// Generator determines the type of private key the keystore creates
type Generator interface {
Generate(secret []byte) (crypto.PrivKey, error)
}
// GenFunc is a helper to transform a function into a Generator
type GenFunc func(secret []byte) (crypto.PrivKey, error)
func (f GenFunc) Generate(secret []byte) (crypto.PrivKey, error) {
return f(secret)
}
func genEd25519(secret []byte) (crypto.PrivKey, error) {
key := crypto.GenPrivKeyEd25519FromSecret(secret).Wrap()
return key, nil
}
func genSecp256(secret []byte) (crypto.PrivKey, error) {
key := crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap()
return key, nil
}
// secret is completely ignored for the ledger...
// just for interface compatibility
func genLedgerEd25519(secret []byte) (crypto.PrivKey, error) {
return nano.NewPrivKeyLedgerEd25519Ed25519()
}
type genInvalidByte struct {
typ byte
}
func (g genInvalidByte) Generate(secret []byte) (crypto.PrivKey, error) {
err := errors.Errorf("Cannot generate keys for algorithm: %X", g.typ)
return crypto.PrivKey{}, err
}
type genInvalidAlgo struct {
algo string
}
func (g genInvalidAlgo) Generate(secret []byte) (crypto.PrivKey, error) {
err := errors.Errorf("Cannot generate keys for algorithm: %s", g.algo)
return crypto.PrivKey{}, err
}
func getGenerator(algo string) Generator {
switch algo {
case crypto.NameEd25519:
return GenEd25519
case crypto.NameSecp256k1:
return GenSecp256k1
case nano.NameLedgerEd25519:
return GenLedgerEd25519
default:
return genInvalidAlgo{algo}
}
}
func getGeneratorByType(typ byte) Generator {
switch typ {
case crypto.TypeEd25519:
return GenEd25519
case crypto.TypeSecp256k1:
return GenSecp256k1
case nano.TypeLedgerEd25519:
return GenLedgerEd25519
default:
return genInvalidByte{typ}
}
}

+ 0
- 169
keys/cryptostore/holder.go View File

@ -1,169 +0,0 @@
package cryptostore
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
codec keys.Codec
}
func New(coder Encoder, store keys.Storage, codec keys.Codec) Manager {
return Manager{
es: encryptedStorage{
coder: coder,
store: store,
},
codec: codec,
}
}
// assert Manager satisfies keys.Signer and keys.Manager interfaces
var _ keys.Signer = Manager{}
var _ keys.Manager = 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: ed25519, secp256k1
func (s Manager) Create(name, passphrase, algo string) (keys.Info, string, error) {
// 128-bits are the all the randomness we can make use of
secret := crypto.CRandBytes(16)
gen := getGenerator(algo)
key, err := gen.Generate(secret)
if err != nil {
return keys.Info{}, "", err
}
err = s.es.Put(name, passphrase, key)
if err != nil {
return keys.Info{}, "", err
}
// we append the type byte to the serialized secret to help with recovery
// ie [secret] = [secret] + [type]
typ := key.Bytes()[0]
secret = append(secret, typ)
seed, err := s.codec.BytesToWords(secret)
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), " ")
secret, err := s.codec.WordsToBytes(words)
if err != nil {
return keys.Info{}, err
}
// secret is comprised of the actual secret with the type appended
// ie [secret] = [secret] + [type]
l := len(secret)
secret, typ := secret[:l-1], secret[l-1]
gen := getGeneratorByType(typ)
key, err := gen.Generate(secret)
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
}
// List loads the keys from the storage and enforces alphabetical order
func (s Manager) List() (keys.Infos, error) {
res, err := s.es.List()
res.Sort()
return res, err
}
// Get returns the public information about one key
func (s Manager) Get(name string) (keys.Info, error) {
_, info, err := s.es.store.Get(name)
return info, err
}
// Sign will modify the Signable in order to attach a valid signature with
// this public key
//
// If no key for this name, or the passphrase doesn't match, returns an error
func (s Manager) Sign(name, passphrase string, tx keys.Signable) error {
key, _, err := s.es.Get(name, passphrase)
if err != nil {
return err
}
sig := key.Sign(tx.SignBytes())
pubkey := key.PubKey()
return tx.Sign(pubkey, sig)
}
// Export decodes the private key with the current password, encodes
// it with a secure one-time password and generates a sequence that can be
// Imported by another Manager
//
// This is designed to copy from one device to another, or provide backups
// during version updates.
func (s Manager) Export(name, oldpass, transferpass string) ([]byte, error) {
key, _, err := s.es.Get(name, oldpass)
if err != nil {
return nil, err
}
res, err := s.es.coder.Encrypt(key, transferpass)
return res, err
}
// Import accepts bytes generated by Export along with the same transferpass
// If they are valid, it stores the password under the given name with the
// new passphrase.
func (s Manager) Import(name, newpass, transferpass string, data []byte) error {
key, err := s.es.coder.Decrypt(data, transferpass)
if err != nil {
return err
}
return s.es.Put(name, newpass, key)
}
// Delete removes key forever, but we must present the
// proper passphrase before deleting it (for security)
func (s Manager) Delete(name, passphrase string) error {
// verify we have the proper password before deleting
_, _, err := s.es.Get(name, passphrase)
if err != nil {
return err
}
return s.es.Delete(name)
}
// Update changes the passphrase with which a already stored key is encoded.
//
// oldpass must be the current passphrase used for encoding, newpass will be
// the only valid passphrase from this time forward
func (s Manager) Update(name, oldpass, newpass string) error {
key, _, err := s.es.Get(name, oldpass)
if err != nil {
return err
}
// we must delete first, as Putting over an existing name returns an error
s.Delete(name, oldpass)
return s.es.Put(name, newpass, key)
}

+ 0
- 378
keys/cryptostore/holder_test.go View File

@ -1,378 +0,0 @@
package cryptostore_test
import (
"bytes"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tmlibs/common"
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"
"github.com/tendermint/go-crypto/nano"
)
// TestKeyManagement makes sure we can manipulate these keys well
func TestKeyManagement(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, n3 := "personal", "business", "other"
p1, p2 := "1234", "really-secure!@#$"
// Check empty state
l, err := cstore.List()
require.Nil(err)
assert.Empty(l)
// create some keys
_, err = cstore.Get(n1)
assert.NotNil(err)
i, _, err := cstore.Create(n1, p1, algo)
require.Equal(n1, i.Name)
require.Nil(err)
_, _, err = cstore.Create(n2, p2, algo)
require.Nil(err)
// we can get these keys
i2, err := cstore.Get(n2)
assert.Nil(err)
_, err = cstore.Get(n3)
assert.NotNil(err)
// list shows them in order
keyS, err := cstore.List()
require.Nil(err)
require.Equal(2, len(keyS))
// note these are in alphabetical order
assert.Equal(n2, keyS[0].Name)
assert.Equal(n1, keyS[1].Name)
assert.Equal(i2.PubKey, keyS[0].PubKey)
// deleting a key removes it
err = cstore.Delete("bad name", "foo")
require.NotNil(err)
err = cstore.Delete(n1, p1)
require.Nil(err)
keyS, err = cstore.List()
require.Nil(err)
assert.Equal(1, len(keyS))
_, err = cstore.Get(n1)
assert.NotNil(err)
// make sure that it only signs with the right password
// tx := mock.NewSig([]byte("mytransactiondata"))
// err = cstore.Sign(n2, p1, tx)
// assert.NotNil(err)
// err = cstore.Sign(n2, p2, tx)
// assert.Nil(err, "%+v", err)
// sigs, err := tx.Signers()
// assert.Nil(err, "%+v", err)
// if assert.Equal(1, len(sigs)) {
// assert.Equal(i2.PubKey, sigs[0])
// }
}
// TestSignVerify does some detailed checks on how we sign and validate
// signatures
func TestSignVerify(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.NameSecp256k1
n1, n2 := "some dude", "a dudette"
p1, p2 := "1234", "foobar"
// create two users and get their info
i1, _, err := cstore.Create(n1, p1, algo)
require.Nil(err)
i2, _, err := cstore.Create(n2, p2, algo)
require.Nil(err)
// let's try to sign some messages
d1 := []byte("my first message")
d2 := []byte("some other important info!")
// try signing both data with both keys...
s11 := keys.NewMockSignable(d1)
err = cstore.Sign(n1, p1, s11)
require.Nil(err)
s12 := keys.NewMockSignable(d2)
err = cstore.Sign(n1, p1, s12)
require.Nil(err)
s21 := keys.NewMockSignable(d1)
err = cstore.Sign(n2, p2, s21)
require.Nil(err)
s22 := keys.NewMockSignable(d2)
err = cstore.Sign(n2, p2, s22)
require.Nil(err)
// let's try to validate and make sure it only works when everything is proper
cases := []struct {
key crypto.PubKey
data []byte
sig crypto.Signature
valid bool
}{
// proper matches
{i1.PubKey, d1, s11.Signature, true},
// change data, pubkey, or signature leads to fail
{i1.PubKey, d2, s11.Signature, false},
{i2.PubKey, d1, s11.Signature, false},
{i1.PubKey, d1, s21.Signature, false},
// make sure other successes
{i1.PubKey, d2, s12.Signature, true},
{i2.PubKey, d1, s21.Signature, true},
{i2.PubKey, d2, s22.Signature, true},
}
for i, tc := range cases {
valid := tc.key.VerifyBytes(tc.data, tc.sig)
assert.Equal(tc.valid, valid, "%d", i)
}
}
// TestSignWithLedger makes sure we have ledger compatibility with
// the crypto store.
//
// This test will only succeed with a ledger attached to the computer
// and the cosmos app open
func TestSignWithLedger(t *testing.T) {
assert, require := assert.New(t), require.New(t)
if os.Getenv("WITH_LEDGER") == "" {
t.Skip("Set WITH_LEDGER to run code on real ledger")
}
// make the storage with reasonable defaults
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
n := "nano-s"
p := "hard2hack"
// create a nano user
c, _, err := cstore.Create(n, p, nano.NameLedgerEd25519)
require.Nil(err, "%+v", err)
assert.Equal(c.Name, n)
_, ok := c.PubKey.Unwrap().(nano.PubKeyLedgerEd25519)
require.True(ok)
// make sure we can get it back
info, err := cstore.Get(n)
require.Nil(err, "%+v", err)
assert.Equal(info.Name, n)
key := info.PubKey
require.False(key.Empty())
require.True(key.Equals(c.PubKey))
// let's try to sign some messages
d1 := []byte("welcome to cosmos")
d2 := []byte("please turn on the app")
// try signing both data with the ledger...
s1 := keys.NewMockSignable(d1)
err = cstore.Sign(n, p, s1)
require.Nil(err)
s2 := keys.NewMockSignable(d2)
err = cstore.Sign(n, p, s2)
require.Nil(err)
// now, let's check those signatures work
assert.True(key.VerifyBytes(d1, s1.Signature))
assert.True(key.VerifyBytes(d2, s2.Signature))
// and mismatched signatures don't
assert.False(key.VerifyBytes(d1, s2.Signature))
}
func assertPassword(assert *assert.Assertions, cstore cryptostore.Manager, name, pass, badpass string) {
err := cstore.Update(name, badpass, pass)
assert.NotNil(err)
err = cstore.Update(name, pass, pass)
assert.Nil(err, "%+v", err)
}
// TestImportUnencrypted tests accepting raw priv keys bytes as input
func TestImportUnencrypted(t *testing.T) {
require := require.New(t)
// make the storage with reasonable defaults
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
keys.MustLoadCodec("english"),
)
key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
require.NoError(err)
addr := key.PubKey().Address()
name := "john"
pass := "top-secret"
// import raw bytes
err = cstore.Import(name, pass, "", key.Bytes())
require.Nil(err, "%+v", err)
// make sure the address matches
info, err := cstore.Get(name)
require.Nil(err, "%+v", err)
require.EqualValues(addr, info.Address)
}
// TestAdvancedKeyManagement verifies update, import, export functionality
func TestAdvancedKeyManagement(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.NameSecp256k1
n1, n2 := "old-name", "new name"
p1, p2, p3, pt := "1234", "foobar", "ding booms!", "really-secure!@#$"
// make sure key works with initial password
_, _, err := cstore.Create(n1, p1, algo)
require.Nil(err, "%+v", err)
assertPassword(assert, cstore, n1, p1, p2)
// update password requires the existing password
err = cstore.Update(n1, "jkkgkg", p2)
assert.NotNil(err)
assertPassword(assert, cstore, n1, p1, p2)
// then it changes the password when correct
err = cstore.Update(n1, p1, p2)
assert.Nil(err)
// p2 is now the proper one!
assertPassword(assert, cstore, n1, p2, p1)
// exporting requires the proper name and passphrase
_, err = cstore.Export(n2, p2, pt)
assert.NotNil(err)
_, err = cstore.Export(n1, p1, pt)
assert.NotNil(err)
exported, err := cstore.Export(n1, p2, pt)
require.Nil(err, "%+v", err)
// import fails on bad transfer pass
err = cstore.Import(n2, p3, p2, exported)
assert.NotNil(err)
}
// 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 ExampleNew() {
// Select the encryption and storage for your cryptostore
cstore := cryptostore.New(
cryptostore.SecretBox,
// Note: use filestorage.New(dir) for real data
memstorage.New(),
keys.MustLoadCodec("english"),
)
ed := crypto.NameEd25519
sec := crypto.NameSecp256k1
// Add keys and see they return in alphabetical order
bob, _, err := cstore.Create("Bob", "friend", ed)
if err != nil {
// this should never happen
fmt.Println(err)
} else {
// return info here just like in List
fmt.Println(bob.Name)
}
cstore.Create("Alice", "secret", sec)
cstore.Create("Carl", "mitm", ed)
info, _ := cstore.List()
for _, i := range info {
fmt.Println(i.Name)
}
// We need to use passphrase to generate a signature
tx := keys.NewMockSignable([]byte("deadbeef"))
err = cstore.Sign("Bob", "friend", tx)
if err != nil {
fmt.Println("don't accept real passphrase")
}
// and we can validate the signature with publically available info
binfo, _ := cstore.Get("Bob")
if !binfo.PubKey.Equals(bob.PubKey) {
fmt.Println("Get and Create return different keys")
}
sigs, err := tx.Signers()
if err != nil {
fmt.Println("badly signed")
} else if bytes.Equal(sigs[0].Bytes(), binfo.PubKey.Bytes()) {
fmt.Println("signed by Bob")
} else {
fmt.Println("signed by someone else")
}
// Output:
// Bob
// Alice
// Bob
// Carl
// signed by Bob
}

+ 0
- 48
keys/cryptostore/storage_test.go View File

@ -1,48 +0,0 @@
package cryptostore
import (
"testing"
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
keys "github.com/tendermint/go-crypto/keys"
)
func TestSortKeys(t *testing.T) {
assert := assert.New(t)
gen := func() crypto.PrivKey {
key, _ := GenEd25519.Generate(cmn.RandBytes(16))
return key
}
assert.NotEqual(gen(), gen())
// alphabetical order is n3, n1, n2
n1, n2, n3 := "john", "mike", "alice"
infos := keys.Infos{
info(n1, gen()),
info(n2, gen()),
info(n3, gen()),
}
// make sure they are initialized unsorted
assert.Equal(n1, infos[0].Name)
assert.Equal(n2, infos[1].Name)
assert.Equal(n3, infos[2].Name)
// now they are sorted
infos.Sort()
assert.Equal(n3, infos[0].Name)
assert.Equal(n1, infos[1].Name)
assert.Equal(n2, infos[2].Name)
// make sure info put some real data there...
assert.NotEmpty(infos[0].PubKey)
assert.NotEmpty(infos[0].PubKey.Address())
assert.NotEmpty(infos[1].PubKey)
assert.NotEmpty(infos[1].PubKey.Address())
assert.NotEqual(infos[0].PubKey, infos[1].PubKey)
}

hd/address.go → keys/hd/address.go View File


hd/address_test.go → keys/hd/address_test.go View File


hd/hd_test.go → keys/hd/hd_test.go View File


hd/test.json → keys/hd/test.json View File


+ 202
- 0
keys/keybase.go View File

@ -0,0 +1,202 @@
package keys
import (
"fmt"
"strings"
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys/words"
dbm "github.com/tendermint/tmlibs/db"
)
// dbKeybase combines encyption and storage implementation to provide
// a full-featured key manager
type dbKeybase struct {
db dbm.DB
codec words.Codec
}
func New(db dbm.DB, codec words.Codec) dbKeybase {
return dbKeybase{
db: db,
codec: codec,
}
}
var _ Keybase = dbKeybase{}
// Create generates a new key and persists it to storage, encrypted
// using the passphrase. It returns the generated seedphrase
// (mnemonic) and the key Info. It returns an error if it fails to
// generate a key for the given algo type, or if another key is
// already stored under the same name.
func (kb dbKeybase) Create(name, passphrase string, algo CryptoAlgo) (Info, string, error) {
// NOTE: secret is SHA256 hashed by secp256k1 and ed25519.
// 16 byte secret corresponds to 12 BIP39 words.
// XXX: Ledgers use 24 words now - should we ?
secret := crypto.CRandBytes(16)
priv, err := generate(algo, secret)
if err != nil {
return Info{}, "", err
}
// encrypt and persist the key
info := kb.writeKey(priv, name, passphrase)
// we append the type byte to the serialized secret to help with
// recovery
// ie [secret] = [type] + [secret]
typ := cryptoAlgoToByte(algo)
secret = append([]byte{typ}, secret...)
// return the mnemonic phrase
words, err := kb.codec.BytesToWords(secret)
seed := strings.Join(words, " ")
return info, seed, err
}
// Recover converts a seedphrase to a private key and persists it,
// encrypted with the given passphrase. Functions like Create, but
// seedphrase is input not output.
func (kb dbKeybase) Recover(name, passphrase, seedphrase string) (Info, error) {
words := strings.Split(strings.TrimSpace(seedphrase), " ")
secret, err := kb.codec.WordsToBytes(words)
if err != nil {
return Info{}, err
}
// secret is comprised of the actual secret with the type
// appended.
// ie [secret] = [type] + [secret]
typ, secret := secret[0], secret[1:]
algo := byteToCryptoAlgo(typ)
priv, err := generate(algo, secret)
if err != nil {
return Info{}, err
}
// encrypt and persist key.
public := kb.writeKey(priv, name, passphrase)
return public, err
}
// List returns the keys from storage in alphabetical order.
func (kb dbKeybase) List() ([]Info, error) {
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
}
// Get returns the public information about one key.
func (kb dbKeybase) Get(name string) (Info, error) {
bs := kb.db.Get(infoKey(name))
return readInfo(bs)
}
// Sign signs the msg with the named key.
// It returns an error if the key doesn't exist or the decryption fails.
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pub crypto.PubKey, err error) {
info, err := kb.Get(name)
if err != nil {
return
}
priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
if err != nil {
return
}
sig = priv.Sign(msg)
pub = priv.PubKey()
return
}
func (kb dbKeybase) Export(name string) (armor string, err error) {
bz := kb.db.Get(infoKey(name))
if bz == nil {
return "", errors.New("No key to export with name " + name)
}
return armorInfoBytes(bz), nil
}
func (kb dbKeybase) Import(name string, armor string) (err error) {
bz := kb.db.Get(infoKey(name))
if len(bz) > 0 {
return errors.New("Cannot overwrite data for name " + name)
}
infoBytes, err := unarmorInfoBytes(armor)
if err != nil {
return
}
kb.db.Set(infoKey(name), infoBytes)
return nil
}
// Delete removes key forever, but we must present the
// proper passphrase before deleting it (for security).
func (kb dbKeybase) Delete(name, passphrase string) error {
// verify we have the proper password before deleting
info, err := kb.Get(name)
if err != nil {
return err
}
_, err = unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
if err != nil {
return err
}
kb.db.DeleteSync(infoKey(name))
return nil
}
// Update changes the passphrase with which an already stored key is
// encrypted.
//
// oldpass must be the current passphrase used for encryption,
// newpass will be the only valid passphrase from this time forward.
func (kb dbKeybase) Update(name, oldpass, newpass string) error {
info, err := kb.Get(name)
if err != nil {
return err
}
key, err := unarmorDecryptPrivKey(info.PrivKeyArmor, oldpass)
if err != nil {
return err
}
kb.writeKey(key, name, newpass)
return nil
}
func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
// generate the encrypted privkey
privArmor := encryptArmorPrivKey(priv, passphrase)
// make Info
info := newInfo(name, priv.PubKey(), privArmor)
// write them both
kb.db.SetSync(infoKey(name), info.bytes())
return info
}
func generate(algo CryptoAlgo, secret []byte) (crypto.PrivKey, error) {
switch algo {
case AlgoEd25519:
return crypto.GenPrivKeyEd25519FromSecret(secret).Wrap(), nil
case AlgoSecp256k1:
return crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap(), nil
default:
err := errors.Errorf("Cannot generate keys for algorithm: %s", algo)
return crypto.PrivKey{}, err
}
}
func infoKey(name string) []byte {
return []byte(fmt.Sprintf("%s.info", name))
}

+ 380
- 0
keys/keybase_test.go View File

@ -0,0 +1,380 @@
package keys_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/words"
)
// TestKeyManagement makes sure we can manipulate these keys well
func TestKeyManagement(t *testing.T) {
// make the storage with reasonable defaults
cstore := keys.New(
dbm.NewMemDB(),
words.MustLoadCodec("english"),
)
algo := keys.AlgoEd25519
n1, n2, n3 := "personal", "business", "other"
p1, p2 := "1234", "really-secure!@#$"
// Check empty state
l, err := cstore.List()
require.Nil(t, err)
assert.Empty(t, l)
// create some keys
_, err = cstore.Get(n1)
assert.NotNil(t, err)
i, _, err := cstore.Create(n1, p1, algo)
require.Equal(t, n1, i.Name)
require.Nil(t, err)
_, _, err = cstore.Create(n2, p2, algo)
require.Nil(t, err)
// we can get these keys
i2, err := cstore.Get(n2)
assert.Nil(t, err)
_, err = cstore.Get(n3)
assert.NotNil(t, err)
// list shows them in order
keyS, err := cstore.List()
require.Nil(t, err)
require.Equal(t, 2, len(keyS))
// note these are in alphabetical order
assert.Equal(t, n2, keyS[0].Name)
assert.Equal(t, n1, keyS[1].Name)
assert.Equal(t, i2.PubKey, keyS[0].PubKey)
// deleting a key removes it
err = cstore.Delete("bad name", "foo")
require.NotNil(t, err)
err = cstore.Delete(n1, p1)
require.Nil(t, err)
keyS, err = cstore.List()
require.Nil(t, err)
assert.Equal(t, 1, len(keyS))
_, err = cstore.Get(n1)
assert.NotNil(t, err)
// make sure that it only signs with the right password
// tx := mock.NewSig([]byte("mytransactiondata"))
// err = cstore.Sign(n2, p1, tx)
// assert.NotNil(t, err)
// err = cstore.Sign(n2, p2, tx)
// assert.Nil(t, err, "%+v", err)
// sigs, err := tx.Signers()
// assert.Nil(t, err, "%+v", err)
// if assert.Equal(t, 1, len(sigs)) {
// assert.Equal(t, i2.PubKey, sigs[0])
// }
}
// TestSignVerify does some detailed checks on how we sign and validate
// signatures
func TestSignVerify(t *testing.T) {
// make the storage with reasonable defaults
cstore := keys.New(
dbm.NewMemDB(),
words.MustLoadCodec("english"),
)
algo := keys.AlgoSecp256k1
n1, n2 := "some dude", "a dudette"
p1, p2 := "1234", "foobar"
// create two users and get their info
i1, _, err := cstore.Create(n1, p1, algo)
require.Nil(t, err)
i2, _, err := cstore.Create(n2, p2, algo)
require.Nil(t, err)
// let's try to sign some messages
d1 := []byte("my first message")
d2 := []byte("some other important info!")
// try signing both data with both keys...
s11, pub1, err := cstore.Sign(n1, p1, d1)
require.Nil(t, err)
require.Equal(t, i1.PubKey, pub1)
s12, pub1, err := cstore.Sign(n1, p1, d2)
require.Nil(t, err)
require.Equal(t, i1.PubKey, pub1)
s21, pub2, err := cstore.Sign(n2, p2, d1)
require.Nil(t, err)
require.Equal(t, i2.PubKey, pub2)
s22, pub2, err := cstore.Sign(n2, p2, d2)
require.Nil(t, err)
require.Equal(t, i2.PubKey, pub2)
// let's try to validate and make sure it only works when everything is proper
cases := []struct {
key crypto.PubKey
data []byte
sig crypto.Signature
valid bool
}{
// proper matches
{i1.PubKey, d1, s11, true},
// change data, pubkey, or signature leads to fail
{i1.PubKey, d2, s11, false},
{i2.PubKey, d1, s11, false},
{i1.PubKey, d1, s21, false},
// make sure other successes
{i1.PubKey, d2, s12, true},
{i2.PubKey, d1, s21, true},
{i2.PubKey, d2, s22, true},
}
for i, tc := range cases {
valid := tc.key.VerifyBytes(tc.data, tc.sig)
assert.Equal(t, tc.valid, valid, "%d", i)
}
}
/*
// TestSignWithLedger makes sure we have ledger compatibility with
// the crypto store.
//
// This test will only succeed with a ledger attached to the computer
// and the cosmos app open
func TestSignWithLedger(t *testing.T) {
if os.Getenv("WITH_LEDGER") == "" {
t.Skip("Set WITH_LEDGER to run code on real ledger")
}
// make the storage with reasonable defaults
cstore := keys.New(
dbm.NewMemDB(),
words.MustLoadCodec("english"),
)
n := "nano-s"
p := "hard2hack"
// create a nano user
c, _, err := cstore.Create(n, p, nano.KeyLedgerEd25519)
require.Nil(t, err, "%+v", err)
assert.Equal(t, c.Key, n)
_, ok := c.PubKey.Unwrap().(nano.PubKeyLedgerEd25519)
require.True(t, ok)
// make sure we can get it back
info, err := cstore.Get(n)
require.Nil(t, err, "%+v", err)
assert.Equal(t, info.Key, n)
key := info.PubKey
require.False(t ,key.Empty())
require.True(t, key.Equals(c.PubKey))
// let's try to sign some messages
d1 := []byte("welcome to cosmos")
d2 := []byte("please turn on the app")
// try signing both data with the ledger...
s1, pub, err := cstore.Sign(n, p, d1)
require.Nil(t, err)
require.Equal(t, info.PubKey, pub)
s2, pub, err := cstore.Sign(n, p, d2)
require.Nil(t, err)
require.Equal(t, info.PubKey, pub)
// now, let's check those signatures work
assert.True(t, key.VerifyBytes(d1, s1))
assert.True(t, key.VerifyBytes(d2, s2))
// and mismatched signatures don't
assert.False(t, key.VerifyBytes(d1, s2))
}
*/
func assertPassword(t *testing.T, cstore keys.Keybase, name, pass, badpass string) {
err := cstore.Update(name, badpass, pass)
assert.NotNil(t, err)
err = cstore.Update(name, pass, pass)
assert.Nil(t, err, "%+v", err)
}
// TestExportImport tests exporting and importing keys.
func TestExportImport(t *testing.T) {
// make the storage with reasonable defaults
db := dbm.NewMemDB()
cstore := keys.New(
db,
words.MustLoadCodec("english"),
)
info, _, err := cstore.Create("john", "passphrase", keys.AlgoEd25519)
assert.Nil(t, err)
assert.Equal(t, info.Name, "john")
addr := info.PubKey.Address()
john, err := cstore.Get("john")
assert.Nil(t, err)
assert.Equal(t, john.Name, "john")
assert.Equal(t, john.PubKey.Address(), addr)
armor, err := cstore.Export("john")
assert.Nil(t, err)
err = cstore.Import("john2", armor)
assert.Nil(t, err)
john2, err := cstore.Get("john2")
assert.Nil(t, err)
assert.Equal(t, john.PubKey.Address(), addr)
assert.Equal(t, john.Name, "john")
assert.Equal(t, john, john2)
}
// TestAdvancedKeyManagement verifies update, import, export functionality
func TestAdvancedKeyManagement(t *testing.T) {
// make the storage with reasonable defaults
cstore := keys.New(
dbm.NewMemDB(),
words.MustLoadCodec("english"),
)
algo := keys.AlgoSecp256k1
n1, n2 := "old-name", "new name"
p1, p2 := "1234", "foobar"
// make sure key works with initial password
_, _, err := cstore.Create(n1, p1, algo)
require.Nil(t, err, "%+v", err)
assertPassword(t, cstore, n1, p1, p2)
// update password requires the existing password
err = cstore.Update(n1, "jkkgkg", p2)
assert.NotNil(t, err)
assertPassword(t, cstore, n1, p1, p2)
// then it changes the password when correct
err = cstore.Update(n1, p1, p2)
assert.Nil(t, err)
// p2 is now the proper one!
assertPassword(t, cstore, n1, p2, p1)
// exporting requires the proper name and passphrase
_, err = cstore.Export(n1 + ".notreal")
assert.NotNil(t, err)
_, err = cstore.Export(" " + n1)
assert.NotNil(t, err)
_, err = cstore.Export(n1 + " ")
assert.NotNil(t, err)
_, err = cstore.Export("")
assert.NotNil(t, err)
exported, err := cstore.Export(n1)
require.Nil(t, err, "%+v", err)
// import succeeds
err = cstore.Import(n2, exported)
assert.Nil(t, err)
// second import fails
err = cstore.Import(n2, exported)
assert.NotNil(t, err)
}
// TestSeedPhrase verifies restoring from a seed phrase
func TestSeedPhrase(t *testing.T) {
// make the storage with reasonable defaults
cstore := keys.New(
dbm.NewMemDB(),
words.MustLoadCodec("english"),
)
algo := keys.AlgoEd25519
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(t, err, "%+v", err)
assert.Equal(t, n1, info.Name)
assert.NotEmpty(t, seed)
// now, let us delete this key
err = cstore.Delete(n1, p1)
require.Nil(t, err, "%+v", err)
_, err = cstore.Get(n1)
require.NotNil(t, err)
// let us re-create it from the seed-phrase
newInfo, err := cstore.Recover(n2, p2, seed)
require.Nil(t, err, "%+v", err)
assert.Equal(t, n2, newInfo.Name)
assert.Equal(t, info.Address(), newInfo.Address())
assert.Equal(t, info.PubKey, newInfo.PubKey)
}
func ExampleNew() {
// Select the encryption and storage for your cryptostore
cstore := keys.New(
dbm.NewMemDB(),
words.MustLoadCodec("english"),
)
ed := keys.AlgoEd25519
sec := keys.AlgoSecp256k1
// Add keys and see they return in alphabetical order
bob, _, err := cstore.Create("Bob", "friend", ed)
if err != nil {
// this should never happen
fmt.Println(err)
} else {
// return info here just like in List
fmt.Println(bob.Name)
}
cstore.Create("Alice", "secret", sec)
cstore.Create("Carl", "mitm", ed)
info, _ := cstore.List()
for _, i := range info {
fmt.Println(i.Name)
}
// We need to use passphrase to generate a signature
tx := []byte("deadbeef")
sig, pub, err := cstore.Sign("Bob", "friend", tx)
if err != nil {
fmt.Println("don't accept real passphrase")
}
// and we can validate the signature with publically available info
binfo, _ := cstore.Get("Bob")
if !binfo.PubKey.Equals(bob.PubKey) {
fmt.Println("Get and Create return different keys")
}
if pub.Equals(binfo.PubKey) {
fmt.Println("signed by Bob")
}
if !pub.VerifyBytes(tx, sig) {
fmt.Println("invalid signature")
}
// Output:
// Bob
// Alice
// Bob
// Carl
// signed by Bob
}

+ 32
- 0
keys/keys.go View File

@ -0,0 +1,32 @@
package keys
import "fmt"
type CryptoAlgo string
const (
AlgoEd25519 = CryptoAlgo("ed25519")
AlgoSecp256k1 = CryptoAlgo("secp256k1")
)
func cryptoAlgoToByte(key CryptoAlgo) byte {
switch key {
case AlgoEd25519:
return 0x01
case AlgoSecp256k1:
return 0x02
default:
panic(fmt.Sprintf("Unexpected type key %v", key))
}
}
func byteToCryptoAlgo(b byte) CryptoAlgo {
switch b {
case 0x01:
return AlgoEd25519
case 0x02:
return AlgoSecp256k1
default:
panic(fmt.Sprintf("Unexpected type byte %X", b))
}
}

+ 99
- 0
keys/mintkey.go View File

@ -0,0 +1,99 @@
package keys
import (
"encoding/hex"
"fmt"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys/bcrypt"
)
const (
blockTypePrivKey = "TENDERMINT PRIVATE KEY"
blockTypeKeyInfo = "TENDERMINT KEY INFO"
)
func armorInfoBytes(bz []byte) string {
header := map[string]string{
"type": "Info",
"version": "0.0.0",
}
armorStr := crypto.EncodeArmor(blockTypeKeyInfo, header, bz)
return armorStr
}
func unarmorInfoBytes(armorStr string) (bz []byte, err error) {
blockType, header, bz, err := crypto.DecodeArmor(armorStr)
if err != nil {
return
}
if blockType != blockTypeKeyInfo {
err = fmt.Errorf("Unrecognized armor type: %v", blockType)
return
}
if header["version"] != "0.0.0" {
err = fmt.Errorf("Unrecognized version: %v", header["version"])
return
}
return
}
func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
header := map[string]string{
"kdf": "bcrypt",
"salt": fmt.Sprintf("%X", saltBytes),
}
armorStr := crypto.EncodeArmor(blockTypePrivKey, header, encBytes)
return armorStr
}
func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
var privKey crypto.PrivKey
blockType, header, encBytes, err := crypto.DecodeArmor(armorStr)
if err != nil {
return privKey, err
}
if blockType != blockTypePrivKey {
return privKey, fmt.Errorf("Unrecognized armor type: %v", blockType)
}
if header["kdf"] != "bcrypt" {
return privKey, fmt.Errorf("Unrecognized KDF type: %v", header["KDF"])
}
if header["salt"] == "" {
return privKey, fmt.Errorf("Missing salt bytes")
}
saltBytes, err := hex.DecodeString(header["salt"])
if err != nil {
return privKey, fmt.Errorf("Error decoding salt: %v", err.Error())
}
privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase)
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), 12) // TODO parameterize. 12 is good today (2016)
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, crypto.EncryptSymmetric(privKeyBytes, key)
}
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 12) // TODO parameterize. 12 is good today (2016)
if err != nil {
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
}
key = crypto.Sha256(key) // Get 32 bytes
privKeyBytes, err := crypto.DecryptSymmetric(encBytes, key)
if err != nil {
return privKey, err
}
privKey, err = crypto.PrivKeyFromBytes(privKeyBytes)
return privKey, err
}

+ 0
- 177
keys/storage/filestorage/main.go View File

@ -1,177 +0,0 @@
/*
package filestorage provides a secure on-disk storage of private keys and
metadata. Security is enforced by file and directory permissions, much
like standard ssh key storage.
*/
package filestorage
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
const (
BlockType = "Tendermint Light Client"
// PrivExt is the extension for private keys.
PrivExt = "tlc"
// PubExt is the extensions for public keys.
PubExt = "pub"
keyPerm = os.FileMode(0600)
// pubPerm = os.FileMode(0644)
dirPerm = os.FileMode(0700)
)
type FileStore struct {
keyDir string
}
// New creates an instance of file-based key storage with tight permissions
//
// dir should be an absolute path of a directory owner by this user. It will
// be created if it doesn't exist already.
func New(dir string) FileStore {
err := os.MkdirAll(dir, dirPerm)
if err != nil {
panic(err)
}
return FileStore{dir}
}
// assert FileStore satisfies keys.Storage
var _ keys.Storage = FileStore{}
// Put creates two files, one with the public info as json, the other
// with the (encoded) private key as gpg ascii-armor style
func (s FileStore) Put(name string, key []byte, info keys.Info) error {
pub, priv := s.nameToPaths(name)
// write public info
err := writeInfo(pub, info)
if err != nil {
return err
}
// write private info
return write(priv, name, key)
}
// Get loads the info and (encoded) private key from the directory
// It uses `name` to generate the filename, and returns an error if the
// files don't exist or are in the incorrect format
func (s FileStore) Get(name string) ([]byte, keys.Info, error) {
pub, priv := s.nameToPaths(name)
info, err := readInfo(pub)
if err != nil {
return nil, info, err
}
key, _, err := read(priv)
return key, info.Format(), err
}
// List parses the key directory for public info and returns a list of
// Info for all keys located in this directory.
func (s FileStore) List() (keys.Infos, error) {
dir, err := os.Open(s.keyDir)
if err != nil {
return nil, errors.Wrap(err, "List Keys")
}
defer dir.Close()
names, err := dir.Readdirnames(0)
if err != nil {
return nil, errors.Wrap(err, "List Keys")
}
// filter names for .pub ending and load them one by one
// half the files is a good guess for pre-allocating the slice
infos := make([]keys.Info, 0, len(names)/2)
for _, name := range names {
if strings.HasSuffix(name, PubExt) {
p := path.Join(s.keyDir, name)
info, err := readInfo(p)
if err != nil {
return nil, err
}
infos = append(infos, info.Format())
}
}
return infos, nil
}
// Delete permanently removes the public and private info for the named key
// The calling function should provide some security checks first.
func (s FileStore) Delete(name string) error {
pub, priv := s.nameToPaths(name)
err := os.Remove(priv)
if err != nil {
return errors.Wrap(err, "Deleting Private Key")
}
err = os.Remove(pub)
return errors.Wrap(err, "Deleting Public Key")
}
func (s FileStore) nameToPaths(name string) (pub, priv string) {
privName := fmt.Sprintf("%s.%s", name, PrivExt)
pubName := fmt.Sprintf("%s.%s", name, PubExt)
return path.Join(s.keyDir, pubName), path.Join(s.keyDir, privName)
}
func writeInfo(path string, info keys.Info) error {
return write(path, info.Name, info.PubKey.Bytes())
}
func readInfo(path string) (info keys.Info, err error) {
var data []byte
data, info.Name, err = read(path)
if err != nil {
return
}
pk, err := crypto.PubKeyFromBytes(data)
info.PubKey = pk
return
}
func read(path string) ([]byte, string, error) {
f, err := os.Open(path)
if err != nil {
return nil, "", errors.Wrap(err, "Reading data")
}
defer f.Close()
d, err := ioutil.ReadAll(f)
if err != nil {
return nil, "", errors.Wrap(err, "Reading data")
}
block, headers, key, err := crypto.DecodeArmor(string(d))
if err != nil {
return nil, "", errors.Wrap(err, "Invalid Armor")
}
if block != BlockType {
return nil, "", errors.Errorf("Unknown key type: %s", block)
}
return key, headers["name"], nil
}
func write(path, name string, key []byte) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm)
if err != nil {
return errors.Wrap(err, "Writing data")
}
defer f.Close()
headers := map[string]string{"name": name}
text := crypto.EncodeArmor(BlockType, headers, key)
_, err = f.WriteString(text)
return errors.Wrap(err, "Writing data")
}

+ 0
- 106
keys/storage/filestorage/main_test.go View File

@ -1,106 +0,0 @@
package filestorage
import (
"io/ioutil"
"os"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
func TestBasicCRUD(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dir, err := ioutil.TempDir("", "filestorage-test")
assert.Nil(err)
defer os.RemoveAll(dir)
store := New(dir)
name := "bar"
key := []byte("secret-key-here")
pubkey := crypto.GenPrivKeyEd25519().PubKey()
info := keys.Info{
Name: name,
PubKey: pubkey.Wrap(),
}
// No data: Get and Delete return nothing
_, _, err = store.Get(name)
assert.NotNil(err)
err = store.Delete(name)
assert.NotNil(err)
// List returns empty list
l, err := store.List()
assert.Nil(err)
assert.Empty(l)
// Putting the key in the store must work
err = store.Put(name, key, info)
assert.Nil(err)
// But a second time is a failure
err = store.Put(name, key, info)
assert.NotNil(err)
// Now, we can get and list properly
k, i, err := store.Get(name)
require.Nil(err, "%+v", err)
assert.Equal(key, k)
assert.Equal(info.Name, i.Name)
assert.Equal(info.PubKey, i.PubKey)
assert.NotEmpty(i.Address)
l, err = store.List()
require.Nil(err, "%+v", err)
assert.Equal(1, len(l))
assert.Equal(i, l[0])
// querying a non-existent key fails
_, _, err = store.Get("badname")
assert.NotNil(err)
// We can only delete once
err = store.Delete(name)
assert.Nil(err)
err = store.Delete(name)
assert.NotNil(err)
// and then Get and List don't work
_, _, err = store.Get(name)
assert.NotNil(err)
// List returns empty list
l, err = store.List()
assert.Nil(err)
assert.Empty(l)
}
func TestDirectoryHandling(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// prepare a temp dir and make sure it is not there
newDir := path.Join(os.TempDir(), "file-test-dir")
_, err := os.Open(newDir)
assert.True(os.IsNotExist(err))
defer os.RemoveAll(newDir)
// now, check with two levels deep....
parentDir := path.Join(os.TempDir(), "missing-dir")
nestedDir := path.Join(parentDir, "lots", "of", "levels", "here")
_, err = os.Open(parentDir)
assert.True(os.IsNotExist(err))
defer os.RemoveAll(parentDir)
// create a new storage, and verify it creates the directory with good permissions
for _, dir := range []string{newDir, nestedDir, newDir} {
New(dir)
d, err := os.Open(dir)
require.Nil(err)
defer d.Close()
stat, err := d.Stat()
require.Nil(err)
assert.Equal(dirPerm, stat.Mode()&os.ModePerm)
}
}

+ 0
- 68
keys/storage/memstorage/main.go View File

@ -1,68 +0,0 @@
/*
package memstorage provides a simple in-memory key store designed for
use in test cases, particularly to isolate them from the filesystem,
concurrency, and cleanup issues.
*/
package memstorage
import (
"github.com/pkg/errors"
keys "github.com/tendermint/go-crypto/keys"
)
type data struct {
info keys.Info
key []byte
}
type MemStore map[string]data
// New creates an instance of file-based key storage with tight permissions
func New() MemStore {
return MemStore{}
}
// assert MemStore satisfies keys.Storage
var _ keys.Storage = MemStore{}
// Put adds the given key, returns an error if it another key
// is already stored under this name
func (s MemStore) Put(name string, key []byte, info keys.Info) error {
if _, ok := s[name]; ok {
return errors.Errorf("Key named '%s' already exists", name)
}
s[name] = data{info, key}
return nil
}
// Get returns the key stored under the name, or returns an error if not present
func (s MemStore) Get(name string) ([]byte, keys.Info, error) {
var err error
d, ok := s[name]
if !ok {
err = errors.Errorf("Key named '%s' doesn't exist", name)
}
return d.key, d.info.Format(), err
}
// List returns the public info of all keys in the MemStore in unsorted order
func (s MemStore) List() (keys.Infos, error) {
res := make([]keys.Info, len(s))
i := 0
for _, d := range s {
res[i] = d.info.Format()
i++
}
return res, nil
}
// Delete removes the named key from the MemStore, raising an error if it
// wasn't present yet.
func (s MemStore) Delete(name string) error {
_, ok := s[name]
if !ok {
return errors.Errorf("Key named '%s' doesn't exist", name)
}
delete(s, name)
return nil
}

+ 0
- 69
keys/storage/memstorage/main_test.go View File

@ -1,69 +0,0 @@
package memstorage
import (
"testing"
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
func TestBasicCRUD(t *testing.T) {
assert := assert.New(t)
store := New()
name := "foo"
key := []byte("secret-key-here")
pubkey := crypto.GenPrivKeyEd25519().PubKey()
info := keys.Info{
Name: name,
PubKey: pubkey,
}
// No data: Get and Delete return nothing
_, _, err := store.Get(name)
assert.NotNil(err)
err = store.Delete(name)
assert.NotNil(err)
// List returns empty list
l, err := store.List()
assert.Nil(err)
assert.Empty(l)
// Putting the key in the store must work
err = store.Put(name, key, info)
assert.Nil(err)
// But a second time is a failure
err = store.Put(name, key, info)
assert.NotNil(err)
// Now, we can get and list properly
k, i, err := store.Get(name)
assert.Nil(err)
assert.Equal(key, k)
assert.Equal(info.Name, i.Name)
assert.Equal(info.PubKey, i.PubKey)
assert.NotEmpty(i.Address)
l, err = store.List()
assert.Nil(err)
assert.Equal(1, len(l))
assert.Equal(i, l[0])
// querying a non-existent key fails
_, _, err = store.Get("badname")
assert.NotNil(err)
// We can only delete once
err = store.Delete(name)
assert.Nil(err)
err = store.Delete(name)
assert.NotNil(err)
// and then Get and List don't work
_, _, err = store.Get(name)
assert.NotNil(err)
// List returns empty list
l, err = store.List()
assert.Nil(err)
assert.Empty(l)
}

+ 34
- 112
keys/types.go View File

@ -1,134 +1,56 @@
package keys
import (
"fmt"
"sort"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
data "github.com/tendermint/go-wire/data"
)
// Storage has many implementation, based on security and sharing requirements
// like disk-backed, mem-backed, vault, db, etc.
type Storage interface {
Put(name string, key []byte, info Info) error
Get(name string) (key []byte, info Info, err error)
List() (Infos, error)
Delete(name string) error
}
// Info is the public information about a key
type Info struct {
Name string `json:"name"`
Address data.Bytes `json:"address"`
PubKey crypto.PubKey `json:"pubkey"`
}
func (i *Info) Format() Info {
if !i.PubKey.Empty() {
i.Address = i.PubKey.Address()
}
return *i
}
// Infos is a wrapper to allows alphabetical sorting of the keys
type Infos []Info
func (k Infos) Len() int { return len(k) }
func (k Infos) Less(i, j int) bool { return k[i].Name < k[j].Name }
func (k Infos) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k Infos) Sort() {
if k != nil {
sort.Sort(k)
}
}
// Signable represents any transaction we wish to send to tendermint core
// These methods allow us to sign arbitrary Tx with the KeyStore
type Signable interface {
// SignBytes is the immutable data, which needs to be signed
SignBytes() []byte
// Sign will add a signature and pubkey.
//
// Depending on the Signable, one may be able to call this multiple times for multisig
// Returns error if called with invalid data or too many times
Sign(pubkey crypto.PubKey, sig crypto.Signature) error
// Signers will return the public key(s) that signed if the signature
// is valid, or an error if there is any issue with the signature,
// including if there are no signatures
Signers() ([]crypto.PubKey, error)
// TxBytes returns the transaction data as well as all signatures
// It should return an error if Sign was never called
TxBytes() ([]byte, error)
}
// Signer allows one to use a keystore to sign transactions
type Signer interface {
Sign(name, passphrase string, tx Signable) error
}
// Manager allows simple CRUD on a keystore, as an aid to signing
type Manager interface {
Signer
// 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)
// Keybase allows simple CRUD on a keystore, as an aid to signing
type Keybase interface {
// Sign some bytes
Sign(name, passphrase string, msg []byte) (crypto.Signature, crypto.PubKey, error)
// Create a new keypair
Create(name, passphrase string, algo CryptoAlgo) (info Info, seed string, err error)
// Recover takes a seedphrase and loads in the key
Recover(name, passphrase, seedphrase string) (info Info, erro error)
List() ([]Info, error)
Get(name string) (Info, error)
Update(name, oldpass, newpass string) error
Delete(name, passphrase string) error
}
/**** MockSignable allows us to view data ***/
// MockSignable lets us wrap arbitrary data with a go-crypto signature
type MockSignable struct {
Data []byte
PubKey crypto.PubKey
Signature crypto.Signature
Import(name string, armor string) (err error)
Export(name string) (armor string, err error)
}
var _ Signable = &MockSignable{}
// NewMockSignable sets the data to sign
func NewMockSignable(data []byte) *MockSignable {
return &MockSignable{Data: data}
// Info is the public information about a key
type Info struct {
Name string `json:"name"`
PubKey crypto.PubKey `json:"pubkey"`
PrivKeyArmor string `json:"privkey.armor"`
}
// TxBytes returns the full data with signatures
func (s *MockSignable) TxBytes() ([]byte, error) {
return wire.BinaryBytes(s), nil
func newInfo(name string, pub crypto.PubKey, privArmor string) Info {
return Info{
Name: name,
PubKey: pub,
PrivKeyArmor: privArmor,
}
}
// SignBytes returns the original data passed into `NewSig`
func (s *MockSignable) SignBytes() []byte {
return s.Data
// Address is a helper function to calculate the address from the pubkey
func (i Info) Address() []byte {
return i.PubKey.Address()
}
// Sign will add a signature and pubkey.
//
// Depending on the Signable, one may be able to call this multiple times for multisig
// Returns error if called with invalid data or too many times
func (s *MockSignable) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
s.PubKey = pubkey
s.Signature = sig
return nil
func (i Info) bytes() []byte {
bz, err := wire.MarshalBinary(i)
if err != nil {
panic(err)
}
return bz
}
// Signers will return the public key(s) that signed if the signature
// is valid, or an error if there is any issue with the signature,
// including if there are no signatures
func (s *MockSignable) Signers() ([]crypto.PubKey, error) {
if s.PubKey.Empty() {
return nil, fmt.Errorf("no signers")
}
if !s.PubKey.VerifyBytes(s.SignBytes(), s.Signature) {
return nil, fmt.Errorf("invalid signature")
}
return []crypto.PubKey{s.PubKey}, nil
func readInfo(bz []byte) (info Info, err error) {
err = wire.UnmarshalBinary(bz, &info)
return
}

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


keys/ecc.go → keys/words/ecc.go View File


keys/ecc_test.go → keys/words/ecc_test.go View File


keys/wordcodec.go → keys/words/wordcodec.go View File


keys/wordcodec_test.go → keys/words/wordcodec_test.go View File


keys/wordcodecbench_test.go → keys/words/wordcodecbench_test.go View File


keys/wordlist/chinese_simplified.txt → keys/words/wordlist/chinese_simplified.txt View File


keys/wordlist/english.txt → keys/words/wordlist/english.txt View File


keys/wordlist/japanese.txt → keys/words/wordlist/japanese.txt View File


keys/wordlist/spanish.txt → keys/words/wordlist/spanish.txt View File


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


+ 7
- 3
priv_key.go View File

@ -1,7 +1,7 @@
package crypto
import (
"bytes"
"crypto/subtle"
secp256k1 "github.com/btcsuite/btcd/btcec"
"github.com/tendermint/ed25519"
@ -69,9 +69,11 @@ func (privKey PrivKeyEd25519) PubKey() PubKey {
return PubKeyEd25519(pubBytes).Wrap()
}
// Equals - you probably don't need to use this.
// Runs in constant time based on length of the keys.
func (privKey PrivKeyEd25519) Equals(other PrivKey) bool {
if otherEd, ok := other.Unwrap().(PrivKeyEd25519); ok {
return bytes.Equal(privKey[:], otherEd[:])
return subtle.ConstantTimeCompare(privKey[:], otherEd[:]) == 1
} else {
return false
}
@ -156,9 +158,11 @@ func (privKey PrivKeySecp256k1) PubKey() PubKey {
return pub.Wrap()
}
// Equals - you probably don't need to use this.
// Runs in constant time based on length of the keys.
func (privKey PrivKeySecp256k1) Equals(other PrivKey) bool {
if otherSecp, ok := other.Unwrap().(PrivKeySecp256k1); ok {
return bytes.Equal(privKey[:], otherSecp[:])
return subtle.ConstantTimeCompare(privKey[:], otherSecp[:]) == 1
} else {
return false
}


+ 21
- 13
pub_key.go View File

@ -3,19 +3,27 @@ package crypto
import (
"bytes"
"crypto/sha256"
"fmt"
secp256k1 "github.com/btcsuite/btcd/btcec"
"github.com/tendermint/ed25519"
"github.com/tendermint/ed25519/extra25519"
"github.com/tendermint/go-wire"
data "github.com/tendermint/go-wire/data"
. "github.com/tendermint/tmlibs/common"
cmn "github.com/tendermint/tmlibs/common"
"golang.org/x/crypto/ripemd160"
)
// An address is a []byte, but hex-encoded even in JSON.
// []byte leaves us the option to change the address length.
// Use an alias so Unmarshal methods (with ptr receivers) are available too.
type Address = cmn.HexBytes
func PubKeyFromBytes(pubKeyBytes []byte) (pubKey PubKey, err error) {
err = wire.ReadBinaryBytes(pubKeyBytes, &pubKey)
return
if err := wire.ReadBinaryBytes(pubKeyBytes, &pubKey); err != nil {
return PubKey{}, err
}
return pubKey, nil
}
//----------------------------------------
@ -25,7 +33,7 @@ func PubKeyFromBytes(pubKeyBytes []byte) (pubKey PubKey, err error) {
// +gen wrapper:"PubKey,Impl[PubKeyEd25519,PubKeySecp256k1],ed25519,secp256k1"
type PubKeyInner interface {
AssertIsPubKeyInner()
Address() []byte
Address() Address
Bytes() []byte
KeyString() string
VerifyBytes(msg []byte, sig Signature) bool
@ -42,17 +50,17 @@ type PubKeyEd25519 [32]byte
func (pubKey PubKeyEd25519) AssertIsPubKeyInner() {}
func (pubKey PubKeyEd25519) Address() []byte {
func (pubKey PubKeyEd25519) Address() Address {
w, n, err := new(bytes.Buffer), new(int), new(error)
wire.WriteBinary(pubKey[:], w, n, err)
if *err != nil {
PanicCrisis(*err)
panic(*err)
}
// append type byte
encodedPubkey := append([]byte{TypeEd25519}, w.Bytes()...)
hasher := ripemd160.New()
hasher.Write(encodedPubkey) // does not error
return hasher.Sum(nil)
return Address(hasher.Sum(nil))
}
func (pubKey PubKeyEd25519) Bytes() []byte {
@ -93,13 +101,13 @@ func (pubKey PubKeyEd25519) ToCurve25519() *[32]byte {
}
func (pubKey PubKeyEd25519) String() string {
return Fmt("PubKeyEd25519{%X}", pubKey[:])
return fmt.Sprintf("PubKeyEd25519{%X}", pubKey[:])
}
// Must return the full bytes in hex.
// Used for map keying, etc.
func (pubKey PubKeyEd25519) KeyString() string {
return Fmt("%X", pubKey[:])
return fmt.Sprintf("%X", pubKey[:])
}
func (pubKey PubKeyEd25519) Equals(other PubKey) bool {
@ -122,14 +130,14 @@ type PubKeySecp256k1 [33]byte
func (pubKey PubKeySecp256k1) AssertIsPubKeyInner() {}
// Implements Bitcoin style addresses: RIPEMD160(SHA256(pubkey))
func (pubKey PubKeySecp256k1) Address() []byte {
func (pubKey PubKeySecp256k1) Address() Address {
hasherSHA256 := sha256.New()
hasherSHA256.Write(pubKey[:]) // does not error
sha := hasherSHA256.Sum(nil)
hasherRIPEMD160 := ripemd160.New()
hasherRIPEMD160.Write(sha) // does not error
return hasherRIPEMD160.Sum(nil)
return Address(hasherRIPEMD160.Sum(nil))
}
func (pubKey PubKeySecp256k1) Bytes() []byte {
@ -166,13 +174,13 @@ func (p *PubKeySecp256k1) UnmarshalJSON(enc []byte) error {
}
func (pubKey PubKeySecp256k1) String() string {
return Fmt("PubKeySecp256k1{%X}", pubKey[:])
return fmt.Sprintf("PubKeySecp256k1{%X}", pubKey[:])
}
// Must return the full bytes in hex.
// Used for map keying, etc.
func (pubKey PubKeySecp256k1) KeyString() string {
return Fmt("%X", pubKey[:])
return fmt.Sprintf("%X", pubKey[:])
}
func (pubKey PubKeySecp256k1) Equals(other PubKey) bool {


+ 9
- 1
pub_key_test.go View File

@ -6,6 +6,7 @@ import (
"github.com/btcsuite/btcutil/base58"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type keyData struct {
@ -26,7 +27,8 @@ func TestPubKeySecp256k1Address(t *testing.T) {
for _, d := range secpDataTable {
privB, _ := hex.DecodeString(d.priv)
pubB, _ := hex.DecodeString(d.pub)
addrB, _, _ := base58.CheckDecode(d.addr)
addrBbz, _, _ := base58.CheckDecode(d.addr)
addrB := Address(addrBbz)
var priv PrivKeySecp256k1
copy(priv[:], privB)
@ -39,3 +41,9 @@ func TestPubKeySecp256k1Address(t *testing.T) {
assert.Equal(t, addr, addrB, "Expected addresses to match")
}
}
func TestPubKeyInvalidDataProperReturnsEmpty(t *testing.T) {
pk, err := PubKeyFromBytes([]byte("foo"))
require.NotNil(t, err, "expecting a non-nil error")
require.True(t, pk.Empty(), "expecting an empty public key on error")
}

+ 4
- 1
random.go View File

@ -44,7 +44,10 @@ func CRandBytes(numBytes int) []byte {
return b
}
// RandHex(24) gives 96 bits of randomness, strong enough for most purposes.
// CRandHex returns a hex encoded string that's floor(numDigits/2) * 2 long.
//
// Note: CRandHex(24) gives 96 bits of randomness that
// are usually strong enough for most purposes.
func CRandHex(numDigits int) string {
return hex.EncodeToString(CRandBytes(numDigits / 2))
}


+ 2
- 2
signature.go View File

@ -87,8 +87,8 @@ func (sig SignatureSecp256k1) IsZero() bool { return len(sig) == 0 }
func (sig SignatureSecp256k1) String() string { return fmt.Sprintf("/%X.../", Fingerprint(sig[:])) }
func (sig SignatureSecp256k1) Equals(other Signature) bool {
if otherEd, ok := other.Unwrap().(SignatureSecp256k1); ok {
return bytes.Equal(sig[:], otherEd[:])
if otherSecp, ok := other.Unwrap().(SignatureSecp256k1); ok {
return bytes.Equal(sig[:], otherSecp[:])
} else {
return false
}


+ 26
- 4
signature_test.go View File

@ -109,8 +109,6 @@ func TestSignatureEncodings(t *testing.T) {
}
func TestWrapping(t *testing.T) {
assert := assert.New(t)
// construct some basic constructs
msg := CRandBytes(128)
priv := GenPrivKeyEd25519()
@ -126,7 +124,7 @@ func TestWrapping(t *testing.T) {
}
for _, p := range pubs {
_, ok := p.PubKeyInner.(PubKey)
assert.False(ok)
assert.False(t, ok)
}
sigs := []Signature{
@ -137,7 +135,31 @@ func TestWrapping(t *testing.T) {
}
for _, s := range sigs {
_, ok := s.SignatureInner.(Signature)
assert.False(ok)
assert.False(t, ok)
}
}
func TestPrivKeyEquality(t *testing.T) {
{
privKey := GenPrivKeySecp256k1().Wrap()
privKey2 := GenPrivKeySecp256k1().Wrap()
assert.False(t, privKey.Equals(privKey2))
assert.False(t, privKey2.Equals(privKey))
privKeyCopy := privKey // copy
assert.True(t, privKey.Equals(privKeyCopy))
assert.True(t, privKeyCopy.Equals(privKey))
}
{
privKey := GenPrivKeyEd25519().Wrap()
privKey2 := GenPrivKeyEd25519().Wrap()
assert.False(t, privKey.Equals(privKey2))
assert.False(t, privKey2.Equals(privKey))
privKeyCopy := privKey // copy
assert.True(t, privKey.Equals(privKeyCopy))
assert.True(t, privKeyCopy.Equals(privKey))
}
}

+ 1
- 1
version.go View File

@ -1,3 +1,3 @@
package crypto
const Version = "0.4.1"
const Version = "0.5.0"

Loading…
Cancel
Save