Browse Source

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)
pull/1782/head
Christopher Goes 6 years ago
committed by Ismail Khoffi
parent
commit
c21f67c5af
14 changed files with 391 additions and 210 deletions
  1. +16
    -7
      Gopkg.lock
  2. +1
    -2
      Gopkg.toml
  3. +4
    -2
      encode_test.go
  4. +125
    -43
      keys/keybase.go
  5. +82
    -65
      keys/keybase_test.go
  6. +5
    -5
      keys/keys.go
  7. +103
    -21
      keys/types.go
  8. +4
    -0
      keys/wire.go
  9. +9
    -18
      ledger_secp256k1.go
  10. +8
    -4
      ledger_test.go
  11. +13
    -34
      priv_key.go
  12. +5
    -1
      priv_key_test.go
  13. +4
    -2
      pub_key_test.go
  14. +12
    -6
      signature_test.go

+ 16
- 7
Gopkg.lock View File

@ -119,7 +119,7 @@
"leveldb/table",
"leveldb/util"
]
revision = "5d6fca44a948d2be89a9702de7717f0168403d3d"
revision = "e2150783cd35f5b607daca48afd8c57ec54cc995"
[[projects]]
branch = "master"
@ -142,10 +142,11 @@
packages = [
"common",
"db",
"log"
"log",
"test"
]
revision = "d970af87248a4e162590300dbb74e102183a417d"
version = "v0.8.3"
revision = "692f1d86a6e2c0efa698fd1e4541b68c74ffaf38"
version = "v0.8.4"
[[projects]]
branch = "master"
@ -156,7 +157,7 @@
[[projects]]
name = "github.com/zondax/ledger-goclient"
packages = ["."]
revision = "3e2146609cdb97894c064d59e9d00accd8c2b1dd"
revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a"
[[projects]]
branch = "master"
@ -164,6 +165,8 @@
packages = [
"bcrypt",
"blowfish",
"chacha20poly1305",
"internal/chacha20",
"nacl/secretbox",
"openpgp/armor",
"openpgp/errors",
@ -172,11 +175,17 @@
"ripemd160",
"salsa20/salsa"
]
revision = "5ba7f63082460102a45837dbd1827e10f9479ac0"
revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["cpu"]
revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "365c3bca75ced49eb0ebcdc5c98fd47b534850684fcc94c16d1bc6a671116395"
inputs-digest = "f20e34cd998442d4ffe2f9aa45ab87a55ba6e4cd19f29009adaadac3b5dccf26"
solver-name = "gps-cdcl"
solver-version = 1

+ 1
- 2
Gopkg.toml View File

@ -24,7 +24,6 @@
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/btcsuite/btcutil"
branch = "master"
@ -59,7 +58,7 @@
[[constraint]]
name = "github.com/zondax/ledger-goclient"
revision = "3e2146609cdb97894c064d59e9d00accd8c2b1dd"
revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a"
[prune]
go-tests = true


+ 4
- 2
encode_test.go View File

@ -69,14 +69,16 @@ func TestKeyEncodings(t *testing.T) {
// Check (de/en)codings of Signatures.
var sig1, sig2, sig3 Signature
sig1 = tc.privKey.Sign([]byte("something"))
sig1, err := tc.privKey.Sign([]byte("something"))
assert.NoError(t, err)
checkAminoBinary(t, sig1, &sig2, -1) // Siganture size changes for Secp anyways.
assert.EqualValues(t, sig1, sig2)
checkAminoJSON(t, sig1, &sig3, false) // TODO also check Prefix bytes.
assert.EqualValues(t, sig1, sig3)
// Check (de/en)codings of PubKeys.
pubKey := tc.privKey.PubKey()
pubKey, err := tc.privKey.PubKey()
assert.NoError(t, err)
var pub2, pub3 PubKey
checkAminoBinary(t, pubKey, &pub2, tc.pubSize)
assert.EqualValues(t, pubKey, pub2)


+ 125
- 43
keys/keybase.go View File

@ -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 {
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, pub, "")
// 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 (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)
func (kb dbKeybase) writeLedgerKey(pub crypto.PubKey, path crypto.DerivationPath, name string) Info {
info := newLedgerInfo(name, pub, path)
kb.writeInfo(info, name)
return info
}
// write them both
kb.db.SetSync(infoKey(name), info.bytes())
func (kb dbKeybase) writeOfflineKey(pub crypto.PubKey, name string) Info {
info := newOfflineInfo(name, pub)
kb.writeInfo(info, name)
return info
}
func generate(algo CryptoAlgo, secret []byte) (crypto.PrivKey, error) {
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
keys/keybase_test.go View File

@ -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
keys/keys.go View File

@ -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
keys/types.go View File

@ -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,
}
}
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,
}
return bz
}
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
}

