tendermint/ledger_secp256k1.go

156 lines
4.4 KiB
Go
Raw Normal View History

2018-04-30 16:42:11 +02:00
package crypto
import (
"fmt"
2018-04-30 16:42:11 +02:00
2018-04-30 19:34:19 +02:00
secp256k1 "github.com/btcsuite/btcd/btcec"
2018-04-30 16:42:11 +02:00
ledger "github.com/zondax/ledger-goclient"
)
2018-05-30 03:29:42 +02:00
func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) {
2018-05-10 02:03:28 +02:00
key, err := device.GetPublicKeySECP256K1(path)
2018-04-30 16:42:11 +02:00
if err != nil {
2018-05-31 21:30:20 +02:00
return nil, fmt.Errorf("error fetching public key: %v", err)
2018-04-30 16:42:11 +02:00
}
var p PubKeySecp256k1
2018-04-30 19:34:19 +02:00
// Reserialize in the 33-byte compressed format
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256())
copy(p[:], cmp.SerializeCompressed())
2018-05-31 21:30:20 +02:00
pub = p
return
}
2018-05-30 03:29:42 +02:00
func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) {
2018-05-10 02:03:28 +02:00
bsig, err := device.SignSECP256K1(path, msg)
if err != nil {
return sig, err
}
sig = SignatureSecp256k1FromBytes(bsig)
2018-05-31 21:30:20 +02:00
return
2018-04-30 16:42:11 +02:00
}
// 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
2018-05-10 02:03:28 +02:00
Path DerivationPath
2018-04-30 16:42:11 +02:00
}
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the
// public key for later use.
2018-05-10 02:03:28 +02:00
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (PrivKey, error) {
2018-04-30 16:42:11 +02:00
var pk PrivKeyLedgerSecp256k1
pk.Path = path
2018-04-30 16:42:11 +02:00
// 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) {
2018-05-31 21:30:20 +02:00
return fmt.Errorf("cached key does not match retrieved key")
2018-04-30 16:42:11 +02:00
}
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)
}
2018-05-30 03:29:42 +02:00
sig, err := signLedgerSecp256k1(dev, pk.Path, msg)
if err != nil {
panic(err)
}
2018-05-30 03:29:42 +02:00
pub, err := pubkeyLedgerSecp256k1(dev, pk.Path)
2018-04-30 16:42:11 +02:00
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) {
2018-05-31 21:30:20 +02:00
panic("stored key does not match signing key")
2018-04-30 16:42:11 +02:00
}
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 {
2018-05-31 21:30:20 +02:00
return key, fmt.Errorf("cannot connect to Ledger device - error: %v", err)
2018-04-30 16:42:11 +02:00
}
2018-05-30 03:29:42 +02:00
key, err = pubkeyLedgerSecp256k1(dev, pk.Path)
2018-04-30 16:42:11 +02:00
if err != nil {
2018-05-31 21:30:20 +02:00
return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
2018-04-30 16:42:11 +02:00
}
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
}