mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
Merge pull request #85 from tendermint/cwgoes/ledger-integration
Ledger integration
This commit is contained in:
commit
2bbad9d496
31
Gopkg.lock
generated
31
Gopkg.lock
generated
@ -1,17 +1,23 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# 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]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/btcsuite/btcd"
|
name = "github.com/btcsuite/btcd"
|
||||||
packages = ["btcec"]
|
packages = ["btcec"]
|
||||||
revision = "2be2f12b358dc57d70b8f501b00be450192efbc3"
|
revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/btcsuite/btcutil"
|
name = "github.com/btcsuite/btcutil"
|
||||||
packages = ["base58"]
|
packages = ["base58"]
|
||||||
revision = "501929d3d046174c3d39f0ea54ece471aa17238c"
|
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/davecgh/go-spew"
|
name = "github.com/davecgh/go-spew"
|
||||||
@ -55,7 +61,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/golang/snappy"
|
name = "github.com/golang/snappy"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "553a641470496b2327abcac10b36396bd98e45c9"
|
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -113,7 +119,7 @@
|
|||||||
"leveldb/table",
|
"leveldb/table",
|
||||||
"leveldb/util"
|
"leveldb/util"
|
||||||
]
|
]
|
||||||
revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad"
|
revision = "5d6fca44a948d2be89a9702de7717f0168403d3d"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -128,8 +134,8 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/go-amino"
|
name = "github.com/tendermint/go-amino"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "42246108ff925a457fb709475070a03dfd3e2b5c"
|
revision = "3c22a7a539411f89a96738fcfa14c1027e24e5ec"
|
||||||
version = "0.9.6"
|
version = "0.9.10"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/tmlibs"
|
name = "github.com/tendermint/tmlibs"
|
||||||
@ -138,8 +144,8 @@
|
|||||||
"db",
|
"db",
|
||||||
"log"
|
"log"
|
||||||
]
|
]
|
||||||
revision = "2e24b64fc121dcdf1cabceab8dc2f7257675483c"
|
revision = "d970af87248a4e162590300dbb74e102183a417d"
|
||||||
version = "v0.8.1"
|
version = "v0.8.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -147,6 +153,11 @@
|
|||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc"
|
revision = "8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/zondax/ledger-goclient"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "3e2146609cdb97894c064d59e9d00accd8c2b1dd"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
@ -161,11 +172,11 @@
|
|||||||
"ripemd160",
|
"ripemd160",
|
||||||
"salsa20/salsa"
|
"salsa20/salsa"
|
||||||
]
|
]
|
||||||
revision = "b2aa35443fbc700ab74c586ae79b81c171851023"
|
revision = "5ba7f63082460102a45837dbd1827e10f9479ac0"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "f9ccfa2cadfcbfb43bf729b871a0ad2f8d4f4acb118cd859e6faf9b24842b840"
|
inputs-digest = "365c3bca75ced49eb0ebcdc5c98fd47b534850684fcc94c16d1bc6a671116395"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -57,6 +57,10 @@
|
|||||||
name = "github.com/tyler-smith/go-bip39"
|
name = "github.com/tyler-smith/go-bip39"
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/zondax/ledger-goclient"
|
||||||
|
revision = "3e2146609cdb97894c064d59e9d00accd8c2b1dd"
|
||||||
|
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
unused-packages = true
|
unused-packages = true
|
||||||
|
2
amino.go
2
amino.go
@ -27,6 +27,8 @@ func RegisterAmino(cdc *amino.Codec) {
|
|||||||
"tendermint/PrivKeyEd25519", nil)
|
"tendermint/PrivKeyEd25519", nil)
|
||||||
cdc.RegisterConcrete(PrivKeySecp256k1{},
|
cdc.RegisterConcrete(PrivKeySecp256k1{},
|
||||||
"tendermint/PrivKeySecp256k1", nil)
|
"tendermint/PrivKeySecp256k1", nil)
|
||||||
|
cdc.RegisterConcrete(PrivKeyLedgerSecp256k1{},
|
||||||
|
"tendermint/PrivKeyLedgerSecp256k1", nil)
|
||||||
|
|
||||||
cdc.RegisterInterface((*Signature)(nil), nil)
|
cdc.RegisterInterface((*Signature)(nil), nil)
|
||||||
cdc.RegisterConcrete(SignatureEd25519{},
|
cdc.RegisterConcrete(SignatureEd25519{},
|
||||||
|
19
ledger_common.go
Normal file
19
ledger_common.go
Normal file
@ -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
|
||||||
|
}
|
155
ledger_secp256k1.go
Normal file
155
ledger_secp256k1.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
secp256k1 "github.com/btcsuite/btcd/btcec"
|
||||||
|
ledger "github.com/zondax/ledger-goclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) {
|
||||||
|
key, err := device.GetPublicKeySECP256K1(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetching public key: %v", err)
|
||||||
|
}
|
||||||
|
var p PubKeySecp256k1
|
||||||
|
// Reserialize in the 33-byte compressed format
|
||||||
|
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256())
|
||||||
|
copy(p[:], cmp.SerializeCompressed())
|
||||||
|
pub = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) {
|
||||||
|
bsig, err := device.SignSECP256K1(path, msg)
|
||||||
|
if err != nil {
|
||||||
|
return sig, err
|
||||||
|
}
|
||||||
|
sig = SignatureSecp256k1FromBytes(bsig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano
|
||||||
|
// we cache the PubKey from the first call to use it later
|
||||||
|
type PrivKeyLedgerSecp256k1 struct {
|
||||||
|
// PubKey should be private, but we want to encode it via go-amino
|
||||||
|
// so we can view the address later, even without having the ledger
|
||||||
|
// attached
|
||||||
|
CachedPubKey PubKey
|
||||||
|
Path DerivationPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the
|
||||||
|
// public key for later use.
|
||||||
|
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (PrivKey, error) {
|
||||||
|
var pk PrivKeyLedgerSecp256k1
|
||||||
|
pk.Path = path
|
||||||
|
// getPubKey will cache the pubkey for later use,
|
||||||
|
// this allows us to return an error early if the ledger
|
||||||
|
// is not plugged in
|
||||||
|
_, err := pk.getPubKey()
|
||||||
|
return &pk, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateKey allows us to verify the sanity of a key
|
||||||
|
// after loading it from disk
|
||||||
|
func (pk PrivKeyLedgerSecp256k1) ValidateKey() error {
|
||||||
|
// getPubKey will return an error if the ledger is not
|
||||||
|
// properly set up...
|
||||||
|
pub, err := pk.forceGetPubKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// verify this matches cached address
|
||||||
|
if !pub.Equals(pk.CachedPubKey) {
|
||||||
|
return fmt.Errorf("cached key does not match retrieved key")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertIsPrivKeyInner fulfils PrivKey Interface
|
||||||
|
func (pk *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
|
||||||
|
|
||||||
|
// Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify
|
||||||
|
// the same key when we reconnect to a ledger
|
||||||
|
func (pk PrivKeyLedgerSecp256k1) Bytes() []byte {
|
||||||
|
bin, err := cdc.MarshalBinaryBare(pk)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign calls the ledger and stores the PubKey for future use
|
||||||
|
//
|
||||||
|
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes,
|
||||||
|
// returning an error, so this should only trigger if the privkey is held
|
||||||
|
// in memory for a while before use.
|
||||||
|
func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature {
|
||||||
|
// oh, I wish there was better error handling
|
||||||
|
dev, err := getLedger()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := signLedgerSecp256k1(dev, pk.Path, msg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := pubkeyLedgerSecp256k1(dev, pk.Path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have no pubkey yet, store it for future queries
|
||||||
|
if pk.CachedPubKey == nil {
|
||||||
|
pk.CachedPubKey = pub
|
||||||
|
} else if !pk.CachedPubKey.Equals(pub) {
|
||||||
|
panic("stored key does not match signing key")
|
||||||
|
}
|
||||||
|
return sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubKey returns the stored PubKey
|
||||||
|
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, fmt.Errorf("cannot connect to Ledger device - error: %v", err)
|
||||||
|
}
|
||||||
|
key, err = pubkeyLedgerSecp256k1(dev, pk.Path)
|
||||||
|
if err != nil {
|
||||||
|
return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
|
||||||
|
}
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals fulfils PrivKey Interface - makes sure both keys refer to the
|
||||||
|
// same
|
||||||
|
func (pk PrivKeyLedgerSecp256k1) Equals(other PrivKey) bool {
|
||||||
|
if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok {
|
||||||
|
return pk.CachedPubKey.Equals(ledger.CachedPubKey)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
61
ledger_test.go
Normal file
61
ledger_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRealLedgerSecp256k1(t *testing.T) {
|
||||||
|
|
||||||
|
if os.Getenv("WITH_LEDGER") == "" {
|
||||||
|
t.Skip("Set WITH_LEDGER to run code on real ledger")
|
||||||
|
}
|
||||||
|
msg := []byte("kuhehfeohg")
|
||||||
|
|
||||||
|
path := DerivationPath{44, 60, 0, 0, 0}
|
||||||
|
|
||||||
|
priv, err := NewPrivKeyLedgerSecp256k1(path)
|
||||||
|
require.Nil(t, err, "%+v", err)
|
||||||
|
pub := 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)
|
||||||
|
path := DerivationPath{44, 60, 0, 0, 0}
|
||||||
|
_, err := NewPrivKeyLedgerSecp256k1(path)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
@ -80,3 +80,9 @@ func (sig SignatureSecp256k1) Equals(other Signature) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SignatureSecp256k1FromBytes(data []byte) Signature {
|
||||||
|
sig := make(SignatureSecp256k1, len(data))
|
||||||
|
copy(sig[:], data)
|
||||||
|
return sig
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user