mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-26 23:32:15 +00:00
Keybase refactor
Same as 788cc0a79256d4fe0448e17a90217acf3d61346b but without the new go-wire
This commit is contained in:
parent
67a47e6a0b
commit
6c6d01b51c
32
glide.lock
generated
32
glide.lock
generated
@ -1,8 +1,8 @@
|
||||
hash: a21061afc44c148eb6bfeb91478b520661f3d086234383a0208d915b0cb058b8
|
||||
updated: 2017-12-27T14:29:49.534901894-08:00
|
||||
hash: 575a7b42282ded36e66490c44105140387d305b07e8e5a9e8e4b9eeb6f995e66
|
||||
updated: 2018-03-02T03:06:22.122862726-05:00
|
||||
imports:
|
||||
- name: github.com/btcsuite/btcd
|
||||
version: c7588cbf7690cd9f047a28efa2dcd8f2435a4e5e
|
||||
version: 50de9da05b50eb15658bb350f6ea24368a111ab7
|
||||
subpackages:
|
||||
- btcec
|
||||
- name: github.com/btcsuite/btcutil
|
||||
@ -10,11 +10,9 @@ imports:
|
||||
subpackages:
|
||||
- base58
|
||||
- name: github.com/ethanfrey/ledger
|
||||
version: 5e432577be582bd18a3b4a9cd75dae7a317ade36
|
||||
- name: github.com/flynn/hid
|
||||
version: ed06a31c6245d4552e8dbba7e32e5b010b875d65
|
||||
version: 23a7bb9d74bc83a862fcb4bddde24215b2295ad9
|
||||
- name: github.com/go-kit/kit
|
||||
version: e2b298466b32c7cd5579a9b9b07e968fc9d9452c
|
||||
version: 4dc7be5d2d12881735283bcab7352178e190fc71
|
||||
subpackages:
|
||||
- log
|
||||
- log/level
|
||||
@ -22,9 +20,9 @@ imports:
|
||||
- name: github.com/go-logfmt/logfmt
|
||||
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
|
||||
- name: github.com/go-stack/stack
|
||||
version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf
|
||||
version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc
|
||||
- name: github.com/gogo/protobuf
|
||||
version: 342cbe0a04158f6dcb03ca0079991a51a4248c02
|
||||
version: 1adfc126b41513cc696b209667c8656ea7aac67c
|
||||
subpackages:
|
||||
- gogoproto
|
||||
- proto
|
||||
@ -40,7 +38,7 @@ imports:
|
||||
- name: github.com/pkg/errors
|
||||
version: 645ef00459ed84a119197bfb8d8205042c6df63d
|
||||
- name: github.com/syndtr/goleveldb
|
||||
version: b89cc31ef7977104127d34c1bd31ebd1a9db2199
|
||||
version: 34011bf325bce385408353a30b101fe5e923eb6e
|
||||
subpackages:
|
||||
- leveldb
|
||||
- leveldb/cache
|
||||
@ -55,23 +53,23 @@ imports:
|
||||
- leveldb/table
|
||||
- leveldb/util
|
||||
- name: github.com/tendermint/ed25519
|
||||
version: 1f52c6f8b8a5c7908aff4497c186af344b428925
|
||||
version: d8387025d2b9d158cf4efb07e7ebf814bcce2057
|
||||
subpackages:
|
||||
- edwards25519
|
||||
- extra25519
|
||||
- name: github.com/tendermint/go-wire
|
||||
version: 27be46e25124ddf775e23317a83647ce62a93f6b
|
||||
version: 67ee274c5f9da166622f3b6e6747003b563e3742
|
||||
subpackages:
|
||||
- data
|
||||
- data/base58
|
||||
- name: github.com/tendermint/tmlibs
|
||||
version: 93c05aa8c06ef38f2b15fcdd1d91eafefda2732d
|
||||
version: 1b9b5652a199ab0be2e781393fb275b66377309d
|
||||
subpackages:
|
||||
- common
|
||||
- db
|
||||
- log
|
||||
- name: golang.org/x/crypto
|
||||
version: edd5e9b0879d13ee6970a50153d85b8fec9f7686
|
||||
version: 1875d0a70c90e57f11972aefd42276df65e895b9
|
||||
subpackages:
|
||||
- bcrypt
|
||||
- blowfish
|
||||
@ -84,17 +82,17 @@ imports:
|
||||
- salsa20/salsa
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||
version: 346938d642f2ec3594ed81d874461961cd0faa76
|
||||
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
|
||||
|
@ -17,9 +17,9 @@ import:
|
||||
subpackages:
|
||||
- extra25519
|
||||
- package: github.com/tendermint/tmlibs
|
||||
version: sdk2
|
||||
version: master
|
||||
- package: github.com/tendermint/go-wire
|
||||
version: develop
|
||||
version: bucky/new-go-wire-api
|
||||
subpackages:
|
||||
- data
|
||||
- data/base58
|
||||
|
221
keys/keybase.go
221
keys/keybase.go
@ -6,16 +6,10 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
|
||||
"github.com/tendermint/go-crypto/keys/words"
|
||||
"github.com/tendermint/go-crypto/nano"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
)
|
||||
|
||||
// XXX Lets use go-crypto/bcrypt and ascii encoding directly in here without
|
||||
// further wrappers around a store or DB.
|
||||
// Copy functions from: https://github.com/tendermint/mintkey/blob/master/cmd/mintkey/common.go
|
||||
//
|
||||
// dbKeybase combines encyption and storage implementation to provide
|
||||
// a full-featured key manager
|
||||
type dbKeybase struct {
|
||||
@ -32,58 +26,59 @@ func New(db dbm.DB, codec words.Codec) dbKeybase {
|
||||
|
||||
var _ Keybase = dbKeybase{}
|
||||
|
||||
// Create generates a new key and persists it 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, algo string) (string, Info, error) {
|
||||
// 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)
|
||||
key, err := generate(algo, secret)
|
||||
priv, err := generate(algo, secret)
|
||||
if err != nil {
|
||||
return "", Info{}, err
|
||||
return Info{}, "", err
|
||||
}
|
||||
|
||||
// encrypt and persist the key
|
||||
public := kb.writeKey(key, name, passphrase)
|
||||
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)
|
||||
seedphrase := strings.Join(words, " ")
|
||||
return seedphrase, public, err
|
||||
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, algo string, seedphrase string) (Info, error) {
|
||||
|
||||
key, err := kb.SeedToPrivKey(algo, seedphrase)
|
||||
// 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
|
||||
}
|
||||
|
||||
// Valid seedphrase. Encrypt key and persist to disk.
|
||||
public := kb.writeKey(key, name, passphrase)
|
||||
return public, nil
|
||||
}
|
||||
|
||||
// SeedToPrivKey returns the private key corresponding to a seedphrase
|
||||
// without persisting the private key.
|
||||
// TODO: enable the keybase to just hold these in memory so we can sign without persisting (?)
|
||||
func (kb dbKeybase) SeedToPrivKey(algo, seedphrase string) (crypto.PrivKey, error) {
|
||||
words := strings.Split(strings.TrimSpace(seedphrase), " ")
|
||||
secret, err := kb.codec.WordsToBytes(words)
|
||||
// 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 crypto.PrivKey{}, err
|
||||
return Info{}, err
|
||||
}
|
||||
|
||||
key, err := generate(algo, secret)
|
||||
if err != nil {
|
||||
return crypto.PrivKey{}, err
|
||||
}
|
||||
return key, nil
|
||||
// encrypt and persist key.
|
||||
public := kb.writeKey(priv, name, passphrase)
|
||||
return public, err
|
||||
}
|
||||
|
||||
// List returns the keys from storage in alphabetical order.
|
||||
@ -92,75 +87,56 @@ func (kb dbKeybase) List() ([]Info, error) {
|
||||
iter := kb.db.Iterator(nil, nil)
|
||||
defer iter.Close()
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
key := iter.Key()
|
||||
if isPub(key) {
|
||||
info, err := readInfo(iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, info)
|
||||
// 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(pubName(name))
|
||||
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.
|
||||
// TODO: what if leddger fails ?
|
||||
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pk crypto.PubKey, err error) {
|
||||
var key crypto.PrivKey
|
||||
armorStr := kb.db.Get(privName(name))
|
||||
key, err = unarmorDecryptPrivKey(string(armorStr), passphrase)
|
||||
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
|
||||
}
|
||||
|
||||
sig = key.Sign(msg)
|
||||
pk = key.PubKey()
|
||||
priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sig = priv.Sign(msg)
|
||||
pub = priv.PubKey()
|
||||
return
|
||||
}
|
||||
|
||||
// Export decodes the private key with the current password, encrypts
|
||||
// it with a secure one-time password and generates an armored private key
|
||||
// that can be Imported by another dbKeybase.
|
||||
//
|
||||
// This is designed to copy from one device to another, or provide backups
|
||||
// during version updates.
|
||||
func (kb dbKeybase) Export(name, oldpass, transferpass string) ([]byte, error) {
|
||||
armorStr := kb.db.Get(privName(name))
|
||||
key, err := unarmorDecryptPrivKey(string(armorStr), oldpass)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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)
|
||||
}
|
||||
|
||||
if transferpass == "" {
|
||||
return key.Bytes(), nil
|
||||
}
|
||||
armorBytes := encryptArmorPrivKey(key, transferpass)
|
||||
return []byte(armorBytes), nil
|
||||
return armorInfoBytes(bz), nil
|
||||
}
|
||||
|
||||
// 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 (kb dbKeybase) Import(name, newpass, transferpass string, data []byte) (err error) {
|
||||
var key crypto.PrivKey
|
||||
if transferpass == "" {
|
||||
key, err = crypto.PrivKeyFromBytes(data)
|
||||
} else {
|
||||
key, err = unarmorDecryptPrivKey(string(data), transferpass)
|
||||
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 err
|
||||
return
|
||||
}
|
||||
|
||||
kb.writeKey(key, name, newpass)
|
||||
kb.db.Set(infoKey(name), infoBytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -168,80 +144,59 @@ func (kb dbKeybase) Import(name, newpass, transferpass string, data []byte) (err
|
||||
// proper passphrase before deleting it (for security).
|
||||
func (kb dbKeybase) Delete(name, passphrase string) error {
|
||||
// verify we have the proper password before deleting
|
||||
bs := kb.db.Get(privName(name))
|
||||
_, err := unarmorDecryptPrivKey(string(bs), passphrase)
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.db.DeleteSync(pubName(name))
|
||||
kb.db.DeleteSync(privName(name))
|
||||
_, 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.
|
||||
// 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.
|
||||
// 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 {
|
||||
bs := kb.db.Get(privName(name))
|
||||
key, err := unarmorDecryptPrivKey(string(bs), oldpass)
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := unarmorDecryptPrivKey(info.PrivKeyArmor, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the public bytes and the encrypted privkey
|
||||
public := info(name, key)
|
||||
private := encryptArmorPrivKey(key, newpass)
|
||||
|
||||
// We must delete first, as Putting over an existing name returns an error.
|
||||
// Must be done atomically with the write or we could lose the key.
|
||||
batch := kb.db.NewBatch()
|
||||
batch.Delete(pubName(name))
|
||||
batch.Delete(privName(name))
|
||||
batch.Set(pubName(name), public.bytes())
|
||||
batch.Set(privName(name), []byte(private))
|
||||
batch.Write()
|
||||
|
||||
kb.writeKey(key, name, newpass)
|
||||
return nil
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------
|
||||
|
||||
func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
|
||||
// Generate the public bytes and the encrypted privkey
|
||||
public := info(name, priv)
|
||||
private := encryptArmorPrivKey(priv, passphrase)
|
||||
// generate the encrypted privkey
|
||||
privArmor := encryptArmorPrivKey(priv, passphrase)
|
||||
// make Info
|
||||
info := newInfo(name, priv.PubKey(), privArmor)
|
||||
|
||||
// Write them both
|
||||
kb.db.SetSync(pubName(name), public.bytes())
|
||||
kb.db.SetSync(privName(name), []byte(private))
|
||||
|
||||
return public
|
||||
// write them both
|
||||
kb.db.SetSync(infoKey(name), info.bytes())
|
||||
return info
|
||||
}
|
||||
|
||||
// TODO: use a `type TypeKeyAlgo string` (?)
|
||||
func generate(algo string, secret []byte) (crypto.PrivKey, error) {
|
||||
func generate(algo CryptoAlgo, secret []byte) (crypto.PrivKey, error) {
|
||||
switch algo {
|
||||
case crypto.NameEd25519:
|
||||
case AlgoEd25519:
|
||||
return crypto.GenPrivKeyEd25519FromSecret(secret).Wrap(), nil
|
||||
case crypto.NameSecp256k1:
|
||||
case AlgoSecp256k1:
|
||||
return crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap(), nil
|
||||
case nano.NameLedgerEd25519:
|
||||
return nano.NewPrivKeyLedgerEd25519()
|
||||
default:
|
||||
err := errors.Errorf("Cannot generate keys for algorithm: %s", algo)
|
||||
return crypto.PrivKey{}, err
|
||||
}
|
||||
}
|
||||
|
||||
func pubName(name string) []byte {
|
||||
return []byte(fmt.Sprintf("%s.pub", name))
|
||||
}
|
||||
|
||||
func privName(name string) []byte {
|
||||
return []byte(fmt.Sprintf("%s.priv", name))
|
||||
}
|
||||
|
||||
func isPub(name []byte) bool {
|
||||
return strings.HasSuffix(string(name), ".pub")
|
||||
func infoKey(name string) []byte {
|
||||
return []byte(fmt.Sprintf("%s.info", name))
|
||||
}
|
||||
|
@ -2,24 +2,20 @@ package keys_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
asrt "github.com/stretchr/testify/assert"
|
||||
rqr "github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-crypto/keys/words"
|
||||
"github.com/tendermint/go-crypto/nano"
|
||||
)
|
||||
|
||||
// TestKeyManagement makes sure we can manipulate these keys well
|
||||
func TestKeyManagement(t *testing.T) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := keys.New(
|
||||
@ -27,84 +23,83 @@ func TestKeyManagement(t *testing.T) {
|
||||
words.MustLoadCodec("english"),
|
||||
)
|
||||
|
||||
algo := crypto.NameEd25519
|
||||
algo := keys.AlgoEd25519
|
||||
n1, n2, n3 := "personal", "business", "other"
|
||||
p1, p2 := "1234", "really-secure!@#$"
|
||||
|
||||
// Check empty state
|
||||
l, err := cstore.List()
|
||||
require.Nil(err)
|
||||
assert.Empty(l)
|
||||
require.Nil(t, err)
|
||||
assert.Empty(t, 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)
|
||||
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(err)
|
||||
require.Nil(t, err)
|
||||
|
||||
// we can get these keys
|
||||
i2, err := cstore.Get(n2)
|
||||
assert.Nil(err)
|
||||
assert.Nil(t, err)
|
||||
_, err = cstore.Get(n3)
|
||||
assert.NotNil(err)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// list shows them in order
|
||||
keyS, err := cstore.List()
|
||||
require.Nil(err)
|
||||
require.Equal(2, len(keyS))
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 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)
|
||||
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(err)
|
||||
require.NotNil(t, err)
|
||||
err = cstore.Delete(n1, p1)
|
||||
require.Nil(err)
|
||||
require.Nil(t, err)
|
||||
keyS, err = cstore.List()
|
||||
require.Nil(err)
|
||||
assert.Equal(1, len(keyS))
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(keyS))
|
||||
_, err = cstore.Get(n1)
|
||||
assert.NotNil(err)
|
||||
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(err)
|
||||
// assert.NotNil(t, err)
|
||||
// err = cstore.Sign(n2, p2, tx)
|
||||
// assert.Nil(err, "%+v", err)
|
||||
// assert.Nil(t, err, "%+v", err)
|
||||
// sigs, err := tx.Signers()
|
||||
// assert.Nil(err, "%+v", err)
|
||||
// if assert.Equal(1, len(sigs)) {
|
||||
// assert.Equal(i2.PubKey, sigs[0])
|
||||
// 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) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := keys.New(
|
||||
dbm.NewMemDB(),
|
||||
words.MustLoadCodec("english"),
|
||||
)
|
||||
algo := crypto.NameSecp256k1
|
||||
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(err)
|
||||
i1, _, err := cstore.Create(n1, p1, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, i2, err := cstore.Create(n2, p2, algo)
|
||||
require.Nil(err)
|
||||
i2, _, err := cstore.Create(n2, p2, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("my first message")
|
||||
@ -112,20 +107,20 @@ func TestSignVerify(t *testing.T) {
|
||||
|
||||
// try signing both data with both keys...
|
||||
s11, pub1, err := cstore.Sign(n1, p1, d1)
|
||||
require.Nil(err)
|
||||
require.Equal(i1.PubKey, pub1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.PubKey, pub1)
|
||||
|
||||
s12, pub1, err := cstore.Sign(n1, p1, d2)
|
||||
require.Nil(err)
|
||||
require.Equal(i1.PubKey, pub1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.PubKey, pub1)
|
||||
|
||||
s21, pub2, err := cstore.Sign(n2, p2, d1)
|
||||
require.Nil(err)
|
||||
require.Equal(i2.PubKey, pub2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.PubKey, pub2)
|
||||
|
||||
s22, pub2, err := cstore.Sign(n2, p2, d2)
|
||||
require.Nil(err)
|
||||
require.Equal(i2.PubKey, pub2)
|
||||
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 {
|
||||
@ -148,17 +143,17 @@ func TestSignVerify(t *testing.T) {
|
||||
|
||||
for i, tc := range cases {
|
||||
valid := tc.key.VerifyBytes(tc.data, tc.sig)
|
||||
assert.Equal(tc.valid, valid, "%d", i)
|
||||
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) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
if os.Getenv("WITH_LEDGER") == "" {
|
||||
t.Skip("Set WITH_LEDGER to run code on real ledger")
|
||||
}
|
||||
@ -172,19 +167,19 @@ func TestSignWithLedger(t *testing.T) {
|
||||
p := "hard2hack"
|
||||
|
||||
// create a nano user
|
||||
_, c, err := cstore.Create(n, p, nano.NameLedgerEd25519)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(c.Name, n)
|
||||
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(ok)
|
||||
require.True(t, ok)
|
||||
|
||||
// make sure we can get it back
|
||||
info, err := cstore.Get(n)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(info.Name, n)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
assert.Equal(t, info.Key, n)
|
||||
key := info.PubKey
|
||||
require.False(key.Empty())
|
||||
require.True(key.Equals(c.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")
|
||||
@ -192,56 +187,64 @@ func TestSignWithLedger(t *testing.T) {
|
||||
|
||||
// try signing both data with the ledger...
|
||||
s1, pub, err := cstore.Sign(n, p, d1)
|
||||
require.Nil(err)
|
||||
require.Equal(info.PubKey, pub)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, info.PubKey, pub)
|
||||
|
||||
s2, pub, err := cstore.Sign(n, p, d2)
|
||||
require.Nil(err)
|
||||
require.Equal(info.PubKey, pub)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, info.PubKey, pub)
|
||||
|
||||
// now, let's check those signatures work
|
||||
assert.True(key.VerifyBytes(d1, s1))
|
||||
assert.True(key.VerifyBytes(d2, s2))
|
||||
assert.True(t, key.VerifyBytes(d1, s1))
|
||||
assert.True(t, key.VerifyBytes(d2, s2))
|
||||
// and mismatched signatures don't
|
||||
assert.False(key.VerifyBytes(d1, s2))
|
||||
assert.False(t, key.VerifyBytes(d1, s2))
|
||||
}
|
||||
*/
|
||||
|
||||
func assertPassword(assert *asrt.Assertions, cstore keys.Keybase, name, pass, badpass string) {
|
||||
func assertPassword(t *testing.T, cstore keys.Keybase, name, pass, badpass string) {
|
||||
err := cstore.Update(name, badpass, pass)
|
||||
assert.NotNil(err)
|
||||
assert.NotNil(t, err)
|
||||
err = cstore.Update(name, pass, pass)
|
||||
assert.Nil(err, "%+v", err)
|
||||
assert.Nil(t, err, "%+v", err)
|
||||
}
|
||||
|
||||
// TestImportUnencrypted tests accepting raw priv keys bytes as input
|
||||
func TestImportUnencrypted(t *testing.T) {
|
||||
require := rqr.New(t)
|
||||
// TestExportImport tests exporting and importing keys.
|
||||
func TestExportImport(t *testing.T) {
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
db := dbm.NewMemDB()
|
||||
cstore := keys.New(
|
||||
dbm.NewMemDB(),
|
||||
db,
|
||||
words.MustLoadCodec("english"),
|
||||
)
|
||||
|
||||
key := crypto.GenPrivKeyEd25519FromSecret(cmn.RandBytes(16)).Wrap()
|
||||
info, _, err := cstore.Create("john", "passphrase", keys.AlgoEd25519)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, info.Name, "john")
|
||||
addr := info.PubKey.Address()
|
||||
|
||||
addr := key.PubKey().Address()
|
||||
name := "john"
|
||||
pass := "top-secret"
|
||||
john, err := cstore.Get("john")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, john.Name, "john")
|
||||
assert.Equal(t, john.PubKey.Address(), addr)
|
||||
|
||||
// import raw bytes
|
||||
err := cstore.Import(name, pass, "", key.Bytes())
|
||||
require.Nil(err, "%+v", err)
|
||||
armor, err := cstore.Export("john")
|
||||
assert.Nil(t, err)
|
||||
|
||||
// make sure the address matches
|
||||
info, err := cstore.Get(name)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.EqualValues(addr, info.Address())
|
||||
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) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := keys.New(
|
||||
@ -249,42 +252,49 @@ func TestAdvancedKeyManagement(t *testing.T) {
|
||||
words.MustLoadCodec("english"),
|
||||
)
|
||||
|
||||
algo := crypto.NameSecp256k1
|
||||
algo := keys.AlgoSecp256k1
|
||||
n1, n2 := "old-name", "new name"
|
||||
p1, p2, p3, pt := "1234", "foobar", "ding booms!", "really-secure!@#$"
|
||||
p1, p2 := "1234", "foobar"
|
||||
|
||||
// make sure key works with initial password
|
||||
_, _, err := cstore.Create(n1, p1, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
assertPassword(assert, cstore, n1, p1, p2)
|
||||
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(err)
|
||||
assertPassword(assert, cstore, n1, p1, 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(err)
|
||||
assert.Nil(t, err)
|
||||
// p2 is now the proper one!
|
||||
assertPassword(assert, cstore, n1, p2, p1)
|
||||
assertPassword(t, 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)
|
||||
_, 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 fails on bad transfer pass
|
||||
err = cstore.Import(n2, p3, p2, exported)
|
||||
assert.NotNil(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) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := keys.New(
|
||||
@ -292,28 +302,28 @@ func TestSeedPhrase(t *testing.T) {
|
||||
words.MustLoadCodec("english"),
|
||||
)
|
||||
|
||||
algo := crypto.NameEd25519
|
||||
algo := keys.AlgoEd25519
|
||||
n1, n2 := "lost-key", "found-again"
|
||||
p1, p2 := "1234", "foobar"
|
||||
|
||||
// make sure key works with initial password
|
||||
seed, info, err := cstore.Create(n1, p1, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(n1, info.Name)
|
||||
assert.NotEmpty(seed)
|
||||
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(err, "%+v", err)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
_, err = cstore.Get(n1)
|
||||
require.NotNil(err)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// let us re-create it from the seed-phrase
|
||||
newInfo, err := cstore.Recover(n2, p2, algo, seed)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(n2, newInfo.Name)
|
||||
assert.Equal(info.Address(), newInfo.Address())
|
||||
assert.Equal(info.PubKey, newInfo.PubKey)
|
||||
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() {
|
||||
@ -322,11 +332,11 @@ func ExampleNew() {
|
||||
dbm.NewMemDB(),
|
||||
words.MustLoadCodec("english"),
|
||||
)
|
||||
ed := crypto.NameEd25519
|
||||
sec := crypto.NameSecp256k1
|
||||
ed := keys.AlgoEd25519
|
||||
sec := keys.AlgoSecp256k1
|
||||
|
||||
// Add keys and see they return in alphabetical order
|
||||
_, bob, err := cstore.Create("Bob", "friend", ed)
|
||||
bob, _, err := cstore.Create("Bob", "friend", ed)
|
||||
if err != nil {
|
||||
// this should never happen
|
||||
fmt.Println(err)
|
||||
|
32
keys/keys.go
Normal file
32
keys/keys.go
Normal file
@ -0,0 +1,32 @@
|
||||
package keys
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CryptoAlgo string
|
||||
|
||||
const (
|
||||
AlgoEd25519 = CryptoAlgo("ed25519")
|
||||
AlgoSecp256k1 = CryptoAlgo("secp256k1")
|
||||
)
|
||||
|
||||
func cryptoAlgoToByte(key CryptoAlgo) byte {
|
||||
switch key {
|
||||
case AlgoEd25519:
|
||||
return 0x01
|
||||
case AlgoSecp256k1:
|
||||
return 0x02
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected type key %v", key))
|
||||
}
|
||||
}
|
||||
|
||||
func byteToCryptoAlgo(b byte) CryptoAlgo {
|
||||
switch b {
|
||||
case 0x01:
|
||||
return AlgoEd25519
|
||||
case 0x02:
|
||||
return AlgoSecp256k1
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected type byte %X", b))
|
||||
}
|
||||
}
|
@ -12,8 +12,34 @@ import (
|
||||
|
||||
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{
|
||||
|
@ -1,15 +1,40 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
PrivKeyArmor string `json:"privkey.armor"`
|
||||
}
|
||||
|
||||
func newInfo(name string, pub crypto.PubKey, privArmor string) Info {
|
||||
return Info{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
PrivKeyArmor: privArmor,
|
||||
}
|
||||
}
|
||||
|
||||
// Address is a helper function to calculate the address from the pubkey
|
||||
@ -18,31 +43,14 @@ func (i Info) Address() []byte {
|
||||
}
|
||||
|
||||
func (i Info) bytes() []byte {
|
||||
return wire.BinaryBytes(i)
|
||||
bz, err := wire.MarshalBinary(i)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
func readInfo(bs []byte) (info Info, err error) {
|
||||
err = wire.ReadBinaryBytes(bs, &info)
|
||||
func readInfo(bz []byte) (info Info, err error) {
|
||||
err = wire.UnmarshalBinary(bz, &info)
|
||||
return
|
||||
}
|
||||
|
||||
func info(name string, privKey crypto.PrivKey) Info {
|
||||
return Info{
|
||||
Name: name,
|
||||
PubKey: privKey.PubKey(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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, algo string) (seedphrase string, _ Info, _ error)
|
||||
// Recover takes a seedphrase and loads in the key
|
||||
Recover(name, passphrase, algo, seedphrase string) (Info, error)
|
||||
List() ([]Info, error)
|
||||
Get(name string) (Info, error)
|
||||
Update(name, oldpass, newpass string) error
|
||||
Delete(name, passphrase string) error
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user