From 43570388a9dbb56e098bb30ff61d4b0d8795ae09 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 9 May 2018 13:48:49 -0700 Subject: [PATCH 01/22] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 785e887b8..4a524c014 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ go-crypto is the cryptographic package adapted for Tendermint's uses ## Binary encoding +For Binary encoding, please refer to the [Tendermint encoding spec](https://github.com/tendermint/tendermint/blob/develop/docs/specification/new-spec/encoding.md). + +## JSON Encoding + go-crypto `.Bytes()` uses Amino:binary encoding, but Amino:JSON is also supported. ```go From 1c9ff46e98df9e76d4b9a160c4635d72afd12f04 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 30 Apr 2018 16:42:11 +0200 Subject: [PATCH 02/22] Ledger integration, WIP --- Gopkg.lock | 14 ++- Gopkg.toml | 4 + _nano/keys.go | 294 --------------------------------------------- _nano/keys_test.go | 142 ---------------------- _nano/sign.go | 63 ---------- _nano/sign_test.go | 160 ------------------------ amino.go | 2 + ledger.go | 154 ++++++++++++++++++++++++ ledger_test.go | 72 +++++++++++ signature.go | 6 + 10 files changed, 251 insertions(+), 660 deletions(-) delete mode 100644 _nano/keys.go delete mode 100644 _nano/keys_test.go delete mode 100644 _nano/sign.go delete mode 100644 _nano/sign_test.go create mode 100644 ledger.go create mode 100644 ledger_test.go diff --git a/Gopkg.lock b/Gopkg.lock index f52af5591..fbfaa63fa 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,12 @@ # 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" @@ -147,6 +153,12 @@ packages = ["."] revision = "8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc" +[[projects]] + branch = "master" + name = "github.com/zondax/ledger-goclient" + packages = ["."] + revision = "0eb48e14b06efd0354c2e0e18f15db121c64b9b8" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -166,6 +178,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "f9ccfa2cadfcbfb43bf729b871a0ad2f8d4f4acb118cd859e6faf9b24842b840" + inputs-digest = "f3cfb54414cb9d59bab79226c7778673e7ac5b7a464baf9b2ea76c1f2563631e" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 4ccb8c07d..3737ec5f9 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -57,6 +57,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/_nano/keys.go b/_nano/keys.go deleted file mode 100644 index 8cf1c3721..000000000 --- a/_nano/keys.go +++ /dev/null @@ -1,294 +0,0 @@ -package nano - -import ( - "bytes" - "encoding/hex" - - "github.com/pkg/errors" - - ledger "github.com/ethanfrey/ledger" - - crypto "github.com/tendermint/go-crypto" - amino "github.com/tendermint/go-amino" -) - -//nolint -const ( - NameLedgerEd25519 = "ledger-ed25519" - TypeLedgerEd25519 = 0x10 - - // Timeout is the number of seconds to wait for a response from the ledger - // if eg. waiting for user confirmation on button push - Timeout = 20 -) - -var device *ledger.Ledger - -// 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 signLedger(device *ledger.Ledger, msg []byte) (pub crypto.PubKey, sig crypto.Signature, err error) { - var resp []byte - - packets := generateSignRequests(msg) - for _, pack := range packets { - resp, err = device.Exchange(pack, Timeout) - if err != nil { - return pub, sig, err - } - } - - // the last call is the result we want and needs to be parsed - key, bsig, err := parseDigest(resp) - if err != nil { - return pub, sig, err - } - - var b [32]byte - copy(b[:], key) - return PubKeyLedgerEd25519FromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil -} - -// PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano -// we cache the PubKey from the first call to use it later -type PrivKeyLedgerEd25519 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 crypto.PubKey -} - -// NewPrivKeyLedgerEd25519 will generate a new key and store the -// public key for later use. -func NewPrivKeyLedgerEd25519() (crypto.PrivKey, error) { - var pk PrivKeyLedgerEd25519 - // 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.Wrap(), err -} - -// ValidateKey allows us to verify the sanity of a key -// after loading it from disk -func (pk *PrivKeyLedgerEd25519) 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("ledger doesn't match cached key") - } - return nil -} - -// AssertIsPrivKeyInner fulfils PrivKey Interface -func (pk *PrivKeyLedgerEd25519) 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 *PrivKeyLedgerEd25519) Bytes() []byte { - return amino.BinaryBytes(pk.Wrap()) -} - -// 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 *PrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { - // oh, I wish there was better error handling - dev, err := getLedger() - if err != nil { - panic(err) - } - - pub, sig, err := signLedger(dev, msg) - if err != nil { - panic(err) - } - - // if we have no pubkey yet, store it for future queries - if pk.CachedPubKey.Empty() { - pk.CachedPubKey = pub - } else if !pk.CachedPubKey.Equals(pub) { - panic("signed with a different key than stored") - } - return sig -} - -// PubKey returns the stored PubKey -// TODO: query the ledger if not there, once it is not volatile -func (pk *PrivKeyLedgerEd25519) PubKey() crypto.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 *PrivKeyLedgerEd25519) getPubKey() (key crypto.PubKey, err error) { - // if we have no pubkey, set it - if pk.CachedPubKey.Empty() { - 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 *PrivKeyLedgerEd25519) forceGetPubKey() (key crypto.PubKey, err error) { - dev, err := getLedger() - if err != nil { - return key, errors.New("Can't connect to ledger device") - } - key, _, err = signLedger(dev, []byte{0}) - if err != nil { - return key, errors.New("Please open cosmos app on the ledger") - } - return key, err -} - -// Equals fulfils PrivKey Interface - makes sure both keys refer to the -// same -func (pk *PrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { - if ledger, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok { - return pk.CachedPubKey.Equals(ledger.CachedPubKey) - } - return false -} - -// MockPrivKeyLedgerEd25519 behaves as the ledger, but stores a pre-packaged call-response -// for use in test cases -type MockPrivKeyLedgerEd25519 struct { - Msg []byte - Pub [KeyLength]byte - Sig [SigLength]byte -} - -// NewMockKey returns -func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedgerEd25519) { - var err error - pk.Msg, err = hex.DecodeString(msg) - if err != nil { - panic(err) - } - - bpk, err := hex.DecodeString(pubkey) - if err != nil { - panic(err) - } - bsig, err := hex.DecodeString(sig) - if err != nil { - panic(err) - } - - copy(pk.Pub[:], bpk) - copy(pk.Sig[:], bsig) - return pk -} - -var _ crypto.PrivKeyInner = MockPrivKeyLedgerEd25519{} - -// AssertIsPrivKeyInner fulfils PrivKey Interface -func (pk MockPrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} - -// Bytes fulfils PrivKey Interface - not supported -func (pk MockPrivKeyLedgerEd25519) Bytes() []byte { - return nil -} - -// Sign returns a real SignatureLedger, if the msg matches what we expect -func (pk MockPrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { - if !bytes.Equal(pk.Msg, msg) { - panic("Mock key is for different msg") - } - return crypto.SignatureEd25519(pk.Sig).Wrap() -} - -// PubKey returns a real PubKeyLedgerEd25519, that will verify this signature -func (pk MockPrivKeyLedgerEd25519) PubKey() crypto.PubKey { - return PubKeyLedgerEd25519FromBytes(pk.Pub) -} - -// Equals compares that two Mocks have the same data -func (pk MockPrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { - if mock, ok := other.Unwrap().(MockPrivKeyLedgerEd25519); ok { - return bytes.Equal(mock.Pub[:], pk.Pub[:]) && - bytes.Equal(mock.Sig[:], pk.Sig[:]) && - bytes.Equal(mock.Msg, pk.Msg) - } - return false -} - -//////////////////////////////////////////// -// pubkey - -// PubKeyLedgerEd25519 works like a normal Ed25519 except a hash before the verify bytes -type PubKeyLedgerEd25519 struct { - crypto.PubKeyEd25519 -} - -// PubKeyLedgerEd25519FromBytes creates a PubKey from the raw bytes -func PubKeyLedgerEd25519FromBytes(key [32]byte) crypto.PubKey { - return PubKeyLedgerEd25519{crypto.PubKeyEd25519(key)}.Wrap() -} - -// Bytes fulfils pk Interface - no data, just type info -func (pk PubKeyLedgerEd25519) Bytes() []byte { - return amino.BinaryBytes(pk.Wrap()) -} - -// VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand -func (pk PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig crypto.Signature) bool { - hmsg := hashMsg(msg) - return pk.PubKeyEd25519.VerifyBytes(hmsg, sig) -} - -// Equals implements PubKey interface -func (pk PubKeyLedgerEd25519) Equals(other crypto.PubKey) bool { - if ledger, ok := other.Unwrap().(PubKeyLedgerEd25519); ok { - return pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519.Wrap()) - } - return false -} - -/*** registration with go-data ***/ - -func init() { - crypto.PrivKeyMapper. - RegisterImplementation(&PrivKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519). - RegisterImplementation(MockPrivKeyLedgerEd25519{}, "mock-ledger", 0x11) - - crypto.PubKeyMapper. - RegisterImplementation(PubKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519) -} - -// Wrap fulfils interface for PrivKey struct -func (pk *PrivKeyLedgerEd25519) Wrap() crypto.PrivKey { - return crypto.PrivKey{PrivKeyInner: pk} -} - -// Wrap fulfils interface for PrivKey struct -func (pk MockPrivKeyLedgerEd25519) Wrap() crypto.PrivKey { - return crypto.PrivKey{PrivKeyInner: pk} -} - -// Wrap fulfils interface for PubKey struct -func (pk PubKeyLedgerEd25519) Wrap() crypto.PubKey { - return crypto.PubKey{PubKeyInner: pk} -} diff --git a/_nano/keys_test.go b/_nano/keys_test.go deleted file mode 100644 index fda096e29..000000000 --- a/_nano/keys_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package nano - -import ( - "encoding/hex" - "os" - "testing" - - asrt "github.com/stretchr/testify/assert" - rqr "github.com/stretchr/testify/require" - - crypto "github.com/tendermint/go-crypto" -) - -func TestLedgerKeys(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - cases := []struct { - msg, pubkey, sig string - valid bool - }{ - 0: { - msg: "F00D", - pubkey: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", - sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", - valid: true, - }, - 1: { - msg: "DEADBEEF", - pubkey: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C", - sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00", - valid: true, - }, - 2: { - msg: "1234567890AA", - pubkey: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA", - sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B", - valid: true, - }, - 3: { - msg: "1234432112344321", - pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: true, - }, - 4: { - msg: "12344321123443", - pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - 5: { - msg: "1234432112344321", - pubkey: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - 6: { - msg: "1234432112344321", - pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - } - - for i, tc := range cases { - bmsg, err := hex.DecodeString(tc.msg) - require.NoError(err, "%d", i) - - priv := NewMockKey(tc.msg, tc.pubkey, tc.sig) - pub := priv.PubKey() - sig := priv.Sign(bmsg) - - valid := pub.VerifyBytes(bmsg, sig) - assert.Equal(tc.valid, valid, "%d", i) - } -} - -func TestRealLedger(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - if os.Getenv("WITH_LEDGER") == "" { - t.Skip("Set WITH_LEDGER to run code on real ledger") - } - msg := []byte("kuhehfeohg") - - priv, err := NewPrivKeyLedgerEd25519() - require.Nil(err, "%+v", err) - pub := priv.PubKey() - sig := priv.Sign(msg) - - valid := pub.VerifyBytes(msg, sig) - assert.True(valid) - - // now, let's serialize the key and make sure it still works - bs := priv.Bytes() - priv2, err := crypto.PrivKeyFromBytes(bs) - require.Nil(err, "%+v", err) - - // make sure we get the same pubkey when we load from disk - pub2 := priv2.PubKey() - require.Equal(pub, pub2) - - // signing with the loaded key should match the original pubkey - sig = priv2.Sign(msg) - valid = pub.VerifyBytes(msg, sig) - assert.True(valid) - - // make sure pubkeys serialize properly as well - bs = pub.Bytes() - bpub, err := crypto.PubKeyFromBytes(bs) - require.NoError(err) - assert.Equal(pub, bpub) -} - -// TestRealLedgerErrorHandling calls. These tests assume -// the ledger is not plugged in.... -func TestRealLedgerErrorHandling(t *testing.T) { - require := rqr.New(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) - _, err := NewPrivKeyLedgerEd25519() - require.Error(err) - - led := PrivKeyLedgerEd25519{} // empty - // or with some pub key - ed := crypto.GenPrivKeyEd25519() - led2 := PrivKeyLedgerEd25519{CachedPubKey: ed.PubKey()} - - // loading these should return errors - bs := led.Bytes() - _, err = crypto.PrivKeyFromBytes(bs) - require.Error(err) - - bs = led2.Bytes() - _, err = crypto.PrivKeyFromBytes(bs) - require.Error(err) -} diff --git a/_nano/sign.go b/_nano/sign.go deleted file mode 100644 index c40801583..000000000 --- a/_nano/sign.go +++ /dev/null @@ -1,63 +0,0 @@ -package nano - -import ( - "bytes" - "crypto/sha512" - - "github.com/pkg/errors" -) - -const ( - App = 0x80 - Init = 0x00 - Update = 0x01 - Digest = 0x02 - MaxChunk = 253 - KeyLength = 32 - SigLength = 64 -) - -var separator = []byte{0, 0xCA, 0xFE, 0} - -func generateSignRequests(payload []byte) [][]byte { - // nice one-shot - digest := []byte{App, Digest} - if len(payload) < MaxChunk { - return [][]byte{append(digest, payload...)} - } - - // large payload is multi-chunk - result := [][]byte{{App, Init}} - update := []byte{App, Update} - for len(payload) > MaxChunk { - msg := append(update, payload[:MaxChunk]...) - payload = payload[MaxChunk:] - result = append(result, msg) - } - result = append(result, append(update, payload...)) - result = append(result, digest) - return result -} - -func parseDigest(resp []byte) (key, sig []byte, err error) { - if resp[0] != App || resp[1] != Digest { - return nil, nil, errors.New("Invalid header") - } - resp = resp[2:] - if len(resp) != KeyLength+SigLength+len(separator) { - return nil, nil, errors.Errorf("Incorrect length: %d", len(resp)) - } - - key, resp = resp[:KeyLength], resp[KeyLength:] - if !bytes.Equal(separator, resp[:len(separator)]) { - return nil, nil, errors.New("Cannot find 0xCAFE") - } - - sig = resp[len(separator):] - return key, sig, nil -} - -func hashMsg(data []byte) []byte { - res := sha512.Sum512(data) - return res[:] -} diff --git a/_nano/sign_test.go b/_nano/sign_test.go deleted file mode 100644 index 18e4e0d0b..000000000 --- a/_nano/sign_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package nano - -import ( - "encoding/hex" - "testing" - - "github.com/pkg/errors" - asrt "github.com/stretchr/testify/assert" - rqr "github.com/stretchr/testify/require" - - crypto "github.com/tendermint/go-crypto" -) - -func parseEdKey(data []byte) (key crypto.PubKey, err error) { - ed := crypto.PubKeyEd25519{} - if len(data) < len(ed) { - return key, errors.Errorf("Key length too short: %d", len(data)) - } - copy(ed[:], data) - return ed.Wrap(), nil -} - -func parseSig(data []byte) (key crypto.Signature, err error) { - ed := crypto.SignatureEd25519{} - if len(data) < len(ed) { - return key, errors.Errorf("Sig length too short: %d", len(data)) - } - copy(ed[:], data) - return ed.Wrap(), nil -} - -func TestParseDigest(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - cases := []struct { - output string - key string - sig string - valid bool - }{ - { - output: "80028E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C9300CAFE00787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", - key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", - sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", - valid: true, - }, - { - output: "800235467890876543525437890796574535467890", - key: "", - sig: "", - valid: false, - }, - } - - for i, tc := range cases { - msg, err := hex.DecodeString(tc.output) - require.Nil(err, "%d: %+v", i, err) - - lKey, lSig, err := parseDigest(msg) - if !tc.valid { - assert.NotNil(err, "%d", i) - } else if assert.Nil(err, "%d: %+v", i, err) { - key, err := hex.DecodeString(tc.key) - require.Nil(err, "%d: %+v", i, err) - sig, err := hex.DecodeString(tc.sig) - require.Nil(err, "%d: %+v", i, err) - - assert.Equal(key, lKey, "%d", i) - assert.Equal(sig, lSig, "%d", i) - } - } -} - -type cryptoCase struct { - msg string - key string - sig string - valid bool -} - -func toBytes(c cryptoCase) (msg, key, sig []byte, err error) { - msg, err = hex.DecodeString(c.msg) - if err != nil { - return - } - key, err = hex.DecodeString(c.key) - if err != nil { - return - } - sig, err = hex.DecodeString(c.sig) - return -} - -func TestCryptoConvert(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - cases := []cryptoCase{ - 0: { - msg: "F00D", - key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", - sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", - valid: true, - }, - 1: { - msg: "DEADBEEF", - key: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C", - sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00", - valid: true, - }, - 2: { - msg: "1234567890AA", - key: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA", - sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B", - valid: true, - }, - 3: { - msg: "1234432112344321", - key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: true, - }, - 4: { - msg: "12344321123443", - key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - 5: { - msg: "1234432112344321", - key: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - 6: { - msg: "1234432112344321", - key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - } - - for i, tc := range cases { - msg, key, sig, err := toBytes(tc) - require.Nil(err, "%d: %+v", i, err) - - pk, err := parseEdKey(key) - require.Nil(err, "%d: %+v", i, err) - psig, err := parseSig(sig) - require.Nil(err, "%d: %+v", i, err) - - // it is not the signature of the message itself - valid := pk.VerifyBytes(msg, psig) - assert.False(valid, "%d", i) - - // but rather of the hash of the msg - hmsg := hashMsg(msg) - valid = pk.VerifyBytes(hmsg, psig) - assert.Equal(tc.valid, valid, "%d", i) - } -} diff --git a/amino.go b/amino.go index 89636895a..2af765434 100644 --- a/amino.go +++ b/amino.go @@ -27,6 +27,8 @@ 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{}, diff --git a/ledger.go b/ledger.go new file mode 100644 index 000000000..0fe4b7d39 --- /dev/null +++ b/ledger.go @@ -0,0 +1,154 @@ +package crypto + +import ( + "github.com/pkg/errors" + + // secp256k1 "github.com/btcsuite/btcd/btcec" + ledger "github.com/zondax/ledger-goclient" +) + +var device *ledger.Ledger + +// 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 signLedger(device *ledger.Ledger, msg []byte) (pub PubKey, sig Signature, err error) { + bsig, err := device.Sign(msg) + if err != nil { + return pub, sig, err + } + key, err := device.GetPublicKey() + if err != nil { + return pub, sig, err + } + var p PubKeySecp256k1 + copy(p[:], key) + return p, SignatureSecp256k1FromBytes(bsig), 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 +} + +// NewPrivKeyLedgerSecp256k1 will generate a new key and store the +// public key for later use. +func NewPrivKeyLedgerSecp256k1() (PrivKey, error) { + var pk PrivKeyLedgerSecp256k1 + // 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("ledger doesn't match cached 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) + } + + pub, sig, err := signLedger(dev, msg) + 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("signed with a different key than stored") + } + 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("Can't connect to ledger device") + } + key, _, err = signLedger(dev, []byte{0}) + if err != nil { + return key, errors.New("Please open cosmos app on the ledger") + } + 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 000000000..848c2cece --- /dev/null +++ b/ledger_test.go @@ -0,0 +1,72 @@ +package crypto + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRealLedger(t *testing.T) { + + if os.Getenv("WITH_LEDGER") == "" { + t.Skip("Set WITH_LEDGER to run code on real ledger") + } + msg := []byte("kuhehfeohg") + + priv, err := NewPrivKeyLedgerSecp256k1() + require.Nil(t, err, "%+v", err) + pub := priv.PubKey() + sig := priv.Sign(msg) + + 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 := priv2.PubKey() + require.Equal(t, pub, pub2) + + // signing with the loaded key should match the original pubkey + sig = priv2.Sign(msg) + 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) + _, err := NewPrivKeyLedgerSecp256k1() + require.Error(t, err) + + led := PrivKeyLedgerSecp256k1{} // empty + // or with some pub key + ed := GenPrivKeySecp256k1() + led2 := PrivKeyLedgerSecp256k1{CachedPubKey: ed.PubKey()} + + // loading these should return errors + bs := led.Bytes() + _, err = PrivKeyFromBytes(bs) + require.Error(t, err) + + bs = led2.Bytes() + _, err = PrivKeyFromBytes(bs) + require.Error(t, err) +} diff --git a/signature.go b/signature.go index cfe927137..8bf151b41 100644 --- a/signature.go +++ b/signature.go @@ -79,3 +79,9 @@ func (sig SignatureSecp256k1) Equals(other Signature) bool { return false } } + +func SignatureSecp256k1FromBytes(data []byte) Signature { + var sig SignatureSecp256k1 + copy(sig[:], data) + return sig +} From 065c3943b1918e9bcbd55ede1df3ee6392552d41 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 30 Apr 2018 16:49:11 +0200 Subject: [PATCH 03/22] Fix no-Ledger testcase --- ledger_test.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ledger_test.go b/ledger_test.go index 848c2cece..7b2b4ea1e 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -55,18 +55,4 @@ func TestRealLedgerErrorHandling(t *testing.T) { // (no panic) _, err := NewPrivKeyLedgerSecp256k1() require.Error(t, err) - - led := PrivKeyLedgerSecp256k1{} // empty - // or with some pub key - ed := GenPrivKeySecp256k1() - led2 := PrivKeyLedgerSecp256k1{CachedPubKey: ed.PubKey()} - - // loading these should return errors - bs := led.Bytes() - _, err = PrivKeyFromBytes(bs) - require.Error(t, err) - - bs = led2.Bytes() - _, err = PrivKeyFromBytes(bs) - require.Error(t, err) } From e25a64fdf1e4aae53ad5e2780d0b4cc56eedaae6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 30 Apr 2018 19:34:19 +0200 Subject: [PATCH 04/22] Fix testcases, all looks OK --- ledger.go | 9 ++++++--- signature.go | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ledger.go b/ledger.go index 0fe4b7d39..b2b06415d 100644 --- a/ledger.go +++ b/ledger.go @@ -3,7 +3,7 @@ package crypto import ( "github.com/pkg/errors" - // secp256k1 "github.com/btcsuite/btcd/btcec" + secp256k1 "github.com/btcsuite/btcd/btcec" ledger "github.com/zondax/ledger-goclient" ) @@ -23,13 +23,16 @@ func signLedger(device *ledger.Ledger, msg []byte) (pub PubKey, sig Signature, e if err != nil { return pub, sig, err } + sig = SignatureSecp256k1FromBytes(bsig) key, err := device.GetPublicKey() if err != nil { return pub, sig, err } var p PubKeySecp256k1 - copy(p[:], key) - return p, SignatureSecp256k1FromBytes(bsig), nil + // Reserialize in the 33-byte compressed format + cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) + copy(p[:], cmp.SerializeCompressed()) + return p, sig, nil } // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano diff --git a/signature.go b/signature.go index 8bf151b41..a364dc7fb 100644 --- a/signature.go +++ b/signature.go @@ -81,7 +81,7 @@ func (sig SignatureSecp256k1) Equals(other Signature) bool { } func SignatureSecp256k1FromBytes(data []byte) Signature { - var sig SignatureSecp256k1 + sig := make(SignatureSecp256k1, len(data)) copy(sig[:], data) return sig } From 391936b7346a116053b301fd6c74ace17eac4d75 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 2 May 2018 17:10:32 +0200 Subject: [PATCH 05/22] Prevent unnecessary signatures, improve error messages --- ledger.go | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/ledger.go b/ledger.go index b2b06415d..18fdf478e 100644 --- a/ledger.go +++ b/ledger.go @@ -18,21 +18,25 @@ func getLedger() (*ledger.Ledger, error) { return device, err } -func signLedger(device *ledger.Ledger, msg []byte) (pub PubKey, sig Signature, err error) { - bsig, err := device.Sign(msg) - if err != nil { - return pub, sig, err - } - sig = SignatureSecp256k1FromBytes(bsig) +func pubkeyLedger(device *ledger.Ledger) (pub PubKey, err error) { key, err := device.GetPublicKey() if err != nil { - return pub, sig, err + return pub, err } var p PubKeySecp256k1 // Reserialize in the 33-byte compressed format cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) copy(p[:], cmp.SerializeCompressed()) - return p, sig, nil + return pub, err +} + +func signLedger(device *ledger.Ledger, msg []byte) (sig Signature, err error) { + bsig, err := device.Sign(msg) + if err != nil { + return sig, err + } + sig = SignatureSecp256k1FromBytes(bsig) + return sig, nil } // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano @@ -66,7 +70,7 @@ func (pk PrivKeyLedgerSecp256k1) ValidateKey() error { } // verify this matches cached address if !pub.Equals(pk.CachedPubKey) { - return errors.New("ledger doesn't match cached key") + return errors.New("Cached key does not match retrieved key") } return nil } @@ -98,7 +102,12 @@ func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature { panic(err) } - pub, sig, err := signLedger(dev, msg) + sig, err := signLedger(dev, msg) + if err != nil { + panic(err) + } + + pub, err := pubkeyLedger(dev) if err != nil { panic(err) } @@ -107,7 +116,7 @@ func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature { if pk.CachedPubKey == nil { pk.CachedPubKey = pub } else if !pk.CachedPubKey.Equals(pub) { - panic("signed with a different key than stored") + panic("Stored key does not match signing key") } return sig } @@ -138,11 +147,11 @@ func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) { func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) { dev, err := getLedger() if err != nil { - return key, errors.New("Can't connect to ledger device") + return key, errors.New("Cannot connect to Ledger device") } - key, _, err = signLedger(dev, []byte{0}) + key, err = pubkeyLedger(dev) if err != nil { - return key, errors.New("Please open cosmos app on the ledger") + return key, errors.New("Please open Cosmos app on the Ledger device") } return key, err } From 86b09b0cd7c3a7b6a4ce825564ede75a80d5605b Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 3 May 2018 06:26:58 +0200 Subject: [PATCH 06/22] Bugfix --- ledger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger.go b/ledger.go index 18fdf478e..4effa2590 100644 --- a/ledger.go +++ b/ledger.go @@ -27,7 +27,7 @@ func pubkeyLedger(device *ledger.Ledger) (pub PubKey, err error) { // Reserialize in the 33-byte compressed format cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) copy(p[:], cmp.SerializeCompressed()) - return pub, err + return p, err } func signLedger(device *ledger.Ledger, msg []byte) (sig Signature, err error) { From 49e03fb481075d3f8d601dfc73e9f34ff8edd9d6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 01:41:15 +0200 Subject: [PATCH 07/22] Update dependency versions --- Gopkg.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index fbfaa63fa..3e44587e1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,7 +11,7 @@ branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "2be2f12b358dc57d70b8f501b00be450192efbc3" + revision = "675abc5df3c5531bc741b56a765e35623459da6d" [[projects]] branch = "master" @@ -119,7 +119,7 @@ "leveldb/table", "leveldb/util" ] - revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad" + revision = "ae970a0732be3a1f5311da86118d37b9f4bd2a5a" [[projects]] branch = "master" @@ -134,8 +134,8 @@ [[projects]] name = "github.com/tendermint/go-amino" packages = ["."] - revision = "42246108ff925a457fb709475070a03dfd3e2b5c" - version = "0.9.6" + revision = "ed62928576cfcaf887209dc96142cd79cdfff389" + version = "0.9.9" [[projects]] name = "github.com/tendermint/tmlibs" @@ -144,8 +144,8 @@ "db", "log" ] - revision = "2e24b64fc121dcdf1cabceab8dc2f7257675483c" - version = "v0.8.1" + revision = "d94e312673e16a11ea55d742cefb3e331228f898" + version = "v0.8.2" [[projects]] branch = "master" @@ -157,7 +157,7 @@ branch = "master" name = "github.com/zondax/ledger-goclient" packages = ["."] - revision = "0eb48e14b06efd0354c2e0e18f15db121c64b9b8" + revision = "a242cfad962597a588288f99fa3425a01a820295" [[projects]] branch = "master" @@ -173,7 +173,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "b2aa35443fbc700ab74c586ae79b81c171851023" + revision = "2d027ae1dddd4694d54f7a8b6cbe78dca8720226" [solve-meta] analyzer-name = "dep" From 337ad8e594d31a16449cf2756ed33ba959ea3964 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 02:03:28 +0200 Subject: [PATCH 08/22] Update to new Ledger API in progress --- ledger.go | 19 +++++++++++-------- ledger_test.go | 7 +++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ledger.go b/ledger.go index 4effa2590..d44b2dab9 100644 --- a/ledger.go +++ b/ledger.go @@ -9,6 +9,8 @@ import ( 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 @@ -18,8 +20,8 @@ func getLedger() (*ledger.Ledger, error) { return device, err } -func pubkeyLedger(device *ledger.Ledger) (pub PubKey, err error) { - key, err := device.GetPublicKey() +func pubkeyLedger(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { + key, err := device.GetPublicKeySECP256K1(path) if err != nil { return pub, err } @@ -30,8 +32,8 @@ func pubkeyLedger(device *ledger.Ledger) (pub PubKey, err error) { return p, err } -func signLedger(device *ledger.Ledger, msg []byte) (sig Signature, err error) { - bsig, err := device.Sign(msg) +func signLedger(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { + bsig, err := device.SignSECP256K1(path, msg) if err != nil { return sig, err } @@ -46,11 +48,12 @@ type PrivKeyLedgerSecp256k1 struct { // 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() (PrivKey, error) { +func NewPrivKeyLedgerSecp256k1(path DerivationPath) (PrivKey, error) { var pk PrivKeyLedgerSecp256k1 // getPubKey will cache the pubkey for later use, // this allows us to return an error early if the ledger @@ -102,12 +105,12 @@ func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature { panic(err) } - sig, err := signLedger(dev, msg) + sig, err := signLedger(dev, pk.Path, msg) if err != nil { panic(err) } - pub, err := pubkeyLedger(dev) + pub, err := pubkeyLedger(dev, pk.Path) if err != nil { panic(err) } @@ -149,7 +152,7 @@ func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) { if err != nil { return key, errors.New("Cannot connect to Ledger device") } - key, err = pubkeyLedger(dev) + key, err = pubkeyLedger(dev, pk.Path) if err != nil { return key, errors.New("Please open Cosmos app on the Ledger device") } diff --git a/ledger_test.go b/ledger_test.go index 7b2b4ea1e..1c193b5f1 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -15,7 +15,9 @@ func TestRealLedger(t *testing.T) { } msg := []byte("kuhehfeohg") - priv, err := NewPrivKeyLedgerSecp256k1() + path := DerivationPath{44, 60, 0, 0, 0} + + priv, err := NewPrivKeyLedgerSecp256k1(path) require.Nil(t, err, "%+v", err) pub := priv.PubKey() sig := priv.Sign(msg) @@ -53,6 +55,7 @@ func TestRealLedgerErrorHandling(t *testing.T) { // first, try to generate a key, must return an error // (no panic) - _, err := NewPrivKeyLedgerSecp256k1() + path := DerivationPath{44, 60, 0, 0, 0} + _, err := NewPrivKeyLedgerSecp256k1(path) require.Error(t, err) } From e6d0ade0e1a8315613e026fe64b006f24f58beab Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 14 May 2018 15:33:10 +0200 Subject: [PATCH 09/22] Update to latest upstream, debugging information --- Gopkg.lock | 6 +++--- ledger.go | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 3e44587e1..d65f87b68 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -119,7 +119,7 @@ "leveldb/table", "leveldb/util" ] - revision = "ae970a0732be3a1f5311da86118d37b9f4bd2a5a" + revision = "9637fa0b2f0db13c99d899b91007edb7df4610b7" [[projects]] branch = "master" @@ -157,7 +157,7 @@ branch = "master" name = "github.com/zondax/ledger-goclient" packages = ["."] - revision = "a242cfad962597a588288f99fa3425a01a820295" + revision = "5b2fe84d3139027c29bd8f0c054ed9444f8a7622" [[projects]] branch = "master" @@ -173,7 +173,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "2d027ae1dddd4694d54f7a8b6cbe78dca8720226" + revision = "034e5325b6ab92faa14d15c30e86919e911cf8e0" [solve-meta] analyzer-name = "dep" diff --git a/ledger.go b/ledger.go index d44b2dab9..a9bc1d975 100644 --- a/ledger.go +++ b/ledger.go @@ -1,6 +1,7 @@ package crypto import ( + "fmt" "github.com/pkg/errors" secp256k1 "github.com/btcsuite/btcd/btcec" @@ -23,7 +24,7 @@ func getLedger() (*ledger.Ledger, error) { func pubkeyLedger(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { key, err := device.GetPublicKeySECP256K1(path) if err != nil { - return pub, err + return pub, fmt.Errorf("Error fetching public key: %v", err) } var p PubKeySecp256k1 // Reserialize in the 33-byte compressed format @@ -55,6 +56,7 @@ type PrivKeyLedgerSecp256k1 struct { // 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 @@ -150,11 +152,11 @@ func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) { func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) { dev, err := getLedger() if err != nil { - return key, errors.New("Cannot connect to Ledger device") + return key, errors.New(fmt.Sprintf("Cannot connect to Ledger device - error: %v", err)) } key, err = pubkeyLedger(dev, pk.Path) if err != nil { - return key, errors.New("Please open Cosmos app on the Ledger device") + return key, errors.New(fmt.Sprintf("Please open Cosmos app on the Ledger device - error: %v", err)) } return key, err } From e534559bdc88e0cdbecaf41bc0023f3fb57c9785 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 30 May 2018 01:39:50 +0200 Subject: [PATCH 10/22] Update upstream Ledger code --- Gopkg.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index d65f87b68..332714ee4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,13 +11,13 @@ branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "675abc5df3c5531bc741b56a765e35623459da6d" + revision = "bc0944904505aab55e089371a892be2f87883161" [[projects]] branch = "master" name = "github.com/btcsuite/btcutil" packages = ["base58"] - revision = "501929d3d046174c3d39f0ea54ece471aa17238c" + revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] name = "github.com/davecgh/go-spew" @@ -61,7 +61,7 @@ branch = "master" name = "github.com/golang/snappy" packages = ["."] - revision = "553a641470496b2327abcac10b36396bd98e45c9" + revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" [[projects]] branch = "master" @@ -119,7 +119,7 @@ "leveldb/table", "leveldb/util" ] - revision = "9637fa0b2f0db13c99d899b91007edb7df4610b7" + revision = "5d6fca44a948d2be89a9702de7717f0168403d3d" [[projects]] branch = "master" @@ -134,8 +134,8 @@ [[projects]] name = "github.com/tendermint/go-amino" packages = ["."] - revision = "ed62928576cfcaf887209dc96142cd79cdfff389" - version = "0.9.9" + revision = "3c22a7a539411f89a96738fcfa14c1027e24e5ec" + version = "0.9.10" [[projects]] name = "github.com/tendermint/tmlibs" @@ -144,8 +144,8 @@ "db", "log" ] - revision = "d94e312673e16a11ea55d742cefb3e331228f898" - version = "v0.8.2" + revision = "d970af87248a4e162590300dbb74e102183a417d" + version = "v0.8.3" [[projects]] branch = "master" @@ -157,7 +157,7 @@ branch = "master" name = "github.com/zondax/ledger-goclient" packages = ["."] - revision = "5b2fe84d3139027c29bd8f0c054ed9444f8a7622" + revision = "3e2146609cdb97894c064d59e9d00accd8c2b1dd" [[projects]] branch = "master" @@ -173,7 +173,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "034e5325b6ab92faa14d15c30e86919e911cf8e0" + revision = "ab813273cd59e1333f7ae7bff5d027d4aadf528c" [solve-meta] analyzer-name = "dep" From 3186dc4cef541ab8f4a7bd107b2894b1186a601a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 30 May 2018 03:29:42 +0200 Subject: [PATCH 11/22] Clarify function names --- ledger.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ledger.go b/ledger.go index a9bc1d975..5b681cda8 100644 --- a/ledger.go +++ b/ledger.go @@ -21,7 +21,7 @@ func getLedger() (*ledger.Ledger, error) { return device, err } -func pubkeyLedger(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { +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) @@ -33,7 +33,7 @@ func pubkeyLedger(device *ledger.Ledger, path DerivationPath) (pub PubKey, err e return p, err } -func signLedger(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { +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 @@ -107,12 +107,12 @@ func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature { panic(err) } - sig, err := signLedger(dev, pk.Path, msg) + sig, err := signLedgerSecp256k1(dev, pk.Path, msg) if err != nil { panic(err) } - pub, err := pubkeyLedger(dev, pk.Path) + pub, err := pubkeyLedgerSecp256k1(dev, pk.Path) if err != nil { panic(err) } @@ -154,7 +154,7 @@ func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) { if err != nil { return key, errors.New(fmt.Sprintf("Cannot connect to Ledger device - error: %v", err)) } - key, err = pubkeyLedger(dev, pk.Path) + 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)) } From ee411daa1778470e93f6cbc634c22ded60e40ea6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 30 May 2018 03:42:47 +0200 Subject: [PATCH 12/22] Add ed25519, tests will fail until ed25519 verification fix --- ledger_common.go | 19 ++++ ledger_ed25519.go | 156 +++++++++++++++++++++++++++++++ ledger.go => ledger_secp256k1.go | 13 --- ledger_test.go | 40 +++++++- 4 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 ledger_common.go create mode 100644 ledger_ed25519.go rename ledger.go => ledger_secp256k1.go (94%) diff --git a/ledger_common.go b/ledger_common.go new file mode 100644 index 000000000..39f15464a --- /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_ed25519.go b/ledger_ed25519.go new file mode 100644 index 000000000..0c9cd157f --- /dev/null +++ b/ledger_ed25519.go @@ -0,0 +1,156 @@ +package crypto + +import ( + "fmt" + "github.com/pkg/errors" + + // "github.com/tendermint/ed25519" + ledger "github.com/zondax/ledger-goclient" +) + +func pubkeyLedgerEd25519(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { + key, err := device.GetPublicKeyED25519(path) + if err != nil { + return pub, fmt.Errorf("Error fetching public key: %v", err) + } + var p PubKeyEd25519 + copy(p[:], key[0:32]) + return p, err +} + +func signLedgerEd25519(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { + bsig, err := device.SignED25519(path, msg) + if err != nil { + return sig, err + } + sig = SignatureEd25519FromBytes(bsig) + return sig, nil +} + +// PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano +// we cache the PubKey from the first call to use it later +type PrivKeyLedgerEd25519 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 +} + +// NewPrivKeyLedgerEd25519 will generate a new key and store the +// public key for later use. +func NewPrivKeyLedgerEd25519(path DerivationPath) (PrivKey, error) { + var pk PrivKeyLedgerEd25519 + 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 PrivKeyLedgerEd25519) 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 *PrivKeyLedgerEd25519) 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 PrivKeyLedgerEd25519) 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 PrivKeyLedgerEd25519) Sign(msg []byte) Signature { + // oh, I wish there was better error handling + dev, err := getLedger() + if err != nil { + panic(err) + } + + sig, err := signLedgerEd25519(dev, pk.Path, msg) + if err != nil { + panic(err) + } + + pub, err := pubkeyLedgerEd25519(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 PrivKeyLedgerEd25519) 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 PrivKeyLedgerEd25519) 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 PrivKeyLedgerEd25519) 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 = pubkeyLedgerEd25519(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 PrivKeyLedgerEd25519) Equals(other PrivKey) bool { + if ledger, ok := other.(*PrivKeyLedgerEd25519); ok { + return pk.CachedPubKey.Equals(ledger.CachedPubKey) + } + return false +} diff --git a/ledger.go b/ledger_secp256k1.go similarity index 94% rename from ledger.go rename to ledger_secp256k1.go index 5b681cda8..b574035d4 100644 --- a/ledger.go +++ b/ledger_secp256k1.go @@ -8,19 +8,6 @@ import ( 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 { diff --git a/ledger_test.go b/ledger_test.go index 1c193b5f1..a0ea35837 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestRealLedger(t *testing.T) { +func TestRealLedgerSecp256k1(t *testing.T) { if os.Getenv("WITH_LEDGER") == "" { t.Skip("Set WITH_LEDGER to run code on real ledger") @@ -46,6 +46,44 @@ func TestRealLedger(t *testing.T) { assert.Equal(t, pub, bpub) } +func TestRealLedgerEd25519(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 := NewPrivKeyLedgerEd25519(path) + require.Nil(t, err, "%+v", err) + pub := priv.PubKey() + sig := priv.Sign(msg) + + 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 := priv2.PubKey() + require.Equal(t, pub, pub2) + + // signing with the loaded key should match the original pubkey + sig = priv2.Sign(msg) + 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) { From c689f38cb587be1d4681c19e7ebea7ec8e35794e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 30 May 2018 22:07:20 +0200 Subject: [PATCH 13/22] Implement PubKeyLedgerEd25519 --- amino.go | 4 +++ ledger_ed25519.go | 2 +- ledger_pub_key.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 ledger_pub_key.go diff --git a/amino.go b/amino.go index 2af765434..b78a15ed9 100644 --- a/amino.go +++ b/amino.go @@ -19,6 +19,8 @@ func RegisterAmino(cdc *amino.Codec) { cdc.RegisterInterface((*PubKey)(nil), nil) cdc.RegisterConcrete(PubKeyEd25519{}, "tendermint/PubKeyEd25519", nil) + cdc.RegisterConcrete(PubKeyLedgerEd25519{}, + "tendermint/PubKeyLedgerEd25519", nil) cdc.RegisterConcrete(PubKeySecp256k1{}, "tendermint/PubKeySecp256k1", nil) @@ -29,6 +31,8 @@ func RegisterAmino(cdc *amino.Codec) { "tendermint/PrivKeySecp256k1", nil) cdc.RegisterConcrete(PrivKeyLedgerSecp256k1{}, "tendermint/PrivKeyLedgerSecp256k1", nil) + cdc.RegisterConcrete(PrivKeyLedgerEd25519{}, + "tendermint/PrivKeyLedgerEd25519", nil) cdc.RegisterInterface((*Signature)(nil), nil) cdc.RegisterConcrete(SignatureEd25519{}, diff --git a/ledger_ed25519.go b/ledger_ed25519.go index 0c9cd157f..2110b8871 100644 --- a/ledger_ed25519.go +++ b/ledger_ed25519.go @@ -13,7 +13,7 @@ func pubkeyLedgerEd25519(device *ledger.Ledger, path DerivationPath) (pub PubKey if err != nil { return pub, fmt.Errorf("Error fetching public key: %v", err) } - var p PubKeyEd25519 + var p PubKeyLedgerEd25519 copy(p[:], key[0:32]) return p, err } diff --git a/ledger_pub_key.go b/ledger_pub_key.go new file mode 100644 index 000000000..0a5222577 --- /dev/null +++ b/ledger_pub_key.go @@ -0,0 +1,68 @@ +package crypto + +import ( + "bytes" + "crypto/sha512" + "fmt" + + "github.com/tendermint/ed25519" + "github.com/tendermint/ed25519/extra25519" + "golang.org/x/crypto/ripemd160" +) + +var _ PubKey = PubKeyLedgerEd25519{} + +// Implements PubKeyInner +type PubKeyLedgerEd25519 [32]byte + +func (pubKey PubKeyLedgerEd25519) Address() Address { + // append type byte + hasher := ripemd160.New() + hasher.Write(pubKey.Bytes()) // does not error + return Address(hasher.Sum(nil)) +} + +func (pubKey PubKeyLedgerEd25519) Bytes() []byte { + bz, err := cdc.MarshalBinaryBare(pubKey) + if err != nil { + panic(err) + } + return bz +} + +func (pubKey PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig_ Signature) bool { + // must verify sha512 hash of msg, no padding, for Ledger compatibility + sig, ok := sig_.(SignatureEd25519) + if !ok { + return false + } + pubKeyBytes := [32]byte(pubKey) + sigBytes := [64]byte(sig) + h := sha512.New() + h.Write(msg) + digest := h.Sum(nil) + return ed25519.Verify(&pubKeyBytes, digest, &sigBytes) +} + +// For use with golang/crypto/nacl/box +// If error, returns nil. +func (pubKey PubKeyLedgerEd25519) ToCurve25519() *[32]byte { + keyCurve25519, pubKeyBytes := new([32]byte), [32]byte(pubKey) + ok := extra25519.PublicKeyToCurve25519(keyCurve25519, &pubKeyBytes) + if !ok { + return nil + } + return keyCurve25519 +} + +func (pubKey PubKeyLedgerEd25519) String() string { + return fmt.Sprintf("PubKeyLedgerEd25519{%X}", pubKey[:]) +} + +func (pubKey PubKeyLedgerEd25519) Equals(other PubKey) bool { + if otherEd, ok := other.(PubKeyLedgerEd25519); ok { + return bytes.Equal(pubKey[:], otherEd[:]) + } else { + return false + } +} From bb81e4aa5f88218d5b2c478e133909908a5619b4 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 30 May 2018 22:16:30 +0200 Subject: [PATCH 14/22] Pin to an upstream revision --- Gopkg.lock | 3 +-- Gopkg.toml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 332714ee4..2090ac2e3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -154,7 +154,6 @@ revision = "8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc" [[projects]] - branch = "master" name = "github.com/zondax/ledger-goclient" packages = ["."] revision = "3e2146609cdb97894c064d59e9d00accd8c2b1dd" @@ -178,6 +177,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "f3cfb54414cb9d59bab79226c7778673e7ac5b7a464baf9b2ea76c1f2563631e" + inputs-digest = "365c3bca75ced49eb0ebcdc5c98fd47b534850684fcc94c16d1bc6a671116395" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 3737ec5f9..10931a1a6 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -59,7 +59,7 @@ [[constraint]] name = "github.com/zondax/ledger-goclient" - branch = "master" + revision = "3e2146609cdb97894c064d59e9d00accd8c2b1dd" [prune] go-tests = true From 970693523338af9e9cd6e344c4c9500ef517c159 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 30 May 2018 18:57:53 -0400 Subject: [PATCH 15/22] fix link in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a524c014..959290573 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ go-crypto is the cryptographic package adapted for Tendermint's uses ## Binary encoding -For Binary encoding, please refer to the [Tendermint encoding spec](https://github.com/tendermint/tendermint/blob/develop/docs/specification/new-spec/encoding.md). +For Binary encoding, please refer to the [Tendermint encoding spec](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/encoding.md). ## JSON Encoding From e1ce3ffe0fd100816e597d842ee6e476dd320b9c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 30 May 2018 20:48:52 -0400 Subject: [PATCH 16/22] changelog and version --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ version.go | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91dcfab52..5db3a6109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 0.7.0 + +**May 30th, 2018** + +BREAKING CHANGES + +No breaking changes compared to 0.6.2, but making up for the version bump that +should have happened in 0.6.1. + +We also bring in the `tmlibs/merkle` package with breaking changes: + +- change the hash function from RIPEMD160 to tmhash (first 20-bytes of SHA256) +- remove unused funcs and unexport SimpleMap + +FEATURES + +- [xchacha20poly1305] New authenticated encryption module +- [merkle] Moved in from tmlibs +- [merkle/tmhash] New hash function: the first 20-bytes of SHA256 + +IMPROVEMENTS + +- Remove some dead code +- Use constant-time compare for signatures + +BUG FIXES + +- Fix MixEntropy weakness +- Fix PrivKeyEd25519.Generate() + ## 0.6.2 (April 9, 2018) IMPROVEMENTS diff --git a/version.go b/version.go index aac87c4f3..4cf5685a5 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package crypto -const Version = "0.6.2" +const Version = "0.7.0" From 63aac6559032f9fbc1a6a94b2a20ae9b30de0f65 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 21:21:50 +0200 Subject: [PATCH 17/22] Remove Ledger ed25519 support, for now --- amino.go | 4 -- ledger_ed25519.go | 156 ---------------------------------------------- ledger_pub_key.go | 68 -------------------- ledger_test.go | 38 ----------- 4 files changed, 266 deletions(-) delete mode 100644 ledger_ed25519.go delete mode 100644 ledger_pub_key.go diff --git a/amino.go b/amino.go index b78a15ed9..2af765434 100644 --- a/amino.go +++ b/amino.go @@ -19,8 +19,6 @@ func RegisterAmino(cdc *amino.Codec) { cdc.RegisterInterface((*PubKey)(nil), nil) cdc.RegisterConcrete(PubKeyEd25519{}, "tendermint/PubKeyEd25519", nil) - cdc.RegisterConcrete(PubKeyLedgerEd25519{}, - "tendermint/PubKeyLedgerEd25519", nil) cdc.RegisterConcrete(PubKeySecp256k1{}, "tendermint/PubKeySecp256k1", nil) @@ -31,8 +29,6 @@ func RegisterAmino(cdc *amino.Codec) { "tendermint/PrivKeySecp256k1", nil) cdc.RegisterConcrete(PrivKeyLedgerSecp256k1{}, "tendermint/PrivKeyLedgerSecp256k1", nil) - cdc.RegisterConcrete(PrivKeyLedgerEd25519{}, - "tendermint/PrivKeyLedgerEd25519", nil) cdc.RegisterInterface((*Signature)(nil), nil) cdc.RegisterConcrete(SignatureEd25519{}, diff --git a/ledger_ed25519.go b/ledger_ed25519.go deleted file mode 100644 index 2110b8871..000000000 --- a/ledger_ed25519.go +++ /dev/null @@ -1,156 +0,0 @@ -package crypto - -import ( - "fmt" - "github.com/pkg/errors" - - // "github.com/tendermint/ed25519" - ledger "github.com/zondax/ledger-goclient" -) - -func pubkeyLedgerEd25519(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { - key, err := device.GetPublicKeyED25519(path) - if err != nil { - return pub, fmt.Errorf("Error fetching public key: %v", err) - } - var p PubKeyLedgerEd25519 - copy(p[:], key[0:32]) - return p, err -} - -func signLedgerEd25519(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { - bsig, err := device.SignED25519(path, msg) - if err != nil { - return sig, err - } - sig = SignatureEd25519FromBytes(bsig) - return sig, nil -} - -// PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano -// we cache the PubKey from the first call to use it later -type PrivKeyLedgerEd25519 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 -} - -// NewPrivKeyLedgerEd25519 will generate a new key and store the -// public key for later use. -func NewPrivKeyLedgerEd25519(path DerivationPath) (PrivKey, error) { - var pk PrivKeyLedgerEd25519 - 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 PrivKeyLedgerEd25519) 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 *PrivKeyLedgerEd25519) 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 PrivKeyLedgerEd25519) 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 PrivKeyLedgerEd25519) Sign(msg []byte) Signature { - // oh, I wish there was better error handling - dev, err := getLedger() - if err != nil { - panic(err) - } - - sig, err := signLedgerEd25519(dev, pk.Path, msg) - if err != nil { - panic(err) - } - - pub, err := pubkeyLedgerEd25519(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 PrivKeyLedgerEd25519) 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 PrivKeyLedgerEd25519) 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 PrivKeyLedgerEd25519) 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 = pubkeyLedgerEd25519(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 PrivKeyLedgerEd25519) Equals(other PrivKey) bool { - if ledger, ok := other.(*PrivKeyLedgerEd25519); ok { - return pk.CachedPubKey.Equals(ledger.CachedPubKey) - } - return false -} diff --git a/ledger_pub_key.go b/ledger_pub_key.go deleted file mode 100644 index 0a5222577..000000000 --- a/ledger_pub_key.go +++ /dev/null @@ -1,68 +0,0 @@ -package crypto - -import ( - "bytes" - "crypto/sha512" - "fmt" - - "github.com/tendermint/ed25519" - "github.com/tendermint/ed25519/extra25519" - "golang.org/x/crypto/ripemd160" -) - -var _ PubKey = PubKeyLedgerEd25519{} - -// Implements PubKeyInner -type PubKeyLedgerEd25519 [32]byte - -func (pubKey PubKeyLedgerEd25519) Address() Address { - // append type byte - hasher := ripemd160.New() - hasher.Write(pubKey.Bytes()) // does not error - return Address(hasher.Sum(nil)) -} - -func (pubKey PubKeyLedgerEd25519) Bytes() []byte { - bz, err := cdc.MarshalBinaryBare(pubKey) - if err != nil { - panic(err) - } - return bz -} - -func (pubKey PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig_ Signature) bool { - // must verify sha512 hash of msg, no padding, for Ledger compatibility - sig, ok := sig_.(SignatureEd25519) - if !ok { - return false - } - pubKeyBytes := [32]byte(pubKey) - sigBytes := [64]byte(sig) - h := sha512.New() - h.Write(msg) - digest := h.Sum(nil) - return ed25519.Verify(&pubKeyBytes, digest, &sigBytes) -} - -// For use with golang/crypto/nacl/box -// If error, returns nil. -func (pubKey PubKeyLedgerEd25519) ToCurve25519() *[32]byte { - keyCurve25519, pubKeyBytes := new([32]byte), [32]byte(pubKey) - ok := extra25519.PublicKeyToCurve25519(keyCurve25519, &pubKeyBytes) - if !ok { - return nil - } - return keyCurve25519 -} - -func (pubKey PubKeyLedgerEd25519) String() string { - return fmt.Sprintf("PubKeyLedgerEd25519{%X}", pubKey[:]) -} - -func (pubKey PubKeyLedgerEd25519) Equals(other PubKey) bool { - if otherEd, ok := other.(PubKeyLedgerEd25519); ok { - return bytes.Equal(pubKey[:], otherEd[:]) - } else { - return false - } -} diff --git a/ledger_test.go b/ledger_test.go index a0ea35837..dda3d3d3d 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -46,44 +46,6 @@ func TestRealLedgerSecp256k1(t *testing.T) { assert.Equal(t, pub, bpub) } -func TestRealLedgerEd25519(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 := NewPrivKeyLedgerEd25519(path) - require.Nil(t, err, "%+v", err) - pub := priv.PubKey() - sig := priv.Sign(msg) - - 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 := priv2.PubKey() - require.Equal(t, pub, pub2) - - // signing with the loaded key should match the original pubkey - sig = priv2.Sign(msg) - 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) { From 80e97522504e79dca2f0e2c35168e6f47dc307ce Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 21:30:20 +0200 Subject: [PATCH 18/22] Move TODOs to #114 --- Gopkg.lock | 4 ++-- ledger_secp256k1.go | 19 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 2090ac2e3..f51e2b229 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,7 +11,7 @@ branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "bc0944904505aab55e089371a892be2f87883161" + revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" [[projects]] branch = "master" @@ -172,7 +172,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "ab813273cd59e1333f7ae7bff5d027d4aadf528c" + revision = "5ba7f63082460102a45837dbd1827e10f9479ac0" [solve-meta] analyzer-name = "dep" diff --git a/ledger_secp256k1.go b/ledger_secp256k1.go index b574035d4..f485bb415 100644 --- a/ledger_secp256k1.go +++ b/ledger_secp256k1.go @@ -2,7 +2,6 @@ package crypto import ( "fmt" - "github.com/pkg/errors" secp256k1 "github.com/btcsuite/btcd/btcec" ledger "github.com/zondax/ledger-goclient" @@ -11,13 +10,14 @@ import ( 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) + 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()) - return p, err + pub = p + return } func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { @@ -26,7 +26,7 @@ func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) return sig, err } sig = SignatureSecp256k1FromBytes(bsig) - return sig, nil + return } // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano @@ -62,7 +62,7 @@ func (pk PrivKeyLedgerSecp256k1) ValidateKey() error { } // verify this matches cached address if !pub.Equals(pk.CachedPubKey) { - return errors.New("Cached key does not match retrieved key") + return fmt.Errorf("cached key does not match retrieved key") } return nil } @@ -82,8 +82,6 @@ func (pk PrivKeyLedgerSecp256k1) Bytes() []byte { // 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. @@ -108,13 +106,12 @@ func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature { if pk.CachedPubKey == nil { pk.CachedPubKey = pub } else if !pk.CachedPubKey.Equals(pub) { - panic("Stored key does not match signing key") + 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 { @@ -139,11 +136,11 @@ func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) { 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)) + return key, fmt.Errorf("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, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err) } return key, err } From 854eb323ddb27d10eddc7dfb36e91e98165dd6c2 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 8 Jun 2018 17:28:50 -0700 Subject: [PATCH 19/22] dev version bump --- CHANGELOG.md | 4 ++++ version.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db3a6109..4c31551b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.8.0 + +**TBD** + ## 0.7.0 **May 30th, 2018** diff --git a/version.go b/version.go index 4cf5685a5..57806e26c 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package crypto -const Version = "0.7.0" +const Version = "0.8.0-dev" From c21f67c5af7fef3ca064fa578084a73ac9fdfb81 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sun, 10 Jun 2018 10:01:41 +0200 Subject: [PATCH 20/22] 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) From 66794a174ac0f0a03235ba1348321e247f391822 Mon Sep 17 00:00:00 2001 From: Joon Date: Sun, 10 Jun 2018 20:43:40 -0700 Subject: [PATCH 21/22] Move from tmlibs #213 (#115) * move from tmlibs 213 * expose KVPair, simpleproofsfrommap returns keys --- merkle/simple_map.go | 13 +++++-------- merkle/simple_map_test.go | 12 ++++++------ merkle/simple_proof.go | 14 +++++++++++--- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/merkle/simple_map.go b/merkle/simple_map.go index cde5924f4..24863267b 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 a89289a8f..d9d635115 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 f52d1ad9f..2541b6d38 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 From e694c309ba2ad13dfac65c3f0fdd1efb535b27c5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 11 Jun 2018 15:43:32 -0700 Subject: [PATCH 22/22] 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 --- pub_key.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pub_key.go b/pub_key.go index 9be64acdf..c509ff8de 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 {