From d5ec6611da830ec1acd47b3198cfec4d037762fb Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Mon, 11 Jun 2018 17:17:10 -0700 Subject: [PATCH 1/3]  This is a combination of 2 commits.  This is the 1st commit message: Release/0.8.0 (#127) https://github.com/Liamsi/go-crypto/blob/8e31aebe2bc23037b9cc11892f3ec4515bbf5991/CHANGELOG.md#080 fix tests, move encoding to encode_test.go, include an example Ledger integration, WIP Fix testcases, all looks OK Prevent unnecessary signatures, improve error messages Bugfix Update to new Ledger API in progress Update to latest upstream, debugging information  This is the commit message #2: Clarify function names --- CHANGELOG.md | 16 ++++ Gopkg.lock | 42 +++++++--- Gopkg.toml | 7 +- amino.go | 6 +- encode_test.go | 20 ++++- keys/keybase.go | 168 +++++++++++++++++++++++++++---------- keys/keybase_test.go | 147 +++++++++++++++++--------------- keys/keys.go | 10 +-- keys/types.go | 124 ++++++++++++++++++++++----- keys/wire.go | 4 + ledger.go | 171 ++++++++++++++++++++++++++++++++++++++ ledger_common.go | 19 +++++ ledger_secp256k1.go | 146 ++++++++++++++++++++++++++++++++ ledger_test.go | 65 +++++++++++++++ merkle/simple_map.go | 13 ++- merkle/simple_map_test.go | 12 +-- merkle/simple_proof.go | 14 +++- priv_key.go | 47 +++-------- priv_key_test.go | 6 +- pub_key.go | 13 +-- pub_key_test.go | 6 +- signature.go | 6 ++ signature_test.go | 72 ++-------------- version.go | 2 +- 24 files changed, 858 insertions(+), 278 deletions(-) create mode 100644 ledger.go create mode 100644 ledger_common.go create mode 100644 ledger_secp256k1.go create mode 100644 ledger_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db3a61..d6c956e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.8.0 + +BREAKING CHANGES + +- update ed25519 signature scheme to truncated sha256 (#112) +- renaming registered concrete type path for ed25519 signatures (#110) + +IMPROVEMENT + + - SimpleProofsFromMap returns ordered keys (#115) + - return error on `privkey.Sign()` and `privkey.Pubkey()` (#117) + +FEATURE + + - rework API to work with local and external keys (offline, HW wallets, and Ledger) (#117) + ## 0.7.0 **May 30th, 2018** diff --git a/Gopkg.lock b/Gopkg.lock index f52af55..a475c66 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,17 +1,23 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "github.com/brejski/hid" + packages = ["."] + revision = "06112dcfcc50a7e0e4fd06e17f9791e788fdaafc" + [[projects]] branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "2be2f12b358dc57d70b8f501b00be450192efbc3" + revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" [[projects]] branch = "master" name = "github.com/btcsuite/btcutil" packages = ["base58"] - revision = "501929d3d046174c3d39f0ea54ece471aa17238c" + revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] name = "github.com/davecgh/go-spew" @@ -55,7 +61,7 @@ branch = "master" name = "github.com/golang/snappy" packages = ["."] - revision = "553a641470496b2327abcac10b36396bd98e45c9" + revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" [[projects]] branch = "master" @@ -113,7 +119,7 @@ "leveldb/table", "leveldb/util" ] - revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad" + revision = "e2150783cd35f5b607daca48afd8c57ec54cc995" [[projects]] branch = "master" @@ -128,18 +134,19 @@ [[projects]] name = "github.com/tendermint/go-amino" packages = ["."] - revision = "42246108ff925a457fb709475070a03dfd3e2b5c" - version = "0.9.6" + revision = "1715b7b78c65d6adcc5937315be4710234cefe09" + version = "0.10.0-rc2" [[projects]] name = "github.com/tendermint/tmlibs" packages = [ "common", "db", - "log" + "log", + "test" ] - revision = "2e24b64fc121dcdf1cabceab8dc2f7257675483c" - version = "v0.8.1" + revision = "692f1d86a6e2c0efa698fd1e4541b68c74ffaf38" + version = "v0.8.4" [[projects]] branch = "master" @@ -147,12 +154,19 @@ packages = ["."] revision = "8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc" +[[projects]] + name = "github.com/zondax/ledger-goclient" + packages = ["."] + revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a" + [[projects]] branch = "master" name = "golang.org/x/crypto" packages = [ "bcrypt", "blowfish", + "chacha20poly1305", + "internal/chacha20", "nacl/secretbox", "openpgp/armor", "openpgp/errors", @@ -161,11 +175,17 @@ "ripemd160", "salsa20/salsa" ] - revision = "b2aa35443fbc700ab74c586ae79b81c171851023" + revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["cpu"] + revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "f9ccfa2cadfcbfb43bf729b871a0ad2f8d4f4acb118cd859e6faf9b24842b840" + inputs-digest = "f20e34cd998442d4ffe2f9aa45ab87a55ba6e4cd19f29009adaadac3b5dccf26" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 4ccb8c0..d60107f 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" @@ -47,7 +46,7 @@ [[constraint]] name = "github.com/tendermint/go-amino" - version = "0.9.6" + version = "0.10.0-rc2" [[constraint]] name = "github.com/tendermint/tmlibs" @@ -57,6 +56,10 @@ name = "github.com/tyler-smith/go-bip39" branch = "master" +[[constraint]] + name = "github.com/zondax/ledger-goclient" + branch = "master" + [prune] go-tests = true unused-packages = true diff --git a/amino.go b/amino.go index 8963689..ddf3e19 100644 --- a/amino.go +++ b/amino.go @@ -27,10 +27,12 @@ func RegisterAmino(cdc *amino.Codec) { "tendermint/PrivKeyEd25519", nil) cdc.RegisterConcrete(PrivKeySecp256k1{}, "tendermint/PrivKeySecp256k1", nil) + cdc.RegisterConcrete(PrivKeyLedgerSecp256k1{}, + "tendermint/PrivKeyLedgerSecp256k1", nil) cdc.RegisterInterface((*Signature)(nil), nil) cdc.RegisterConcrete(SignatureEd25519{}, - "tendermint/SignatureKeyEd25519", nil) + "tendermint/SignatureEd25519", nil) cdc.RegisterConcrete(SignatureSecp256k1{}, - "tendermint/SignatureKeySecp256k1", nil) + "tendermint/SignatureSecp256k1", nil) } diff --git a/encode_test.go b/encode_test.go index 0bd4508..ed5395d 100644 --- a/encode_test.go +++ b/encode_test.go @@ -1,6 +1,7 @@ package crypto import ( + "os" "testing" "github.com/stretchr/testify/assert" @@ -41,6 +42,19 @@ func checkAminoJSON(t *testing.T, src interface{}, dst interface{}, isNil bool) require.Nil(t, err, "%+v", err) } +func ExamplePrintRegisteredTypes() { + cdc.PrintTypes(os.Stdout) + // Output: | Type | Name | Prefix | Length | Notes | + //| ---- | ---- | ------ | ----- | ------ | + //| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | | + //| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | | + //| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | | + //| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | | + //| PrivKeyLedgerSecp256k1 | tendermint/PrivKeyLedgerSecp256k1 | 0x10CAB393 | variable | | + //| SignatureEd25519 | tendermint/SignatureEd25519 | 0x2031EA53 | 0x40 | | + //| SignatureSecp256k1 | tendermint/SignatureSecp256k1 | 0x7FC4A495 | variable | | +} + func TestKeyEncodings(t *testing.T) { cases := []struct { privKey PrivKey @@ -69,14 +83,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 dd97264..39a3de5 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 aaf3b92..3627626 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 0ed89b3..c8e25da 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 e1df644..53821e8 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 7deaad6..7cde0dd 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.go b/ledger.go new file mode 100644 index 0000000..5b681cd --- /dev/null +++ b/ledger.go @@ -0,0 +1,171 @@ +package crypto + +import ( + "fmt" + "github.com/pkg/errors" + + secp256k1 "github.com/btcsuite/btcd/btcec" + ledger "github.com/zondax/ledger-goclient" +) + +var device *ledger.Ledger + +type DerivationPath = []uint32 + +// getLedger gets a copy of the device, and caches it +func getLedger() (*ledger.Ledger, error) { + var err error + if device == nil { + device, err = ledger.FindLedger() + } + return device, err +} + +func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { + key, err := device.GetPublicKeySECP256K1(path) + if err != nil { + return pub, fmt.Errorf("Error fetching public key: %v", err) + } + var p PubKeySecp256k1 + // Reserialize in the 33-byte compressed format + cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) + copy(p[:], cmp.SerializeCompressed()) + return p, err +} + +func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { + bsig, err := device.SignSECP256K1(path, msg) + if err != nil { + return sig, err + } + sig = SignatureSecp256k1FromBytes(bsig) + return sig, nil +} + +// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano +// we cache the PubKey from the first call to use it later +type PrivKeyLedgerSecp256k1 struct { + // PubKey should be private, but we want to encode it via go-amino + // so we can view the address later, even without having the ledger + // attached + CachedPubKey PubKey + Path DerivationPath +} + +// NewPrivKeyLedgerSecp256k1 will generate a new key and store the +// public key for later use. +func NewPrivKeyLedgerSecp256k1(path DerivationPath) (PrivKey, error) { + var pk PrivKeyLedgerSecp256k1 + pk.Path = path + // getPubKey will cache the pubkey for later use, + // this allows us to return an error early if the ledger + // is not plugged in + _, err := pk.getPubKey() + return &pk, err +} + +// ValidateKey allows us to verify the sanity of a key +// after loading it from disk +func (pk PrivKeyLedgerSecp256k1) ValidateKey() error { + // getPubKey will return an error if the ledger is not + // properly set up... + pub, err := pk.forceGetPubKey() + if err != nil { + return err + } + // verify this matches cached address + if !pub.Equals(pk.CachedPubKey) { + return errors.New("Cached key does not match retrieved key") + } + return nil +} + +// AssertIsPrivKeyInner fulfils PrivKey Interface +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 +} + +// Sign calls the ledger and stores the PubKey for future use +// +// XXX/TODO: panics if there is an error communicating with the ledger. +// +// 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 + dev, err := getLedger() + if err != nil { + panic(err) + } + + sig, err := signLedgerSecp256k1(dev, pk.Path, msg) + if err != nil { + panic(err) + } + + pub, err := pubkeyLedgerSecp256k1(dev, pk.Path) + if err != nil { + panic(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 sig +} + +// PubKey returns the stored PubKey +// TODO: query the ledger if not there, once it is not volatile +func (pk PrivKeyLedgerSecp256k1) PubKey() PubKey { + key, err := pk.getPubKey() + if err != nil { + panic(err) + } + return key +} + +// getPubKey reads the pubkey from cache or from the ledger itself +// since this involves IO, it may return an error, which is not exposed +// in the PubKey interface, so this function allows better error handling +func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) { + // if we have no pubkey, set it + if pk.CachedPubKey == nil { + pk.CachedPubKey, err = pk.forceGetPubKey() + } + return pk.CachedPubKey, err +} + +// forceGetPubKey is like getPubKey but ignores any cached key +// and ensures we get it from the ledger itself. +func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) { + dev, err := getLedger() + if err != nil { + return key, errors.New(fmt.Sprintf("Cannot connect to Ledger device - error: %v", err)) + } + key, err = pubkeyLedgerSecp256k1(dev, pk.Path) + if err != nil { + return key, errors.New(fmt.Sprintf("Please open Cosmos app on the Ledger device - error: %v", err)) + } + return key, err +} + +// Equals fulfils PrivKey Interface - makes sure both keys refer to the +// same +func (pk PrivKeyLedgerSecp256k1) Equals(other PrivKey) bool { + if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok { + return pk.CachedPubKey.Equals(ledger.CachedPubKey) + } + return false +} diff --git a/ledger_common.go b/ledger_common.go new file mode 100644 index 0000000..39f1546 --- /dev/null +++ b/ledger_common.go @@ -0,0 +1,19 @@ +package crypto + +import ( + ledger "github.com/zondax/ledger-goclient" +) + +var device *ledger.Ledger + +// Ledger derivation path +type DerivationPath = []uint32 + +// getLedger gets a copy of the device, and caches it +func getLedger() (*ledger.Ledger, error) { + var err error + if device == nil { + device, err = ledger.FindLedger() + } + return device, err +} diff --git a/ledger_secp256k1.go b/ledger_secp256k1.go new file mode 100644 index 0000000..1a7887a --- /dev/null +++ b/ledger_secp256k1.go @@ -0,0 +1,146 @@ +package crypto + +import ( + "fmt" + + secp256k1 "github.com/btcsuite/btcd/btcec" + ledger "github.com/zondax/ledger-goclient" +) + +func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { + key, err := device.GetPublicKeySECP256K1(path) + if err != nil { + return nil, fmt.Errorf("error fetching public key: %v", err) + } + var p PubKeySecp256k1 + // Reserialize in the 33-byte compressed format + cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) + copy(p[:], cmp.SerializeCompressed()) + pub = p + return +} + +func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { + bsig, err := device.SignSECP256K1(path, msg) + if err != nil { + return sig, err + } + sig = SignatureSecp256k1FromBytes(bsig) + return +} + +// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano +// we cache the PubKey from the first call to use it later +type PrivKeyLedgerSecp256k1 struct { + // PubKey should be private, but we want to encode it via go-amino + // so we can view the address later, even without having the ledger + // attached + CachedPubKey PubKey + Path DerivationPath +} + +// NewPrivKeyLedgerSecp256k1 will generate a new key and store the +// public key for later use. +func NewPrivKeyLedgerSecp256k1(path DerivationPath) (PrivKey, error) { + var pk PrivKeyLedgerSecp256k1 + pk.Path = path + // getPubKey will cache the pubkey for later use, + // this allows us to return an error early if the ledger + // is not plugged in + _, err := pk.getPubKey() + return &pk, err +} + +// ValidateKey allows us to verify the sanity of a key +// after loading it from disk +func (pk PrivKeyLedgerSecp256k1) ValidateKey() error { + // getPubKey will return an error if the ledger is not + // properly set up... + pub, err := pk.forceGetPubKey() + if err != nil { + return err + } + // verify this matches cached address + if !pub.Equals(pk.CachedPubKey) { + return fmt.Errorf("cached key does not match retrieved key") + } + return nil +} + +// AssertIsPrivKeyInner fulfils PrivKey Interface +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 { + return cdc.MustMarshalBinaryBare(pk) +} + +// Sign calls the ledger and stores the PubKey for future use +// +// 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, error) { + dev, err := getLedger() + if err != nil { + return nil, err + } + + sig, err := signLedgerSecp256k1(dev, pk.Path, msg) + if err != nil { + return nil, err + } + + pub, err := pubkeyLedgerSecp256k1(dev, pk.Path) + if err != nil { + 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) { + return nil, fmt.Errorf("stored key does not match signing key") + } + return sig, nil +} + +// PubKey returns the stored PubKey +func (pk PrivKeyLedgerSecp256k1) PubKey() (PubKey, error) { + return pk.getPubKey() +} + +// getPubKey reads the pubkey from cache or from the ledger itself +// since this involves IO, it may return an error, which is not exposed +// in the PubKey interface, so this function allows better error handling +func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) { + // if we have no pubkey, set it + if pk.CachedPubKey == nil { + pk.CachedPubKey, err = pk.forceGetPubKey() + } + return pk.CachedPubKey, err +} + +// forceGetPubKey is like getPubKey but ignores any cached key +// and ensures we get it from the ledger itself. +func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) { + dev, err := getLedger() + if err != nil { + return key, fmt.Errorf("cannot connect to Ledger device - error: %v", err) + } + key, err = pubkeyLedgerSecp256k1(dev, pk.Path) + if err != nil { + return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err) + } + return key, err +} + +// Equals fulfils PrivKey Interface - makes sure both keys refer to the +// same +func (pk PrivKeyLedgerSecp256k1) Equals(other PrivKey) bool { + if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok { + return pk.CachedPubKey.Equals(ledger.CachedPubKey) + } + return false +} diff --git a/ledger_test.go b/ledger_test.go new file mode 100644 index 0000000..4a7ae46 --- /dev/null +++ b/ledger_test.go @@ -0,0 +1,65 @@ +package crypto + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRealLedgerSecp256k1(t *testing.T) { + + if os.Getenv("WITH_LEDGER") == "" { + t.Skip("Set WITH_LEDGER to run code on real ledger") + } + msg := []byte("kuhehfeohg") + + path := DerivationPath{44, 60, 0, 0, 0} + + priv, err := NewPrivKeyLedgerSecp256k1(path) + require.Nil(t, err, "%+v", err) + 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) + + // now, let's serialize the key and make sure it still works + bs := priv.Bytes() + priv2, err := PrivKeyFromBytes(bs) + require.Nil(t, err, "%+v", err) + + // make sure we get the same pubkey when we load from disk + pub2, err := priv2.PubKey() + require.Nil(t, err) + require.Equal(t, pub, pub2) + + // signing with the loaded key should match the original pubkey + sig, err = priv2.Sign(msg) + require.Nil(t, err) + valid = pub.VerifyBytes(msg, sig) + assert.True(t, valid) + + // make sure pubkeys serialize properly as well + bs = pub.Bytes() + bpub, err := PubKeyFromBytes(bs) + require.NoError(t, err) + assert.Equal(t, pub, bpub) +} + +// TestRealLedgerErrorHandling calls. These tests assume +// the ledger is not plugged in.... +func TestRealLedgerErrorHandling(t *testing.T) { + if os.Getenv("WITH_LEDGER") != "" { + t.Skip("Skipping on WITH_LEDGER as it tests unplugged cases") + } + + // first, try to generate a key, must return an error + // (no panic) + path := DerivationPath{44, 60, 0, 0, 0} + _, err := NewPrivKeyLedgerSecp256k1(path) + require.Error(t, err) +} diff --git a/merkle/simple_map.go b/merkle/simple_map.go index cde5924..2486326 100644 --- a/merkle/simple_map.go +++ b/merkle/simple_map.go @@ -24,16 +24,13 @@ func newSimpleMap() *simpleMap { func (sm *simpleMap) Set(key string, value Hasher) { sm.sorted = false - // Hash the key to blind it... why not? - khash := tmhash.Sum([]byte(key)) - - // And the value is hashed too, so you can + // The value is hashed, so you can // check for equality with a cached value (say) // and make a determination to fetch or not. vhash := value.Hash() sm.kvs = append(sm.kvs, cmn.KVPair{ - Key: khash, + Key: []byte(key), Value: vhash, }) } @@ -67,9 +64,9 @@ func (sm *simpleMap) KVPairs() cmn.KVPairs { // A local extension to KVPair that can be hashed. // Key and value are length prefixed and concatenated, // then hashed. -type kvPair cmn.KVPair +type KVPair cmn.KVPair -func (kv kvPair) Hash() []byte { +func (kv KVPair) Hash() []byte { hasher := tmhash.New() err := encodeByteSlice(hasher, kv.Key) if err != nil { @@ -85,7 +82,7 @@ func (kv kvPair) Hash() []byte { func hashKVPairs(kvs cmn.KVPairs) []byte { kvsH := make([]Hasher, len(kvs)) for i, kvp := range kvs { - kvsH[i] = kvPair(kvp) + kvsH[i] = KVPair(kvp) } return SimpleHashFromHashers(kvsH) } diff --git a/merkle/simple_map_test.go b/merkle/simple_map_test.go index a89289a..d9d6351 100644 --- a/merkle/simple_map_test.go +++ b/merkle/simple_map_test.go @@ -18,37 +18,37 @@ func TestSimpleMap(t *testing.T) { { db := newSimpleMap() db.Set("key1", strHasher("value1")) - assert.Equal(t, "3dafc06a52039d029be57c75c9d16356a4256ef4", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "fa9bc106ffd932d919bee935ceb6cf2b3dd72d8f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := newSimpleMap() db.Set("key1", strHasher("value2")) - assert.Equal(t, "03eb5cfdff646bc4e80fec844e72fd248a1c6b2c", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "e00e7dcfe54e9fafef5111e813a587f01ba9c3e8", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := newSimpleMap() db.Set("key1", strHasher("value1")) db.Set("key2", strHasher("value2")) - assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "eff12d1c703a1022ab509287c0f196130123d786", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := newSimpleMap() db.Set("key2", strHasher("value2")) // NOTE: out of order db.Set("key1", strHasher("value1")) - assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "eff12d1c703a1022ab509287c0f196130123d786", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := newSimpleMap() db.Set("key1", strHasher("value1")) db.Set("key2", strHasher("value2")) db.Set("key3", strHasher("value3")) - assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "b2c62a277c08dbd2ad73ca53cd1d6bfdf5830d26", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := newSimpleMap() db.Set("key2", strHasher("value2")) // NOTE: out of order db.Set("key1", strHasher("value1")) db.Set("key3", strHasher("value3")) - assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "b2c62a277c08dbd2ad73ca53cd1d6bfdf5830d26", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } } diff --git a/merkle/simple_proof.go b/merkle/simple_proof.go index f52d1ad..2541b6d 100644 --- a/merkle/simple_proof.go +++ b/merkle/simple_proof.go @@ -27,7 +27,7 @@ func SimpleProofsFromHashers(items []Hasher) (rootHash []byte, proofs []*SimpleP // SimpleProofsFromMap generates proofs from a map. The keys/values of the map will be used as the keys/values // in the underlying key-value pairs. // The keys are sorted before the proofs are computed. -func SimpleProofsFromMap(m map[string]Hasher) (rootHash []byte, proofs []*SimpleProof) { +func SimpleProofsFromMap(m map[string]Hasher) (rootHash []byte, proofs map[string]*SimpleProof, keys []string) { sm := newSimpleMap() for k, v := range m { sm.Set(k, v) @@ -36,9 +36,17 @@ func SimpleProofsFromMap(m map[string]Hasher) (rootHash []byte, proofs []*Simple kvs := sm.kvs kvsH := make([]Hasher, 0, len(kvs)) for _, kvp := range kvs { - kvsH = append(kvsH, kvPair(kvp)) + kvsH = append(kvsH, KVPair(kvp)) } - return SimpleProofsFromHashers(kvsH) + + rootHash, proofList := SimpleProofsFromHashers(kvsH) + proofs = make(map[string]*SimpleProof) + keys = make([]string, len(proofList)) + for i, kvp := range kvs { + proofs[string(kvp.Key)] = proofList[i] + keys[i] = string(kvp.Key) + } + return } // Verify that leafHash is a leaf hash of the simple-merkle-tree diff --git a/priv_key.go b/priv_key.go index 82e1cfc..2c06f34 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 6abe36a..33f3eb7 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.go b/pub_key.go index 9be64ac..c509ff8 100644 --- a/pub_key.go +++ b/pub_key.go @@ -5,11 +5,16 @@ import ( "crypto/sha256" "fmt" + "golang.org/x/crypto/ripemd160" + secp256k1 "github.com/btcsuite/btcd/btcec" + "github.com/tendermint/ed25519" "github.com/tendermint/ed25519/extra25519" + cmn "github.com/tendermint/tmlibs/common" - "golang.org/x/crypto/ripemd160" + + "github.com/tendermint/go-crypto/tmhash" ) // An address is a []byte, but hex-encoded even in JSON. @@ -38,11 +43,9 @@ var _ PubKey = PubKeyEd25519{} // Implements PubKeyInner type PubKeyEd25519 [32]byte +// Address is the SHA256-20 of the raw pubkey bytes. func (pubKey PubKeyEd25519) Address() Address { - // append type byte - hasher := ripemd160.New() - hasher.Write(pubKey.Bytes()) // does not error - return Address(hasher.Sum(nil)) + return Address(tmhash.Sum(pubKey[:])) } func (pubKey PubKeyEd25519) Bytes() []byte { diff --git a/pub_key_test.go b/pub_key_test.go index 2967239..35c78a4 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.go b/signature.go index 4f55420..1ffb45e 100644 --- a/signature.go +++ b/signature.go @@ -80,3 +80,9 @@ func (sig SignatureSecp256k1) Equals(other Signature) bool { return false } } + +func SignatureSecp256k1FromBytes(data []byte) Signature { + sig := make(SignatureSecp256k1, len(data)) + copy(sig[:], data) + return sig +} diff --git a/signature_test.go b/signature_test.go index 0ba44de..be15b87 100644 --- a/signature_test.go +++ b/signature_test.go @@ -3,19 +3,19 @@ package crypto import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/ed25519" - amino "github.com/tendermint/go-amino" + "github.com/stretchr/testify/assert" ) 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)) @@ -44,65 +44,3 @@ func TestSignAndValidateSecp256k1(t *testing.T) { assert.False(t, pubKey.VerifyBytes(msg, sig)) } - -func TestSignatureEncodings(t *testing.T) { - cases := []struct { - privKey PrivKey - sigSize int - sigPrefix amino.PrefixBytes - }{ - { - privKey: GenPrivKeyEd25519(), - sigSize: ed25519.SignatureSize, - sigPrefix: [4]byte{0x3d, 0xa1, 0xdb, 0x2a}, - }, - { - privKey: GenPrivKeySecp256k1(), - sigSize: 0, // unknown - sigPrefix: [4]byte{0x16, 0xe1, 0xfe, 0xea}, - }, - } - - for _, tc := range cases { - // note we embed them from the beginning.... - pubKey := tc.privKey.PubKey() - - msg := CRandBytes(128) - sig := tc.privKey.Sign(msg) - - // store as amino - bin, err := cdc.MarshalBinaryBare(sig) - require.Nil(t, err, "%+v", err) - if tc.sigSize != 0 { - // Q: where is 1 byte coming from? - assert.Equal(t, tc.sigSize+amino.PrefixBytesLen+1, len(bin)) - } - assert.Equal(t, tc.sigPrefix[:], bin[0:amino.PrefixBytesLen]) - - // and back - sig2 := Signature(nil) - err = cdc.UnmarshalBinaryBare(bin, &sig2) - require.Nil(t, err, "%+v", err) - assert.EqualValues(t, sig, sig2) - assert.True(t, pubKey.VerifyBytes(msg, sig2)) - - /* - // store as json - js, err := data.ToJSON(sig) - require.Nil(t, err, "%+v", err) - assert.True(t, strings.Contains(string(js), tc.sigName)) - - // and back - sig3 := Signature{} - err = data.FromJSON(js, &sig3) - require.Nil(t, err, "%+v", err) - assert.EqualValues(t, sig, sig3) - assert.True(t, pubKey.VerifyBytes(msg, sig3)) - - // and make sure we can textify it - text, err := data.ToText(sig) - require.Nil(t, err, "%+v", err) - assert.True(t, strings.HasPrefix(text, tc.sigName)) - */ - } -} diff --git a/version.go b/version.go index 4cf5685..31d9d2d 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package crypto -const Version = "0.7.0" +const Version = "0.8.0" From 662f23e83bde0ae5d5bf8e2c1c018b19a4941005 Mon Sep 17 00:00:00 2001 From: Liamsi Date: Tue, 12 Jun 2018 16:23:14 -0700 Subject: [PATCH 2/3]  This is a combination of 3 commits.  This is the 1st commit message: Release/0.8.0 (#127) https://github.com/Liamsi/go-crypto/blob/8e31aebe2bc23037b9cc11892f3ec4515bbf5991/CHANGELOG.md#080 fix tests, move encoding to encode_test.go, include an example Ledger integration, WIP Fix testcases, all looks OK Prevent unnecessary signatures, improve error messages Bugfix Update to new Ledger API in progress Update to latest upstream, debugging information  This is the commit message #2: Clarify function names  This is the commit message #3: Add ed25519, tests will fail until ed25519 verification fix Implement PubKeyLedgerEd25519 Pin to an upstream revision Remove Ledger ed25519 support, for now Move TODOs to #114 Move from tmlibs #213 (#115) * move from tmlibs 213 * expose KVPair, simpleproofsfrommap returns keys update ed25519 address scheme (#112) make PubKeyEd25519.Address() returns the first 20 bytes of the hash of the raw 32-byte pubkey, no amino required forgot PrivKeyLedgerSecp256k1 version bump (#128) version bump --- Gopkg.toml | 2 +- ledger.go | 171 -------------------------------------------- ledger_secp256k1.go | 27 ++++--- version.go | 2 +- 4 files changed, 20 insertions(+), 182 deletions(-) delete mode 100644 ledger.go diff --git a/Gopkg.toml b/Gopkg.toml index d60107f..822cb58 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -58,7 +58,7 @@ [[constraint]] name = "github.com/zondax/ledger-goclient" - branch = "master" + revision = "3e2146609cdb97894c064d59e9d00accd8c2b1dd" [prune] go-tests = true diff --git a/ledger.go b/ledger.go deleted file mode 100644 index 5b681cd..0000000 --- a/ledger.go +++ /dev/null @@ -1,171 +0,0 @@ -package crypto - -import ( - "fmt" - "github.com/pkg/errors" - - secp256k1 "github.com/btcsuite/btcd/btcec" - ledger "github.com/zondax/ledger-goclient" -) - -var device *ledger.Ledger - -type DerivationPath = []uint32 - -// getLedger gets a copy of the device, and caches it -func getLedger() (*ledger.Ledger, error) { - var err error - if device == nil { - device, err = ledger.FindLedger() - } - return device, err -} - -func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { - key, err := device.GetPublicKeySECP256K1(path) - if err != nil { - return pub, fmt.Errorf("Error fetching public key: %v", err) - } - var p PubKeySecp256k1 - // Reserialize in the 33-byte compressed format - cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) - copy(p[:], cmp.SerializeCompressed()) - return p, err -} - -func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { - bsig, err := device.SignSECP256K1(path, msg) - if err != nil { - return sig, err - } - sig = SignatureSecp256k1FromBytes(bsig) - return sig, nil -} - -// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano -// we cache the PubKey from the first call to use it later -type PrivKeyLedgerSecp256k1 struct { - // PubKey should be private, but we want to encode it via go-amino - // so we can view the address later, even without having the ledger - // attached - CachedPubKey PubKey - Path DerivationPath -} - -// NewPrivKeyLedgerSecp256k1 will generate a new key and store the -// public key for later use. -func NewPrivKeyLedgerSecp256k1(path DerivationPath) (PrivKey, error) { - var pk PrivKeyLedgerSecp256k1 - pk.Path = path - // getPubKey will cache the pubkey for later use, - // this allows us to return an error early if the ledger - // is not plugged in - _, err := pk.getPubKey() - return &pk, err -} - -// ValidateKey allows us to verify the sanity of a key -// after loading it from disk -func (pk PrivKeyLedgerSecp256k1) ValidateKey() error { - // getPubKey will return an error if the ledger is not - // properly set up... - pub, err := pk.forceGetPubKey() - if err != nil { - return err - } - // verify this matches cached address - if !pub.Equals(pk.CachedPubKey) { - return errors.New("Cached key does not match retrieved key") - } - return nil -} - -// AssertIsPrivKeyInner fulfils PrivKey Interface -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 -} - -// Sign calls the ledger and stores the PubKey for future use -// -// XXX/TODO: panics if there is an error communicating with the ledger. -// -// 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 - dev, err := getLedger() - if err != nil { - panic(err) - } - - sig, err := signLedgerSecp256k1(dev, pk.Path, msg) - if err != nil { - panic(err) - } - - pub, err := pubkeyLedgerSecp256k1(dev, pk.Path) - if err != nil { - panic(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 sig -} - -// PubKey returns the stored PubKey -// TODO: query the ledger if not there, once it is not volatile -func (pk PrivKeyLedgerSecp256k1) PubKey() PubKey { - key, err := pk.getPubKey() - if err != nil { - panic(err) - } - return key -} - -// getPubKey reads the pubkey from cache or from the ledger itself -// since this involves IO, it may return an error, which is not exposed -// in the PubKey interface, so this function allows better error handling -func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) { - // if we have no pubkey, set it - if pk.CachedPubKey == nil { - pk.CachedPubKey, err = pk.forceGetPubKey() - } - return pk.CachedPubKey, err -} - -// forceGetPubKey is like getPubKey but ignores any cached key -// and ensures we get it from the ledger itself. -func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) { - dev, err := getLedger() - if err != nil { - return key, errors.New(fmt.Sprintf("Cannot connect to Ledger device - error: %v", err)) - } - key, err = pubkeyLedgerSecp256k1(dev, pk.Path) - if err != nil { - return key, errors.New(fmt.Sprintf("Please open Cosmos app on the Ledger device - error: %v", err)) - } - return key, err -} - -// Equals fulfils PrivKey Interface - makes sure both keys refer to the -// same -func (pk PrivKeyLedgerSecp256k1) Equals(other PrivKey) bool { - if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok { - return pk.CachedPubKey.Equals(ledger.CachedPubKey) - } - return false -} diff --git a/ledger_secp256k1.go b/ledger_secp256k1.go index 1a7887a..f485bb4 100644 --- a/ledger_secp256k1.go +++ b/ledger_secp256k1.go @@ -73,7 +73,11 @@ 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 { - return cdc.MustMarshalBinaryBare(pk) + bin, err := cdc.MarshalBinaryBare(pk) + if err != nil { + panic(err) + } + return bin } // Sign calls the ledger and stores the PubKey for future use @@ -81,34 +85,39 @@ 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, error) { +func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature { + // oh, I wish there was better error handling dev, err := getLedger() if err != nil { - return nil, err + panic(err) } sig, err := signLedgerSecp256k1(dev, pk.Path, msg) if err != nil { - return nil, err + panic(err) } pub, err := pubkeyLedgerSecp256k1(dev, pk.Path) if err != nil { - return nil, err + panic(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) { - return nil, fmt.Errorf("stored key does not match signing key") + panic("stored key does not match signing key") } - return sig, nil + return sig } // PubKey returns the stored PubKey -func (pk PrivKeyLedgerSecp256k1) PubKey() (PubKey, error) { - return pk.getPubKey() +func (pk PrivKeyLedgerSecp256k1) PubKey() PubKey { + key, err := pk.getPubKey() + if err != nil { + panic(err) + } + return key } // getPubKey reads the pubkey from cache or from the ledger itself diff --git a/version.go b/version.go index 31d9d2d..9f8af38 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package crypto -const Version = "0.8.0" +const Version = "0.9.0" From 11456dcaa31067dcd7302daa3e02c8f2e02f3cd0 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 12 Jun 2018 22:35:22 +0200 Subject: [PATCH 3/3] Remove error from priv.PubKey() (#131) * Remove error from priv.PubKey() * Update changelog --- CHANGELOG.md | 6 ++++++ Gopkg.lock | 8 ++++---- encode_test.go | 3 +-- keys/keybase.go | 15 +++------------ keys/keybase_test.go | 3 +-- ledger_secp256k1.go | 33 +++------------------------------ ledger_test.go | 6 ++---- priv_key.go | 10 +++++----- priv_key_test.go | 3 +-- pub_key_test.go | 3 +-- signature_test.go | 5 ++--- 11 files changed, 29 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c956e..aad87aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.9.0 + +BREAKING CHANGES + +- `priv.PubKey()` no longer returns an error. Any applicable errors (such as when fetching the public key from a hardware wallet) should be checked and returned when constructing the private key. + ## 0.8.0 BREAKING CHANGES diff --git a/Gopkg.lock b/Gopkg.lock index a475c66..fbfd54d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -99,8 +99,8 @@ "assert", "require" ] - revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" - version = "v1.2.1" + revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" + version = "v1.2.2" [[projects]] branch = "master" @@ -181,11 +181,11 @@ branch = "master" name = "golang.org/x/sys" packages = ["cpu"] - revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb" + revision = "a9e25c09b96b8870693763211309e213c6ef299d" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "f20e34cd998442d4ffe2f9aa45ab87a55ba6e4cd19f29009adaadac3b5dccf26" + inputs-digest = "1731880d432026bbd22865582cf7c7dd6d5c65618149de586e30ce156d338c01" solver-name = "gps-cdcl" solver-version = 1 diff --git a/encode_test.go b/encode_test.go index ed5395d..025b09c 100644 --- a/encode_test.go +++ b/encode_test.go @@ -91,8 +91,7 @@ func TestKeyEncodings(t *testing.T) { assert.EqualValues(t, sig1, sig3) // Check (de/en)codings of PubKeys. - pubKey, err := tc.privKey.PubKey() - assert.NoError(t, err) + pubKey := tc.privKey.PubKey() 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 39a3de5..79e6f15 100644 --- a/keys/keybase.go +++ b/keys/keybase.go @@ -68,10 +68,7 @@ func (kb dbKeybase) CreateLedger(name string, path crypto.DerivationPath, algo S if err != nil { return nil, err } - pub, err := priv.PubKey() - if err != nil { - return nil, err - } + pub := priv.PubKey() return kb.writeLedgerKey(pub, path, name), nil } @@ -169,10 +166,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signat if err != nil { return nil, nil, err } - pub, err = priv.PubKey() - if err != nil { - return nil, nil, err - } + pub = priv.PubKey() return sig, pub, nil } @@ -290,10 +284,7 @@ func (kb dbKeybase) writeLocalKey(priv crypto.PrivKey, name, passphrase string) // encrypt private key using passphrase privArmor := encryptArmorPrivKey(priv, passphrase) // make Info - pub, err := priv.PubKey() - if err != nil { - panic(err) - } + pub := priv.PubKey() info := newLocalInfo(name, pub, privArmor) kb.writeInfo(info, name) return info diff --git a/keys/keybase_test.go b/keys/keybase_test.go index 3627626..5caf82a 100644 --- a/keys/keybase_test.go +++ b/keys/keybase_test.go @@ -70,8 +70,7 @@ func TestKeyManagement(t *testing.T) { // create an offline key o1 := "offline" priv1 := crypto.GenPrivKeyEd25519() - pub1, err := priv1.PubKey() - require.Nil(t, err) + pub1 := priv1.PubKey() i, err = cstore.CreateOffline(o1, pub1) require.Nil(t, err) require.Equal(t, pub1, i.GetPubKey()) diff --git a/ledger_secp256k1.go b/ledger_secp256k1.go index f485bb4..c89040e 100644 --- a/ledger_secp256k1.go +++ b/ledger_secp256k1.go @@ -91,49 +91,22 @@ func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature { if err != nil { panic(err) } - sig, err := signLedgerSecp256k1(dev, pk.Path, msg) if err != nil { panic(err) } - - pub, err := pubkeyLedgerSecp256k1(dev, pk.Path) - if err != nil { - panic(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 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 + return pk.CachedPubKey } -// getPubKey reads the pubkey from cache or from the ledger itself +// getPubKey reads the pubkey the ledger itself // since this involves IO, it may return an error, which is not exposed // in the PubKey interface, so this function allows better error handling func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) { - // if we have no pubkey, set it - if pk.CachedPubKey == nil { - pk.CachedPubKey, err = pk.forceGetPubKey() - } - return pk.CachedPubKey, err -} - -// forceGetPubKey is like getPubKey but ignores any cached key -// and ensures we get it from the ledger itself. -func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) { dev, err := getLedger() if err != nil { return key, fmt.Errorf("cannot connect to Ledger device - error: %v", err) diff --git a/ledger_test.go b/ledger_test.go index 4a7ae46..83390cc 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -19,8 +19,7 @@ func TestRealLedgerSecp256k1(t *testing.T) { priv, err := NewPrivKeyLedgerSecp256k1(path) require.Nil(t, err, "%+v", err) - pub, err := priv.PubKey() - require.Nil(t, err) + pub := priv.PubKey() sig, err := priv.Sign(msg) require.Nil(t, err) @@ -33,8 +32,7 @@ func TestRealLedgerSecp256k1(t *testing.T) { require.Nil(t, err, "%+v", err) // make sure we get the same pubkey when we load from disk - pub2, err := priv2.PubKey() - require.Nil(t, err) + pub2 := priv2.PubKey() require.Equal(t, pub, pub2) // signing with the loaded key should match the original pubkey diff --git a/priv_key.go b/priv_key.go index 2c06f34..dbfe64c 100644 --- a/priv_key.go +++ b/priv_key.go @@ -18,7 +18,7 @@ func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) { type PrivKey interface { Bytes() []byte Sign(msg []byte) (Signature, error) - PubKey() (PubKey, error) + PubKey() PubKey Equals(PrivKey) bool } @@ -39,10 +39,10 @@ func (privKey PrivKeyEd25519) Sign(msg []byte) (Signature, error) { return SignatureEd25519(*signatureBytes), nil } -func (privKey PrivKeyEd25519) PubKey() (PubKey, error) { +func (privKey PrivKeyEd25519) PubKey() PubKey { privKeyBytes := [64]byte(privKey) pubBytes := *ed25519.MakePublicKey(&privKeyBytes) - return PubKeyEd25519(pubBytes), nil + return PubKeyEd25519(pubBytes) } // Equals - you probably don't need to use this. @@ -115,11 +115,11 @@ func (privKey PrivKeySecp256k1) Sign(msg []byte) (Signature, error) { return SignatureSecp256k1(sig__.Serialize()), nil } -func (privKey PrivKeySecp256k1) PubKey() (PubKey, error) { +func (privKey PrivKeySecp256k1) PubKey() PubKey { _, pub__ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:]) var pub PubKeySecp256k1 copy(pub[:], pub__.SerializeCompressed()) - return pub, nil + return pub } // Equals - you probably don't need to use this. diff --git a/priv_key_test.go b/priv_key_test.go index 33f3eb7..c43a6d1 100644 --- a/priv_key_test.go +++ b/priv_key_test.go @@ -11,8 +11,7 @@ func TestGeneratePrivKey(t *testing.T) { testPriv := crypto.GenPrivKeyEd25519() testGenerate := testPriv.Generate(1) signBytes := []byte("something to sign") - pub, err := testGenerate.PubKey() - assert.NoError(t, err) + pub := testGenerate.PubKey() 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 35c78a4..7b856cf 100644 --- a/pub_key_test.go +++ b/pub_key_test.go @@ -33,8 +33,7 @@ func TestPubKeySecp256k1Address(t *testing.T) { var priv PrivKeySecp256k1 copy(priv[:], privB) - pubKey, err := priv.PubKey() - assert.NoError(t, err) + pubKey := priv.PubKey() pubT, _ := pubKey.(PubKeySecp256k1) pub := pubT[:] addr := pubKey.Address() diff --git a/signature_test.go b/signature_test.go index be15b87..3f080b2 100644 --- a/signature_test.go +++ b/signature_test.go @@ -3,15 +3,14 @@ package crypto import ( "testing" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSignAndValidateEd25519(t *testing.T) { privKey := GenPrivKeyEd25519() - pubKey, err := privKey.PubKey() - require.Nil(t, err) + pubKey := privKey.PubKey() msg := CRandBytes(128) sig, err := privKey.Sign(msg)