mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
Ledger integration, WIP
This commit is contained in:
parent
35cf21c6eb
commit
1c9ff46e98
14
Gopkg.lock
generated
14
Gopkg.lock
generated
@ -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
|
||||
|
@ -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
|
||||
|
294
_nano/keys.go
294
_nano/keys.go
@ -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}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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[:]
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
2
amino.go
2
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{},
|
||||
|
154
ledger.go
Normal file
154
ledger.go
Normal file
@ -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
|
||||
}
|
72
ledger_test.go
Normal file
72
ledger_test.go
Normal file
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user