Merge pull request #73 from tendermint/develop-pre-wire
Develop pre wire
This commit is contained in:
commit
c3e19f3ea2
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -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
|
||||
|
|
115
Makefile
115
Makefile
|
@ -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/...
|
||||
########################################
|
||||
### Testing
|
||||
|
||||
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
|
||||
test:
|
||||
go test -p 1 `glide novendor`
|
||||
|
||||
codegen:
|
||||
@echo "--> regenerating all interface wrappers"
|
||||
@gen
|
||||
@echo "Done!"
|
||||
|
||||
metalinter: ensure_tools
|
||||
@gometalinter --install
|
||||
gometalinter --vendor --deadline=600s --enable-all --disable=lll ./...
|
||||
########################################
|
||||
### Formatting, linting, and vetting
|
||||
|
||||
metalinter_test: ensure_tools
|
||||
@gometalinter --install
|
||||
gometalinter --vendor --deadline=600s --disable-all \
|
||||
--enable=deadcode \
|
||||
--enable=gas \
|
||||
--enable=goconst \
|
||||
--enable=gocyclo \
|
||||
--enable=gosimple \
|
||||
--enable=ineffassign \
|
||||
--enable=interfacer \
|
||||
fmt:
|
||||
@go fmt ./...
|
||||
|
||||
metalinter:
|
||||
@echo "==> Running linter"
|
||||
gometalinter.v2 --vendor --deadline=600s --disable-all \
|
||||
--enable=maligned \
|
||||
--enable=deadcode \
|
||||
--enable=goconst \
|
||||
--enable=goimports \
|
||||
--enable=gosimple \
|
||||
--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
|
||||
|
|
|
@ -33,21 +33,21 @@ func getLedger() (*ledger.Ledger, error) {
|
|||
return device, err
|
||||
}
|
||||
|
||||
func signLedger(device *ledger.Ledger, msg []byte) (pk crypto.PubKey, sig crypto.Signature, err error) {
|
||||
func signLedger(device *ledger.Ledger, msg []byte) (pub crypto.PubKey, sig crypto.Signature, err error) {
|
||||
var resp []byte
|
||||
|
||||
packets := generateSignRequests(msg)
|
||||
for _, pack := range packets {
|
||||
resp, err = device.Exchange(pack, Timeout)
|
||||
if err != nil {
|
||||
return pk, sig, err
|
||||
return pub, sig, err
|
||||
}
|
||||
}
|
||||
|
||||
// the last call is the result we want and needs to be parsed
|
||||
key, bsig, err := parseDigest(resp)
|
||||
if err != nil {
|
||||
return pk, sig, err
|
||||
return pub, sig, err
|
||||
}
|
||||
|
||||
var b [32]byte
|
||||
|
@ -64,9 +64,9 @@ type PrivKeyLedgerEd25519 struct {
|
|||
CachedPubKey crypto.PubKey
|
||||
}
|
||||
|
||||
// NewPrivKeyLedgerEd25519Ed25519 will generate a new key and store the
|
||||
// NewPrivKeyLedgerEd25519 will generate a new key and store the
|
||||
// public key for later use.
|
||||
func NewPrivKeyLedgerEd25519Ed25519() (crypto.PrivKey, error) {
|
||||
func NewPrivKeyLedgerEd25519() (crypto.PrivKey, error) {
|
||||
var pk PrivKeyLedgerEd25519
|
||||
// getPubKey will cache the pubkey for later use,
|
||||
// this allows us to return an error early if the ledger
|
||||
|
@ -94,13 +94,13 @@ func (pk *PrivKeyLedgerEd25519) ValidateKey() error {
|
|||
// AssertIsPrivKeyInner fulfils PrivKey Interface
|
||||
func (pk *PrivKeyLedgerEd25519) AssertIsPrivKeyInner() {}
|
||||
|
||||
// Bytes fulfils pk Interface - stores the cached pubkey so we can verify
|
||||
// Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify
|
||||
// the same key when we reconnect to a ledger
|
||||
func (pk *PrivKeyLedgerEd25519) Bytes() []byte {
|
||||
return wire.BinaryBytes(pk.Wrap())
|
||||
}
|
||||
|
||||
// Sign calls the ledger and stores the pk for future use
|
||||
// Sign calls the ledger and stores the PubKey for future use
|
||||
//
|
||||
// XXX/TODO: panics if there is an error communicating with the ledger.
|
||||
//
|
|
@ -5,14 +5,14 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
asrt "github.com/stretchr/testify/assert"
|
||||
rqr "github.com/stretchr/testify/require"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func TestLedgerKeys(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
cases := []struct {
|
||||
msg, pubkey, sig string
|
||||
|
@ -76,14 +76,14 @@ func TestLedgerKeys(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRealLedger(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
if os.Getenv("WITH_LEDGER") == "" {
|
||||
t.Skip("Set WITH_LEDGER to run code on real ledger")
|
||||
}
|
||||
msg := []byte("kuhehfeohg")
|
||||
|
||||
priv, err := NewPrivKeyLedgerEd25519Ed25519()
|
||||
priv, err := NewPrivKeyLedgerEd25519()
|
||||
require.Nil(err, "%+v", err)
|
||||
pub := priv.PubKey()
|
||||
sig := priv.Sign(msg)
|
||||
|
@ -115,7 +115,7 @@ func TestRealLedger(t *testing.T) {
|
|||
// TestRealLedgerErrorHandling calls. These tests assume
|
||||
// the ledger is not plugged in....
|
||||
func TestRealLedgerErrorHandling(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require := rqr.New(t)
|
||||
|
||||
if os.Getenv("WITH_LEDGER") != "" {
|
||||
t.Skip("Skipping on WITH_LEDGER as it tests unplugged cases")
|
||||
|
@ -123,7 +123,7 @@ func TestRealLedgerErrorHandling(t *testing.T) {
|
|||
|
||||
// first, try to generate a key, must return an error
|
||||
// (no panic)
|
||||
_, err := NewPrivKeyLedgerEd25519Ed25519()
|
||||
_, err := NewPrivKeyLedgerEd25519()
|
||||
require.Error(err)
|
||||
|
||||
led := PrivKeyLedgerEd25519{} // empty
|
|
@ -5,8 +5,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
asrt "github.com/stretchr/testify/assert"
|
||||
rqr "github.com/stretchr/testify/require"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
|
@ -29,7 +30,7 @@ func parseSig(data []byte) (key crypto.Signature, err error) {
|
|||
}
|
||||
|
||||
func TestParseDigest(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
cases := []struct {
|
||||
output string
|
||||
|
@ -91,7 +92,7 @@ func toBytes(c cryptoCase) (msg, key, sig []byte, err error) {
|
|||
}
|
||||
|
||||
func TestCryptoConvert(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
cases := []cryptoCase{
|
||||
0: {
|
|
@ -18,4 +18,4 @@ dependencies:
|
|||
test:
|
||||
override:
|
||||
- "go version"
|
||||
- "cd $PROJECT_PATH && make all"
|
||||
- "cd $PROJECT_PATH && make get_tools && make all"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
11
glide.yaml
11
glide.yaml
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
package hd
|
||||
|
||||
// XXX This package doesn't work with our address scheme,
|
||||
// XXX and it probably doesn't work for our other pubkey types.
|
||||
// XXX Fix it up to be more general but compatible.
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/hmac"
|
||||
|
@ -17,19 +21,30 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
|
||||
func ComputeAddress(pubKeyHex string, chainHex string, path string, index int32) string {
|
||||
/*
|
||||
|
||||
This file implements BIP32 HD wallets.
|
||||
Note it only works for SECP256k1 keys.
|
||||
It also includes some Bitcoin specific utility functions.
|
||||
|
||||
*/
|
||||
|
||||
// ComputeBTCAddress returns the BTC address using the pubKeyHex and chainCodeHex
|
||||
// for the given path and index.
|
||||
func ComputeBTCAddress(pubKeyHex string, chainCodeHex string, path string, index int32) string {
|
||||
pubKeyBytes := DerivePublicKeyForPath(
|
||||
HexDecode(pubKeyHex),
|
||||
HexDecode(chainHex),
|
||||
HexDecode(chainCodeHex),
|
||||
fmt.Sprintf("%v/%v", path, index),
|
||||
)
|
||||
return AddrFromPubKeyBytes(pubKeyBytes)
|
||||
return BTCAddrFromPubKeyBytes(pubKeyBytes)
|
||||
}
|
||||
|
||||
// ComputePrivateKey returns the private key using the master mprivHex and chainCodeHex
|
||||
// for the given path and index.
|
||||
func ComputePrivateKey(mprivHex string, chainHex string, path string, index int32) string {
|
||||
privKeyBytes := DerivePrivateKeyForPath(
|
||||
HexDecode(mprivHex),
|
||||
|
@ -39,12 +54,14 @@ func ComputePrivateKey(mprivHex string, chainHex string, path string, index int3
|
|||
return HexEncode(privKeyBytes)
|
||||
}
|
||||
|
||||
func ComputeAddressForPrivKey(privKey string) string {
|
||||
// ComputeBTCAddressForPrivKey returns the Bitcoin address for the given privKey.
|
||||
func ComputeBTCAddressForPrivKey(privKey string) string {
|
||||
pubKeyBytes := PubKeyBytesFromPrivKeyBytes(HexDecode(privKey), true)
|
||||
return AddrFromPubKeyBytes(pubKeyBytes)
|
||||
return BTCAddrFromPubKeyBytes(pubKeyBytes)
|
||||
}
|
||||
|
||||
func SignMessage(privKey string, message string, compress bool) string {
|
||||
// SignBTCMessage signs a "Bitcoin Signed Message".
|
||||
func SignBTCMessage(privKey string, message string, compress bool) string {
|
||||
prefixBytes := []byte("Bitcoin Signed Message:\n")
|
||||
messageBytes := []byte(message)
|
||||
bytes := []byte{}
|
||||
|
@ -63,25 +80,28 @@ func SignMessage(privKey string, message string, compress bool) string {
|
|||
PublicKey: ecdsaPubKey,
|
||||
D: new(big.Int).SetBytes(privKeyBytes),
|
||||
}
|
||||
sigbytes, err := btcec.SignCompact(btcec.S256(), ecdsaPrivKey, crypto.Sha256(crypto.Sha256(bytes)), compress)
|
||||
sigbytes, err := btcec.SignCompact(btcec.S256(), ecdsaPrivKey, CalcHash256(bytes), compress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(sigbytes)
|
||||
}
|
||||
|
||||
// returns MPK, Chain, and master secret in hex.
|
||||
func ComputeMastersFromSeed(seed string) (string, string, string, string) {
|
||||
secret, chain := I64([]byte("Bitcoin seed"), []byte(seed))
|
||||
// ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex.
|
||||
func ComputeMastersFromSeed(seed string) (string, string, string) {
|
||||
key, data := []byte("Bitcoin seed"), []byte(seed)
|
||||
secret, chain := I64(key, data)
|
||||
pubKeyBytes := PubKeyBytesFromPrivKeyBytes(secret, true)
|
||||
return HexEncode(pubKeyBytes), HexEncode(secret), HexEncode(chain), HexEncode(secret)
|
||||
return HexEncode(pubKeyBytes), HexEncode(secret), HexEncode(chain)
|
||||
}
|
||||
|
||||
// ComputeWIF returns the privKey in Wallet Import Format.
|
||||
func ComputeWIF(privKey string, compress bool) string {
|
||||
return WIFFromPrivKeyBytes(HexDecode(privKey), compress)
|
||||
}
|
||||
|
||||
func ComputeTxId(rawTxHex string) string {
|
||||
// ComputeBTCTxId returns the bitcoin transaction ID.
|
||||
func ComputeBTCTxId(rawTxHex string) string {
|
||||
return HexEncode(ReverseBytes(CalcHash256(HexDecode(rawTxHex))))
|
||||
}
|
||||
|
||||
|
@ -99,7 +119,11 @@ func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) {
|
|||
}
|
||||
*/
|
||||
|
||||
func DerivePrivateKeyForPath(privKeyBytes []byte, chain []byte, path string) []byte {
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// DerivePrivateKeyForPath derives the private key by following the path from privKeyBytes,
|
||||
// using the given chainCode.
|
||||
func DerivePrivateKeyForPath(privKeyBytes []byte, chainCode []byte, path string) []byte {
|
||||
data := privKeyBytes
|
||||
parts := strings.Split(path, "/")
|
||||
for _, part := range parts {
|
||||
|
@ -115,13 +139,15 @@ func DerivePrivateKeyForPath(privKeyBytes []byte, chain []byte, path string) []b
|
|||
if i < 0 {
|
||||
panic(errors.New("index too large."))
|
||||
}
|
||||
data, chain = DerivePrivateKey(data, chain, uint32(i), prime)
|
||||
data, chainCode = DerivePrivateKey(data, chainCode, uint32(i), prime)
|
||||
//printKeyInfo(data, nil, chain)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func DerivePublicKeyForPath(pubKeyBytes []byte, chain []byte, path string) []byte {
|
||||
// DerivePublicKeyForPath derives the public key by following the path from pubKeyBytes
|
||||
// using the given chainCode.
|
||||
func DerivePublicKeyForPath(pubKeyBytes []byte, chainCode []byte, path string) []byte {
|
||||
data := pubKeyBytes
|
||||
parts := strings.Split(path, "/")
|
||||
for _, part := range parts {
|
||||
|
@ -136,36 +162,42 @@ func DerivePublicKeyForPath(pubKeyBytes []byte, chain []byte, path string) []byt
|
|||
if i < 0 {
|
||||
panic(errors.New("index too large."))
|
||||
}
|
||||
data, chain = DerivePublicKey(data, chain, uint32(i))
|
||||
//printKeyInfo(nil, data, chain)
|
||||
data, chainCode = DerivePublicKey(data, chainCode, uint32(i))
|
||||
//printKeyInfo(nil, data, chainCode)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func DerivePrivateKey(privKeyBytes []byte, chain []byte, i uint32, prime bool) ([]byte, []byte) {
|
||||
// DerivePrivateKey derives the private key with index and chainCode.
|
||||
// If prime is true, the derivation is 'hardened'.
|
||||
// It returns the new private key and new chain code.
|
||||
func DerivePrivateKey(privKeyBytes []byte, chainCode []byte, index uint32, prime bool) ([]byte, []byte) {
|
||||
var data []byte
|
||||
if prime {
|
||||
i = i | 0x80000000
|
||||
index = index | 0x80000000
|
||||
data = append([]byte{byte(0)}, privKeyBytes...)
|
||||
} else {
|
||||
public := PubKeyBytesFromPrivKeyBytes(privKeyBytes, true)
|
||||
data = public
|
||||
}
|
||||
data = append(data, uint32ToBytes(i)...)
|
||||
data2, chain2 := I64(chain, data)
|
||||
data = append(data, uint32ToBytes(index)...)
|
||||
data2, chainCode2 := I64(chainCode, data)
|
||||
x := addScalars(privKeyBytes, data2)
|
||||
return x, chain2
|
||||
return x, chainCode2
|
||||
}
|
||||
|
||||
func DerivePublicKey(pubKeyBytes []byte, chain []byte, i uint32) ([]byte, []byte) {
|
||||
// DerivePublicKey derives the public key with index and chainCode.
|
||||
// It returns the new public key and new chain code.
|
||||
func DerivePublicKey(pubKeyBytes []byte, chainCode []byte, index uint32) ([]byte, []byte) {
|
||||
data := []byte{}
|
||||
data = append(data, pubKeyBytes...)
|
||||
data = append(data, uint32ToBytes(i)...)
|
||||
data2, chain2 := I64(chain, data)
|
||||
data = append(data, uint32ToBytes(index)...)
|
||||
data2, chainCode2 := I64(chainCode, data)
|
||||
data2p := PubKeyBytesFromPrivKeyBytes(data2, true)
|
||||
return addPoints(pubKeyBytes, data2p), chain2
|
||||
return addPoints(pubKeyBytes, data2p), chainCode2
|
||||
}
|
||||
|
||||
// eliptic curve pubkey addition
|
||||
func addPoints(a []byte, b []byte) []byte {
|
||||
ap, err := btcec.ParsePubKey(a, btcec.S256())
|
||||
if err != nil {
|
||||
|
@ -184,6 +216,7 @@ func addPoints(a []byte, b []byte) []byte {
|
|||
return sum.SerializeCompressed()
|
||||
}
|
||||
|
||||
// modular big endian addition
|
||||
func addScalars(a []byte, b []byte) []byte {
|
||||
aInt := new(big.Int).SetBytes(a)
|
||||
bInt := new(big.Int).SetBytes(b)
|
||||
|
@ -200,15 +233,21 @@ func uint32ToBytes(i uint32) []byte {
|
|||
return b[:]
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// HexEncode encodes b in hex.
|
||||
func HexEncode(b []byte) string {
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// HexDecode hex decodes the str. If str is not valid hex
|
||||
// it will return an empty byte slice.
|
||||
func HexDecode(str string) []byte {
|
||||
b, _ := hex.DecodeString(str)
|
||||
return b
|
||||
}
|
||||
|
||||
// I64 returns the two halfs of the SHA512 HMAC of key and data.
|
||||
func I64(key []byte, data []byte) ([]byte, []byte) {
|
||||
mac := hmac.New(sha512.New, key)
|
||||
mac.Write(data)
|
||||
|
@ -216,27 +255,36 @@ func I64(key []byte, data []byte) ([]byte, []byte) {
|
|||
return I[:32], I[32:]
|
||||
}
|
||||
|
||||
// This returns a Bitcoin-like address.
|
||||
func AddrFromPubKeyBytes(pubKeyBytes []byte) string {
|
||||
prefix := byte(0x00) // TODO Make const or configurable
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
btcPrefixPubKeyHash = byte(0x00)
|
||||
btcPrefixPrivKey = byte(0x80)
|
||||
)
|
||||
|
||||
// BTCAddrFromPubKeyBytes returns a B58 encoded Bitcoin mainnet address.
|
||||
func BTCAddrFromPubKeyBytes(pubKeyBytes []byte) string {
|
||||
versionPrefix := btcPrefixPubKeyHash // TODO Make const or configurable
|
||||
h160 := CalcHash160(pubKeyBytes)
|
||||
h160 = append([]byte{prefix}, h160...)
|
||||
h160 = append([]byte{versionPrefix}, h160...)
|
||||
checksum := CalcHash256(h160)
|
||||
b := append(h160, checksum[:4]...)
|
||||
return base58.Encode(b)
|
||||
}
|
||||
|
||||
func AddrBytesFromPubKeyBytes(pubKeyBytes []byte) (addrBytes []byte, checksum []byte) {
|
||||
prefix := byte(0x00) // TODO Make const or configurable
|
||||
// BTCAddrBytesFromPubKeyBytes returns a hex Bitcoin mainnet address and its checksum.
|
||||
func BTCAddrBytesFromPubKeyBytes(pubKeyBytes []byte) (addrBytes []byte, checksum []byte) {
|
||||
versionPrefix := btcPrefixPubKeyHash // TODO Make const or configurable
|
||||
h160 := CalcHash160(pubKeyBytes)
|
||||
_h160 := append([]byte{prefix}, h160...)
|
||||
_h160 := append([]byte{versionPrefix}, h160...)
|
||||
checksum = CalcHash256(_h160)[:4]
|
||||
return h160, checksum
|
||||
}
|
||||
|
||||
// WIFFromPrivKeyBytes returns the privKeyBytes in Wallet Import Format.
|
||||
func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string {
|
||||
prefix := byte(0x80) // TODO Make const or configurable
|
||||
bytes := append([]byte{prefix}, privKeyBytes...)
|
||||
versionPrefix := btcPrefixPrivKey // TODO Make const or configurable
|
||||
bytes := append([]byte{versionPrefix}, privKeyBytes...)
|
||||
if compress {
|
||||
bytes = append(bytes, byte(1))
|
||||
}
|
||||
|
@ -245,6 +293,7 @@ func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string {
|
|||
return base58.Encode(bytes)
|
||||
}
|
||||
|
||||
// PubKeyBytesFromPrivKeyBytes returns the optionally compressed public key bytes.
|
||||
func PubKeyBytesFromPrivKeyBytes(privKeyBytes []byte, compress bool) (pubKeyBytes []byte) {
|
||||
x, y := btcec.S256().ScalarBaseMult(privKeyBytes)
|
||||
pub := &btcec.PublicKey{
|
||||
|
@ -259,27 +308,30 @@ func PubKeyBytesFromPrivKeyBytes(privKeyBytes []byte, compress bool) (pubKeyByte
|
|||
return pub.SerializeUncompressed()
|
||||
}
|
||||
|
||||
// Calculate the hash of hasher over buf.
|
||||
func CalcHash(buf []byte, hasher hash.Hash) []byte {
|
||||
hasher.Write(buf)
|
||||
//--------------------------------------------------------------
|
||||
|
||||
// CalcHash returns the hash of data using hasher.
|
||||
func CalcHash(data []byte, hasher hash.Hash) []byte {
|
||||
hasher.Write(data)
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
|
||||
// calculate hash160 which is ripemd160(sha256(data))
|
||||
func CalcHash160(buf []byte) []byte {
|
||||
return CalcHash(CalcHash(buf, sha256.New()), ripemd160.New())
|
||||
// CalcHash160 returns the ripemd160(sha256(data)).
|
||||
func CalcHash160(data []byte) []byte {
|
||||
return CalcHash(CalcHash(data, sha256.New()), ripemd160.New())
|
||||
}
|
||||
|
||||
// calculate hash256 which is sha256(sha256(data))
|
||||
func CalcHash256(buf []byte) []byte {
|
||||
return CalcHash(CalcHash(buf, sha256.New()), sha256.New())
|
||||
// CalcHash256 returns the sha256(sha256(data)).
|
||||
func CalcHash256(data []byte) []byte {
|
||||
return CalcHash(CalcHash(data, sha256.New()), sha256.New())
|
||||
}
|
||||
|
||||
// calculate sha512(data)
|
||||
func CalcSha512(buf []byte) []byte {
|
||||
return CalcHash(buf, sha512.New())
|
||||
// CalcSha512 returns the sha512(data).
|
||||
func CalcSha512(data []byte) []byte {
|
||||
return CalcHash(data, sha512.New())
|
||||
}
|
||||
|
||||
// ReverseBytes returns the buf in the opposite order
|
||||
func ReverseBytes(buf []byte) []byte {
|
||||
var res []byte
|
||||
if len(buf) == 0 {
|
|
@ -2,9 +2,6 @@ package hd
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
//"crypto/hmac"
|
||||
//"crypto/sha512"
|
||||
//"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -15,11 +12,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
|
||||
//"github.com/btcsuite/btcd/chaincfg"
|
||||
//"github.com/btcsuite/btcutil/hdkeychain"
|
||||
//"github.com/mndrix/btcutil"
|
||||
//"github.com/tyler-smith/go-bip32"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
|
@ -84,7 +76,7 @@ func TestHDToAddr(t *testing.T) {
|
|||
copy(pubT[:], pub)
|
||||
addr := pubT.Address()
|
||||
fmt.Printf("ADDR \t%X %X\n", addrB, addr)
|
||||
assert.Equal(t, addr, addrB, fmt.Sprintf("Expected addresses to match %d", i))
|
||||
assert.Equal(t, addr, crypto.Address(addrB), fmt.Sprintf("Expected addresses to match %d", i))
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +112,7 @@ func ifExit(err error, n int) {
|
|||
|
||||
func gocrypto(seed []byte) ([]byte, []byte, []byte) {
|
||||
|
||||
_, priv, ch, _ := ComputeMastersFromSeed(string(seed))
|
||||
_, priv, ch := ComputeMastersFromSeed(string(seed))
|
||||
|
||||
privBytes := DerivePrivateKeyForPath(
|
||||
HexDecode(priv),
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
150
keys/types.go
150
keys/types.go
|
@ -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
|
||||
// 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
|
||||
|
||||
Import(name string, armor string) (err error)
|
||||
Export(name string) (armor string, err 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"`
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
PrivKeyArmor string `json:"privkey.armor"`
|
||||
}
|
||||
|
||||
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)
|
||||
func newInfo(name string, pub crypto.PubKey, privArmor string) Info {
|
||||
return Info{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
PrivKeyArmor: privArmor,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// Address is a helper function to calculate the address from the pubkey
|
||||
func (i Info) Address() []byte {
|
||||
return i.PubKey.Address()
|
||||
}
|
||||
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
|
||||
var _ Signable = &MockSignable{}
|
||||
|
||||
// NewMockSignable sets the data to sign
|
||||
func NewMockSignable(data []byte) *MockSignable {
|
||||
return &MockSignable{Data: data}
|
||||
}
|
||||
|
||||
// TxBytes returns the full data with signatures
|
||||
func (s *MockSignable) TxBytes() ([]byte, error) {
|
||||
return wire.BinaryBytes(s), nil
|
||||
}
|
||||
|
||||
// SignBytes returns the original data passed into `NewSig`
|
||||
func (s *MockSignable) SignBytes() []byte {
|
||||
return s.Data
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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")
|
||||
func (i Info) bytes() []byte {
|
||||
bz, err := wire.MarshalBinary(i)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !s.PubKey.VerifyBytes(s.SignBytes(), s.Signature) {
|
||||
return nil, fmt.Errorf("invalid signature")
|
||||
}
|
||||
return []crypto.PubKey{s.PubKey}, nil
|
||||
return bz
|
||||
}
|
||||
|
||||
func readInfo(bz []byte) (info Info, err error) {
|
||||
err = wire.UnmarshalBinary(bz, &info)
|
||||
return
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
package keys
|
||||
package words
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
|
@ -1,9 +1,9 @@
|
|||
package keys
|
||||
package words
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
asrt "github.com/stretchr/testify/assert"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
@ -21,7 +21,7 @@ var codecs = []ECC{
|
|||
|
||||
// TestECCPasses makes sure that the AddECC/CheckECC methods are symetric
|
||||
func TestECCPasses(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert := asrt.New(t)
|
||||
|
||||
checks := append(codecs, NoECC{})
|
||||
|
||||
|
@ -41,7 +41,7 @@ func TestECCPasses(t *testing.T) {
|
|||
|
||||
// TestECCFails makes sure random data will (usually) fail the checksum
|
||||
func TestECCFails(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert := asrt.New(t)
|
||||
|
||||
checks := codecs
|
||||
attempts := 2000
|
|
@ -1,4 +1,4 @@
|
|||
package keys
|
||||
package words
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/go-crypto/keys/wordlist"
|
||||
"github.com/tendermint/go-crypto/keys/words/wordlist"
|
||||
)
|
||||
|
||||
const BankSize = 2048
|
||||
|
@ -61,7 +61,7 @@ func MustLoadCodec(bank string) *WordCodec {
|
|||
|
||||
// loadBank opens a wordlist file and returns all words inside
|
||||
func loadBank(bank string) ([]string, error) {
|
||||
filename := "keys/wordlist/" + bank + ".txt"
|
||||
filename := "keys/words/wordlist/" + bank + ".txt"
|
||||
words, err := wordlist.Asset(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
|
@ -1,16 +1,16 @@
|
|||
package keys
|
||||
package words
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
asrt "github.com/stretchr/testify/assert"
|
||||
rqr "github.com/stretchr/testify/require"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
func TestLengthCalc(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert := asrt.New(t)
|
||||
|
||||
cases := []struct {
|
||||
bytes, words int
|
||||
|
@ -50,7 +50,7 @@ func TestLengthCalc(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEncodeDecode(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
codec, err := LoadCodec("english")
|
||||
require.Nil(err, "%+v", err)
|
||||
|
@ -82,7 +82,7 @@ func TestEncodeDecode(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCheckInvalidLists(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert := asrt.New(t)
|
||||
|
||||
trivial := []string{"abc", "def"}
|
||||
short := make([]string, 1234)
|
||||
|
@ -144,7 +144,7 @@ func getDiffWord(c *WordCodec, not string) string {
|
|||
}
|
||||
|
||||
func TestCheckTypoDetection(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
banks := []string{"english", "spanish", "japanese", "chinese_simplified"}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package keys
|
||||
package words
|
||||
|
||||
import (
|
||||
"testing"
|
File diff suppressed because one or more lines are too long
10
priv_key.go
10
priv_key.go
|
@ -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
|
||||
}
|
||||
|
|
34
pub_key.go
34
pub_key.go
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,3 +1,3 @@
|
|||
package crypto
|
||||
|
||||
const Version = "0.4.1"
|
||||
const Version = "0.5.0"
|
||||
|
|
Loading…
Reference in New Issue