From c21f67c5af7fef3ca064fa578084a73ac9fdfb81 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sun, 10 Jun 2018 10:01:41 +0200 Subject: [PATCH] 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) --- Gopkg.lock | 23 ++++-- Gopkg.toml | 3 +- encode_test.go | 6 +- keys/keybase.go | 168 ++++++++++++++++++++++++++++++++----------- keys/keybase_test.go | 147 ++++++++++++++++++++----------------- keys/keys.go | 10 +-- keys/types.go | 124 ++++++++++++++++++++++++++------ keys/wire.go | 4 ++ ledger_secp256k1.go | 27 +++---- ledger_test.go | 12 ++-- priv_key.go | 47 ++++-------- priv_key_test.go | 6 +- pub_key_test.go | 6 +- signature_test.go | 18 +++-- 14 files changed, 391 insertions(+), 210 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index f51e2b229..24b638a55 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml index 10931a1a6..d2b50e5a1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -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 diff --git a/encode_test.go b/encode_test.go index 0bd450829..f78d9439c 100644 --- a/encode_test.go +++ b/encode_test.go @@ -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) diff --git a/keys/keybase.go b/keys/keybase.go index dd9726478..39a3de59e 100644 --- a/keys/keybase.go +++ b/keys/keybase.go @@ -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 diff --git a/keys/keybase_test.go b/keys/keybase_test.go index aaf3b92fb..3627626cb 100644 --- a/keys/keybase_test.go +++ b/keys/keybase_test.go @@ -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) { diff --git a/keys/keys.go b/keys/keys.go index 0ed89b3fd..c8e25da3c 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -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 diff --git a/keys/types.go b/keys/types.go index e1df644ab..53821e8bf 100644 --- a/keys/types.go +++ b/keys/types.go @@ -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 } diff --git a/keys/wire.go b/keys/wire.go index 7deaad673..7cde0dd45 100644 --- a/keys/wire.go +++ b/keys/wire.go @@ -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) } diff --git a/ledger_secp256k1.go b/ledger_secp256k1.go index f485bb415..1a7887ad3 100644 --- a/ledger_secp256k1.go +++ b/ledger_secp256k1.go @@ -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 diff --git a/ledger_test.go b/ledger_test.go index dda3d3d3d..4a7ae46e4 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -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) diff --git a/priv_key.go b/priv_key.go index 82e1cfced..2c06f3452 100644 --- a/priv_key.go +++ b/priv_key.go @@ -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 { diff --git a/priv_key_test.go b/priv_key_test.go index 6abe36ac5..33f3eb7ee 100644 --- a/priv_key_test.go +++ b/priv_key_test.go @@ -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)) } /* diff --git a/pub_key_test.go b/pub_key_test.go index 29672390b..35c78a46a 100644 --- a/pub_key_test.go +++ b/pub_key_test.go @@ -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") diff --git a/signature_test.go b/signature_test.go index 0ba44ded5..52da65572 100644 --- a/signature_test.go +++ b/signature_test.go @@ -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)