mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-26 01:44:16 +00:00
Unify local and external keys in keybase interface (#117)
* Return errors on priv.Sign(), priv.PubKey() * Add CreateLedger, CreateOffline * Add switch on .Sign() for Ledger wallets * Add offline signing switch on .Sign() * Use MustUnmarshalBinaryBare() * Add confirmation to delete offline/Ledger keys * Lowercase error message * Add human-readable .GetType() function to Info interface * Rename CryptoAlgo => SignAlgo * assert.Nil(t, err) => assert.NoError(t, err)
This commit is contained in:
committed by
Ismail Khoffi
parent
f6c960c3d3
commit
c21f67c5af
+128
-46
@@ -1,7 +1,9 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -26,23 +28,23 @@ func New(db dbm.DB, codec words.Codec) dbKeybase {
|
||||
|
||||
var _ Keybase = dbKeybase{}
|
||||
|
||||
// Create generates a new key and persists it to storage, encrypted
|
||||
// CreateMnemonic 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) {
|
||||
func (kb dbKeybase) CreateMnemonic(name, passphrase string, algo SignAlgo) (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
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// encrypt and persist the key
|
||||
info := kb.writeKey(priv, name, passphrase)
|
||||
info := kb.writeLocalKey(priv, name, passphrase)
|
||||
|
||||
// we append the type byte to the serialized secret to help with
|
||||
// recovery
|
||||
@@ -56,6 +58,29 @@ func (kb dbKeybase) Create(name, passphrase string, algo CryptoAlgo) (Info, stri
|
||||
return info, seed, err
|
||||
}
|
||||
|
||||
// CreateLedger creates a new locally-stored reference to a Ledger keypair
|
||||
// It returns the created key info and an error if the Ledger could not be queried
|
||||
func (kb dbKeybase) CreateLedger(name string, path crypto.DerivationPath, algo SignAlgo) (Info, error) {
|
||||
if algo != AlgoSecp256k1 {
|
||||
return nil, fmt.Errorf("Only secp256k1 is supported for Ledger devices")
|
||||
}
|
||||
priv, err := crypto.NewPrivKeyLedgerSecp256k1(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub, err := priv.PubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kb.writeLedgerKey(pub, path, name), nil
|
||||
}
|
||||
|
||||
// CreateOffline creates a new reference to an offline keypair
|
||||
// It returns the created key info
|
||||
func (kb dbKeybase) CreateOffline(name string, pub crypto.PubKey) (Info, error) {
|
||||
return kb.writeOfflineKey(pub, name), nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -63,22 +88,22 @@ 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
|
||||
return nil, 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)
|
||||
algo := byteToSignAlgo(typ)
|
||||
priv, err := generate(algo, secret)
|
||||
if err != nil {
|
||||
return Info{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// encrypt and persist key.
|
||||
public := kb.writeKey(priv, name, passphrase)
|
||||
return public, err
|
||||
public := kb.writeLocalKey(priv, name, passphrase)
|
||||
return public, nil
|
||||
}
|
||||
|
||||
// List returns the keys from storage in alphabetical order.
|
||||
@@ -87,7 +112,6 @@ func (kb dbKeybase) List() ([]Info, error) {
|
||||
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
|
||||
@@ -110,17 +134,46 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signat
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if info.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return
|
||||
var priv crypto.PrivKey
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
if linfo.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return
|
||||
}
|
||||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case ledgerInfo:
|
||||
linfo := info.(ledgerInfo)
|
||||
priv, err = crypto.NewPrivKeyLedgerSecp256k1(linfo.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case offlineInfo:
|
||||
linfo := info.(offlineInfo)
|
||||
fmt.Printf("Bytes to sign:\n%s", msg)
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("\nEnter Amino-encoded signature:\n")
|
||||
// Will block until user inputs the signature
|
||||
signed, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cdc.MustUnmarshalBinary([]byte(signed), sig)
|
||||
return sig, linfo.GetPubKey(), nil
|
||||
}
|
||||
priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
|
||||
sig, err = priv.Sign(msg)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, nil, err
|
||||
}
|
||||
sig = priv.Sign(msg)
|
||||
pub = priv.PubKey()
|
||||
return
|
||||
pub, err = priv.PubKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return sig, pub, nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Export(name string) (armor string, err error) {
|
||||
@@ -143,7 +196,7 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return armorPubKeyBytes(info.PubKey.Bytes()), nil
|
||||
return armorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||
@@ -175,23 +228,37 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kb.writePubKey(pubKey, name)
|
||||
kb.writeOfflineKey(pubKey, name)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes key forever, but we must present the
|
||||
// proper passphrase before deleting it (for security).
|
||||
// A passphrase of 'yes' is used to delete stored
|
||||
// references to offline and Ledger / HW wallet keys
|
||||
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
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
_, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.db.DeleteSync(infoKey(name))
|
||||
return nil
|
||||
case ledgerInfo:
|
||||
case offlineInfo:
|
||||
if passphrase != "yes" {
|
||||
return fmt.Errorf("enter exactly 'yes' to delete the key")
|
||||
}
|
||||
kb.db.DeleteSync(infoKey(name))
|
||||
return nil
|
||||
}
|
||||
kb.db.DeleteSync(infoKey(name))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -205,36 +272,51 @@ func (kb dbKeybase) Update(name, oldpass, newpass string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := unarmorDecryptPrivKey(info.PrivKeyArmor, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.writeLocalKey(key, name, newpass)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("Locally stored key required")
|
||||
}
|
||||
|
||||
kb.writeKey(key, name, newpass)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writePubKey(pub crypto.PubKey, name string) Info {
|
||||
// make Info
|
||||
info := newInfo(name, pub, "")
|
||||
|
||||
// write them both
|
||||
kb.db.SetSync(infoKey(name), info.bytes())
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
|
||||
// generate the encrypted privkey
|
||||
func (kb dbKeybase) writeLocalKey(priv crypto.PrivKey, name, passphrase string) Info {
|
||||
// encrypt private key using passphrase
|
||||
privArmor := encryptArmorPrivKey(priv, passphrase)
|
||||
// make Info
|
||||
info := newInfo(name, priv.PubKey(), privArmor)
|
||||
|
||||
// write them both
|
||||
kb.db.SetSync(infoKey(name), info.bytes())
|
||||
pub, err := priv.PubKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
info := newLocalInfo(name, pub, privArmor)
|
||||
kb.writeInfo(info, name)
|
||||
return info
|
||||
}
|
||||
|
||||
func generate(algo CryptoAlgo, secret []byte) (crypto.PrivKey, error) {
|
||||
func (kb dbKeybase) writeLedgerKey(pub crypto.PubKey, path crypto.DerivationPath, name string) Info {
|
||||
info := newLedgerInfo(name, pub, path)
|
||||
kb.writeInfo(info, name)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeOfflineKey(pub crypto.PubKey, name string) Info {
|
||||
info := newOfflineInfo(name, pub)
|
||||
kb.writeInfo(info, name)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeInfo(info Info, name string) {
|
||||
// write the info by key
|
||||
kb.db.SetSync(infoKey(name), writeInfo(info))
|
||||
}
|
||||
|
||||
func generate(algo SignAlgo, secret []byte) (crypto.PrivKey, error) {
|
||||
switch algo {
|
||||
case AlgoEd25519:
|
||||
return crypto.GenPrivKeyEd25519FromSecret(secret), nil
|
||||
|
||||
+82
-65
@@ -35,15 +35,15 @@ func TestKeyManagement(t *testing.T) {
|
||||
// create some keys
|
||||
_, err = cstore.Get(n1)
|
||||
assert.NotNil(t, err)
|
||||
i, _, err := cstore.Create(n1, p1, algo)
|
||||
require.Equal(t, n1, i.Name)
|
||||
i, _, err := cstore.CreateMnemonic(n1, p1, algo)
|
||||
require.Equal(t, n1, i.GetName())
|
||||
require.Nil(t, err)
|
||||
_, _, err = cstore.Create(n2, p2, algo)
|
||||
_, _, err = cstore.CreateMnemonic(n2, p2, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
// we can get these keys
|
||||
i2, err := cstore.Get(n2)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
_, err = cstore.Get(n3)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
@@ -52,9 +52,9 @@ func TestKeyManagement(t *testing.T) {
|
||||
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)
|
||||
assert.Equal(t, n2, keyS[0].GetName())
|
||||
assert.Equal(t, n1, keyS[1].GetName())
|
||||
assert.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
|
||||
|
||||
// deleting a key removes it
|
||||
err = cstore.Delete("bad name", "foo")
|
||||
@@ -67,6 +67,26 @@ func TestKeyManagement(t *testing.T) {
|
||||
_, err = cstore.Get(n1)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// create an offline key
|
||||
o1 := "offline"
|
||||
priv1 := crypto.GenPrivKeyEd25519()
|
||||
pub1, err := priv1.PubKey()
|
||||
require.Nil(t, err)
|
||||
i, err = cstore.CreateOffline(o1, pub1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, pub1, i.GetPubKey())
|
||||
require.Equal(t, o1, i.GetName())
|
||||
keyS, err = cstore.List()
|
||||
require.Equal(t, 2, len(keyS))
|
||||
|
||||
// delete the offline key
|
||||
err = cstore.Delete(o1, "no")
|
||||
require.NotNil(t, err)
|
||||
err = cstore.Delete(o1, "yes")
|
||||
require.Nil(t, err)
|
||||
keyS, err = cstore.List()
|
||||
require.Equal(t, 1, len(keyS))
|
||||
|
||||
// make sure that it only signs with the right password
|
||||
// tx := mock.NewSig([]byte("mytransactiondata"))
|
||||
// err = cstore.Sign(n2, p1, tx)
|
||||
@@ -95,19 +115,18 @@ func TestSignVerify(t *testing.T) {
|
||||
p1, p2, p3 := "1234", "foobar", "foobar"
|
||||
|
||||
// create two users and get their info
|
||||
i1, _, err := cstore.Create(n1, p1, algo)
|
||||
i1, _, err := cstore.CreateMnemonic(n1, p1, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
i2, _, err := cstore.Create(n2, p2, algo)
|
||||
i2, _, err := cstore.CreateMnemonic(n2, p2, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Import a public key
|
||||
armor, err := cstore.ExportPubKey(n2)
|
||||
require.Nil(t, err)
|
||||
cstore.ImportPubKey(n3, armor)
|
||||
i3, err := cstore.Get(n3)
|
||||
_, err = cstore.Get(n3)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i3.PrivKeyArmor, "")
|
||||
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("my first message")
|
||||
@@ -117,19 +136,19 @@ func TestSignVerify(t *testing.T) {
|
||||
// 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)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s12, pub1, err := cstore.Sign(n1, p1, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.PubKey, pub1)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s21, pub2, err := cstore.Sign(n2, p2, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.PubKey, pub2)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
s22, pub2, err := cstore.Sign(n2, p2, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.PubKey, pub2)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
// let's try to validate and make sure it only works when everything is proper
|
||||
cases := []struct {
|
||||
@@ -139,15 +158,15 @@ func TestSignVerify(t *testing.T) {
|
||||
valid bool
|
||||
}{
|
||||
// proper matches
|
||||
{i1.PubKey, d1, s11, true},
|
||||
{i1.GetPubKey(), 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},
|
||||
{i1.GetPubKey(), d2, s11, false},
|
||||
{i2.GetPubKey(), d1, s11, false},
|
||||
{i1.GetPubKey(), d1, s21, false},
|
||||
// make sure other successes
|
||||
{i1.PubKey, d2, s12, true},
|
||||
{i2.PubKey, d1, s21, true},
|
||||
{i2.PubKey, d2, s22, true},
|
||||
{i1.GetPubKey(), d2, s12, true},
|
||||
{i2.GetPubKey(), d1, s21, true},
|
||||
{i2.GetPubKey(), d2, s22, true},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
@@ -232,27 +251,27 @@ func TestExportImport(t *testing.T) {
|
||||
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()
|
||||
info, _, err := cstore.CreateMnemonic("john", "passphrase", keys.AlgoEd25519)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, info.GetName(), "john")
|
||||
addr := info.GetPubKey().Address()
|
||||
|
||||
john, err := cstore.Get("john")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, john.Name, "john")
|
||||
assert.Equal(t, john.PubKey.Address(), addr)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, john.GetName(), "john")
|
||||
assert.Equal(t, john.GetPubKey().Address(), addr)
|
||||
|
||||
armor, err := cstore.Export("john")
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = cstore.Import("john2", armor)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
john2, err := cstore.Get("john2")
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, john.PubKey.Address(), addr)
|
||||
assert.Equal(t, john.Name, "john")
|
||||
assert.Equal(t, john.GetPubKey().Address(), addr)
|
||||
assert.Equal(t, john.GetName(), "john")
|
||||
assert.Equal(t, john, john2)
|
||||
}
|
||||
|
||||
@@ -265,33 +284,31 @@ func TestExportImportPubKey(t *testing.T) {
|
||||
)
|
||||
|
||||
// Create a private-public key pair and ensure consistency
|
||||
info, _, err := cstore.Create("john", "passphrase", keys.AlgoEd25519)
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, info.PrivKeyArmor, "")
|
||||
assert.Equal(t, info.Name, "john")
|
||||
addr := info.PubKey.Address()
|
||||
info, _, err := cstore.CreateMnemonic("john", "passphrase", keys.AlgoEd25519)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, info.GetName(), "john")
|
||||
addr := info.GetPubKey().Address()
|
||||
john, err := cstore.Get("john")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, john.Name, "john")
|
||||
assert.Equal(t, john.PubKey.Address(), addr)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, john.GetName(), "john")
|
||||
assert.Equal(t, john.GetPubKey().Address(), addr)
|
||||
|
||||
// Export the public key only
|
||||
armor, err := cstore.ExportPubKey("john")
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
// Import it under a different name
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
// Ensure consistency
|
||||
john2, err := cstore.Get("john-pubkey-only")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, john2.PrivKeyArmor, "")
|
||||
assert.NoError(t, err)
|
||||
// Compare the public keys
|
||||
assert.True(t, john.PubKey.Equals(john2.PubKey))
|
||||
assert.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
|
||||
// Ensure the original key hasn't changed
|
||||
john, err = cstore.Get("john")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, john.PubKey.Address(), addr)
|
||||
assert.Equal(t, john.Name, "john")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, john.GetPubKey().Address(), addr)
|
||||
assert.Equal(t, john.GetName(), "john")
|
||||
|
||||
// Ensure keys cannot be overwritten
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
@@ -312,7 +329,7 @@ func TestAdvancedKeyManagement(t *testing.T) {
|
||||
p1, p2 := "1234", "foobar"
|
||||
|
||||
// make sure key works with initial password
|
||||
_, _, err := cstore.Create(n1, p1, algo)
|
||||
_, _, err := cstore.CreateMnemonic(n1, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
assertPassword(t, cstore, n1, p1, p2)
|
||||
|
||||
@@ -323,7 +340,7 @@ func TestAdvancedKeyManagement(t *testing.T) {
|
||||
|
||||
// then it changes the password when correct
|
||||
err = cstore.Update(n1, p1, p2)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
// p2 is now the proper one!
|
||||
assertPassword(t, cstore, n1, p2, p1)
|
||||
|
||||
@@ -341,7 +358,7 @@ func TestAdvancedKeyManagement(t *testing.T) {
|
||||
|
||||
// import succeeds
|
||||
err = cstore.Import(n2, exported)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// second import fails
|
||||
err = cstore.Import(n2, exported)
|
||||
@@ -362,9 +379,9 @@ func TestSeedPhrase(t *testing.T) {
|
||||
p1, p2 := "1234", "foobar"
|
||||
|
||||
// make sure key works with initial password
|
||||
info, seed, err := cstore.Create(n1, p1, algo)
|
||||
info, seed, err := cstore.CreateMnemonic(n1, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
assert.Equal(t, n1, info.Name)
|
||||
assert.Equal(t, n1, info.GetName())
|
||||
assert.NotEmpty(t, seed)
|
||||
|
||||
// now, let us delete this key
|
||||
@@ -376,9 +393,9 @@ func TestSeedPhrase(t *testing.T) {
|
||||
// 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)
|
||||
assert.Equal(t, n2, newInfo.GetName())
|
||||
assert.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
||||
assert.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
|
||||
}
|
||||
|
||||
func ExampleNew() {
|
||||
@@ -391,19 +408,19 @@ func ExampleNew() {
|
||||
sec := keys.AlgoSecp256k1
|
||||
|
||||
// Add keys and see they return in alphabetical order
|
||||
bob, _, err := cstore.Create("Bob", "friend", ed)
|
||||
bob, _, err := cstore.CreateMnemonic("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)
|
||||
fmt.Println(bob.GetName())
|
||||
}
|
||||
cstore.Create("Alice", "secret", sec)
|
||||
cstore.Create("Carl", "mitm", ed)
|
||||
cstore.CreateMnemonic("Alice", "secret", sec)
|
||||
cstore.CreateMnemonic("Carl", "mitm", ed)
|
||||
info, _ := cstore.List()
|
||||
for _, i := range info {
|
||||
fmt.Println(i.Name)
|
||||
fmt.Println(i.GetName())
|
||||
}
|
||||
|
||||
// We need to use passphrase to generate a signature
|
||||
@@ -415,11 +432,11 @@ func ExampleNew() {
|
||||
|
||||
// and we can validate the signature with publically available info
|
||||
binfo, _ := cstore.Get("Bob")
|
||||
if !binfo.PubKey.Equals(bob.PubKey) {
|
||||
if !binfo.GetPubKey().Equals(bob.GetPubKey()) {
|
||||
fmt.Println("Get and Create return different keys")
|
||||
}
|
||||
|
||||
if pub.Equals(binfo.PubKey) {
|
||||
if pub.Equals(binfo.GetPubKey()) {
|
||||
fmt.Println("signed by Bob")
|
||||
}
|
||||
if !pub.VerifyBytes(tx, sig) {
|
||||
|
||||
+5
-5
@@ -2,14 +2,14 @@ package keys
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CryptoAlgo string
|
||||
type SignAlgo string
|
||||
|
||||
const (
|
||||
AlgoEd25519 = CryptoAlgo("ed25519")
|
||||
AlgoSecp256k1 = CryptoAlgo("secp256k1")
|
||||
AlgoEd25519 = SignAlgo("ed25519")
|
||||
AlgoSecp256k1 = SignAlgo("secp256k1")
|
||||
)
|
||||
|
||||
func cryptoAlgoToByte(key CryptoAlgo) byte {
|
||||
func cryptoAlgoToByte(key SignAlgo) byte {
|
||||
switch key {
|
||||
case AlgoEd25519:
|
||||
return 0x01
|
||||
@@ -20,7 +20,7 @@ func cryptoAlgoToByte(key CryptoAlgo) byte {
|
||||
}
|
||||
}
|
||||
|
||||
func byteToCryptoAlgo(b byte) CryptoAlgo {
|
||||
func byteToSignAlgo(b byte) SignAlgo {
|
||||
switch b {
|
||||
case 0x01:
|
||||
return AlgoEd25519
|
||||
|
||||
+103
-21
@@ -4,54 +4,136 @@ import (
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
// Keybase allows simple CRUD on a keystore, as an aid to signing
|
||||
// Keybase exposes operations on a generic keystore
|
||||
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)
|
||||
|
||||
// CRUD on the keystore
|
||||
List() ([]Info, error)
|
||||
Get(name string) (Info, error)
|
||||
Update(name, oldpass, newpass string) error
|
||||
Delete(name, passphrase string) error
|
||||
|
||||
// Sign some bytes, looking up the private key to use
|
||||
Sign(name, passphrase string, msg []byte) (crypto.Signature, crypto.PubKey, error)
|
||||
|
||||
// Create a new locally-stored keypair, returning the mnemonic
|
||||
CreateMnemonic(name, passphrase string, algo SignAlgo) (info Info, seed string, err error)
|
||||
// Recover takes a seedphrase and loads in the key
|
||||
Recover(name, passphrase, seedphrase string) (info Info, erro error)
|
||||
|
||||
// Create, store, and return a new Ledger key reference
|
||||
CreateLedger(name string, path crypto.DerivationPath, algo SignAlgo) (info Info, err error)
|
||||
|
||||
// Create, store, and return a new offline key reference
|
||||
CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error)
|
||||
|
||||
// The following operations will *only* work on locally-stored keys
|
||||
Update(name, oldpass, newpass string) error
|
||||
Import(name string, armor string) (err error)
|
||||
ImportPubKey(name string, armor string) (err error)
|
||||
Export(name string) (armor string, err error)
|
||||
ExportPubKey(name string) (armor string, err error)
|
||||
}
|
||||
|
||||
// Info is the public information about a key
|
||||
type Info struct {
|
||||
// Publically exposed information about a keypair
|
||||
type Info interface {
|
||||
// Human-readable type for key listing
|
||||
GetType() string
|
||||
// Name of the key
|
||||
GetName() string
|
||||
// Public key
|
||||
GetPubKey() crypto.PubKey
|
||||
}
|
||||
|
||||
var _ Info = &localInfo{}
|
||||
var _ Info = &ledgerInfo{}
|
||||
var _ Info = &offlineInfo{}
|
||||
|
||||
// localInfo is the public information about a locally stored key
|
||||
type localInfo struct {
|
||||
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{
|
||||
func newLocalInfo(name string, pub crypto.PubKey, privArmor string) Info {
|
||||
return &localInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
PrivKeyArmor: privArmor,
|
||||
}
|
||||
}
|
||||
|
||||
// Address is a helper function to calculate the address from the pubkey
|
||||
func (i Info) Address() []byte {
|
||||
return i.PubKey.Address()
|
||||
func (i localInfo) GetType() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
func (i Info) bytes() []byte {
|
||||
bz, err := cdc.MarshalBinaryBare(i)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
func (i localInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i localInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
// ledgerInfo is the public information about a Ledger key
|
||||
type ledgerInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
Path crypto.DerivationPath `json:"path"`
|
||||
}
|
||||
|
||||
func newLedgerInfo(name string, pub crypto.PubKey, path crypto.DerivationPath) Info {
|
||||
return &ledgerInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
Path: path,
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetType() string {
|
||||
return "ledger"
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
// offlineInfo is the public information about an offline key
|
||||
type offlineInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey crypto.PubKey `json:"pubkey"`
|
||||
}
|
||||
|
||||
func newOfflineInfo(name string, pub crypto.PubKey) Info {
|
||||
return &offlineInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
}
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetType() string {
|
||||
return "offline"
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
// encoding info
|
||||
func writeInfo(i Info) []byte {
|
||||
return cdc.MustMarshalBinary(i)
|
||||
}
|
||||
|
||||
// decoding info
|
||||
func readInfo(bz []byte) (info Info, err error) {
|
||||
err = cdc.UnmarshalBinaryBare(bz, &info)
|
||||
err = cdc.UnmarshalBinary(bz, &info)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,4 +9,8 @@ var cdc = amino.NewCodec()
|
||||
|
||||
func init() {
|
||||
crypto.RegisterAmino(cdc)
|
||||
cdc.RegisterInterface((*Info)(nil), nil)
|
||||
cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil)
|
||||
cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil)
|
||||
cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user