+ 4
- 0
keys/wire.go View File

@ -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)
}

+ 9
- 18
ledger_secp256k1.go View File

@ -73,11 +73,7 @@ func (pk *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
// 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 PrivKeyLedgerSecp256k1) Bytes() []byte {
bin, err := cdc.MarshalBinaryBare(pk)
if err != nil {
panic(err)
}
return bin
return cdc.MustMarshalBinaryBare(pk)
}
// Sign calls the ledger and stores the PubKey for future use
@ -85,39 +81,34 @@ func (pk PrivKeyLedgerSecp256k1) Bytes() []byte {
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes,
// returning an error, so this should only trigger if the privkey is held
// in memory for a while before use.
func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature {
// oh, I wish there was better error handling
func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) (Signature, error) {
dev, err := getLedger()
if err != nil {
panic(err)
return nil, err
}
sig, err := signLedgerSecp256k1(dev, pk.Path, msg)
if err != nil {
panic(err)
return nil, err
}
pub, err := pubkeyLedgerSecp256k1(dev, pk.Path)
if err != nil {
panic(err)
return nil, err
}
// if we have no pubkey yet, store it for future queries
if pk.CachedPubKey == nil {
pk.CachedPubKey = pub
} else if !pk.CachedPubKey.Equals(pub) {
panic("stored key does not match signing key")
return nil, fmt.Errorf("stored key does not match signing key")
}
return sig
return sig, nil
}
// PubKey returns the stored PubKey
func (pk PrivKeyLedgerSecp256k1) PubKey() PubKey {
key, err := pk.getPubKey()
if err != nil {
panic(err)
}
return key
func (pk PrivKeyLedgerSecp256k1) PubKey() (PubKey, error) {
return pk.getPubKey()
}
// getPubKey reads the pubkey from cache or from the ledger itself


+ 8
- 4
ledger_test.go View File

@ -19,8 +19,10 @@ func TestRealLedgerSecp256k1(t *testing.T) {
priv, err := NewPrivKeyLedgerSecp256k1(path)
require.Nil(t, err, "%+v", err)
pub := priv.PubKey()
sig := priv.Sign(msg)
pub, err := priv.PubKey()
require.Nil(t, err)
sig, err := priv.Sign(msg)
require.Nil(t, err)
valid := pub.VerifyBytes(msg, sig)
assert.True(t, valid)
@ -31,11 +33,13 @@ func TestRealLedgerSecp256k1(t *testing.T) {
require.Nil(t, err, "%+v", err)
// make sure we get the same pubkey when we load from disk
pub2 := priv2.PubKey()
pub2, err := priv2.PubKey()
require.Nil(t, err)
require.Equal(t, pub, pub2)
// signing with the loaded key should match the original pubkey
sig = priv2.Sign(msg)
sig, err = priv2.Sign(msg)
require.Nil(t, err)
valid = pub.VerifyBytes(msg, sig)
assert.True(t, valid)


+ 13
- 34
priv_key.go View File

@ -6,7 +6,6 @@ import (
secp256k1 "github.com/btcsuite/btcd/btcec"
"github.com/tendermint/ed25519"
"github.com/tendermint/ed25519/extra25519"
. "github.com/tendermint/tmlibs/common"
)
func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) {
@ -18,8 +17,8 @@ func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) {
type PrivKey interface {
Bytes() []byte
Sign(msg []byte) Signature
PubKey() PubKey
Sign(msg []byte) (Signature, error)
PubKey() (PubKey, error)
Equals(PrivKey) bool
}
@ -31,23 +30,19 @@ var _ PrivKey = PrivKeyEd25519{}
type PrivKeyEd25519 [64]byte
func (privKey PrivKeyEd25519) Bytes() []byte {
bz, err := cdc.MarshalBinaryBare(privKey)
if err != nil {
panic(err)
}
return bz
return cdc.MustMarshalBinaryBare(privKey)
}
func (privKey PrivKeyEd25519) Sign(msg []byte) Signature {
func (privKey PrivKeyEd25519) Sign(msg []byte) (Signature, error) {
privKeyBytes := [64]byte(privKey)
signatureBytes := ed25519.Sign(&privKeyBytes, msg)
return SignatureEd25519(*signatureBytes)
return SignatureEd25519(*signatureBytes), nil
}
func (privKey PrivKeyEd25519) PubKey() PubKey {
func (privKey PrivKeyEd25519) PubKey() (PubKey, error) {
privKeyBytes := [64]byte(privKey)
pubBytes := *ed25519.MakePublicKey(&privKeyBytes)
return PubKeyEd25519(pubBytes)
return PubKeyEd25519(pubBytes), nil
}
// Equals - you probably don't need to use this.
@ -67,12 +62,6 @@ func (privKey PrivKeyEd25519) ToCurve25519() *[32]byte {
return keyCurve25519
}
/*
func (privKey PrivKeyEd25519) String() string {
return Fmt("PrivKeyEd25519{*****}")
}
*/
// Deterministically generates new priv-key bytes from key.
func (privKey PrivKeyEd25519) Generate(index int) PrivKeyEd25519 {
bz, err := cdc.MarshalBinaryBare(struct {
@ -114,27 +103,23 @@ var _ PrivKey = PrivKeySecp256k1{}
type PrivKeySecp256k1 [32]byte
func (privKey PrivKeySecp256k1) Bytes() []byte {
bz, err := cdc.MarshalBinaryBare(privKey)
if err != nil {
panic(err)
}
return bz
return cdc.MustMarshalBinaryBare(privKey)
}
func (privKey PrivKeySecp256k1) Sign(msg []byte) Signature {
func (privKey PrivKeySecp256k1) Sign(msg []byte) (Signature, error) {
priv__, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:])
sig__, err := priv__.Sign(Sha256(msg))
if err != nil {
PanicSanity(err)
return nil, err
}
return SignatureSecp256k1(sig__.Serialize())
return SignatureSecp256k1(sig__.Serialize()), nil
}
func (privKey PrivKeySecp256k1) PubKey() PubKey {
func (privKey PrivKeySecp256k1) PubKey() (PubKey, error) {
_, pub__ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:])
var pub PubKeySecp256k1
copy(pub[:], pub__.SerializeCompressed())
return pub
return pub, nil
}
// Equals - you probably don't need to use this.
@ -147,12 +132,6 @@ func (privKey PrivKeySecp256k1) Equals(other PrivKey) bool {
}
}
/*
func (privKey PrivKeySecp256k1) String() string {
return Fmt("PrivKeySecp256k1{*****}")
}
*/
/*
// Deterministically generates new priv-key bytes from key.
func (key PrivKeySecp256k1) Generate(index int) PrivKeySecp256k1 {


+ 5
- 1
priv_key_test.go View File

@ -11,7 +11,11 @@ func TestGeneratePrivKey(t *testing.T) {
testPriv := crypto.GenPrivKeyEd25519()
testGenerate := testPriv.Generate(1)
signBytes := []byte("something to sign")
assert.True(t, testGenerate.PubKey().VerifyBytes(signBytes, testGenerate.Sign(signBytes)))
pub, err := testGenerate.PubKey()
assert.NoError(t, err)
sig, err := testGenerate.Sign(signBytes)
assert.NoError(t, err)
assert.True(t, pub.VerifyBytes(signBytes, sig))
}
/*


+ 4
- 2
pub_key_test.go View File

@ -33,9 +33,11 @@ func TestPubKeySecp256k1Address(t *testing.T) {
var priv PrivKeySecp256k1
copy(priv[:], privB)
pubT := priv.PubKey().(PubKeySecp256k1)
pubKey, err := priv.PubKey()
assert.NoError(t, err)
pubT, _ := pubKey.(PubKeySecp256k1)
pub := pubT[:]
addr := priv.PubKey().Address()
addr := pubKey.Address()
assert.Equal(t, pub, pubB, "Expected pub keys to match")
assert.Equal(t, addr, addrB, "Expected addresses to match")


+ 12
- 6
signature_test.go View File

@ -12,10 +12,12 @@ import (
func TestSignAndValidateEd25519(t *testing.T) {
privKey := GenPrivKeyEd25519()
pubKey := privKey.PubKey()
pubKey, err := privKey.PubKey()
require.Nil(t, err)
msg := CRandBytes(128)
sig := privKey.Sign(msg)
sig, err := privKey.Sign(msg)
require.Nil(t, err)
// Test the signature
assert.True(t, pubKey.VerifyBytes(msg, sig))
@ -30,10 +32,12 @@ func TestSignAndValidateEd25519(t *testing.T) {
func TestSignAndValidateSecp256k1(t *testing.T) {
privKey := GenPrivKeySecp256k1()
pubKey := privKey.PubKey()
pubKey, err := privKey.PubKey()
require.Nil(t, err)
msg := CRandBytes(128)
sig := privKey.Sign(msg)
sig, err := privKey.Sign(msg)
require.Nil(t, err)
assert.True(t, pubKey.VerifyBytes(msg, sig))
@ -65,10 +69,12 @@ func TestSignatureEncodings(t *testing.T) {
for _, tc := range cases {
// note we embed them from the beginning....
pubKey := tc.privKey.PubKey()
pubKey, err := tc.privKey.PubKey()
require.Nil(t, err)
msg := CRandBytes(128)
sig := tc.privKey.Sign(msg)
sig, err := tc.privKey.Sign(msg)
require.Nil(t, err)
// store as amino
bin, err := cdc.MarshalBinaryBare(sig)


Loading…
Cancel
Save