cosmos-sdk/crypto/ledger_secp256k1.go

161 lines
4.7 KiB
Go

package crypto
import (
"fmt"
"github.com/pkg/errors"
secp256k1 "github.com/btcsuite/btcd/btcec"
tmcrypto "github.com/tendermint/tendermint/crypto"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
)
var (
// discoverLedger defines a function to be invoked at runtime for discovering
// a connected Ledger device.
discoverLedger discoverLedgerFn
)
type (
// discoverLedgerFn defines a Ledger discovery function that returns a
// connected device or an error upon failure. Its allows a method to avoid CGO
// dependencies when Ledger support is potentially not enabled.
discoverLedgerFn func() (LedgerSECP256K1, error)
// DerivationPath represents a Ledger derivation path.
DerivationPath []uint32
// LedgerSECP256K1 reflects an interface a Ledger API must implement for
// the SECP256K1 scheme.
LedgerSECP256K1 interface {
GetPublicKeySECP256K1([]uint32) ([]byte, error)
SignSECP256K1([]uint32, []byte) ([]byte, error)
}
// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we
// cache the PubKey from the first call to use it later.
PrivKeyLedgerSecp256k1 struct {
// CachedPubKey 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 tmcrypto.PubKey
Path DerivationPath
ledger LedgerSECP256K1
}
)
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key
// for later use.
//
// CONTRACT: The ledger device, ledgerDevice, must be loaded and set prior to
// any creation of a PrivKeyLedgerSecp256k1.
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (tmcrypto.PrivKey, error) {
if discoverLedger == nil {
return nil, errors.New("no Ledger discovery function defined")
}
device, err := discoverLedger()
if err != nil {
return nil, errors.Wrap(err, "failed to create PrivKeyLedgerSecp256k1")
}
pkl := &PrivKeyLedgerSecp256k1{Path: path, ledger: device}
pubKey, err := pkl.getPubKey()
if err != nil {
return nil, err
}
pkl.CachedPubKey = pubKey
return pkl, err
}
// PubKey returns the cached public key.
func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey {
return pkl.CachedPubKey
}
// ValidateKey allows us to verify the sanity of a public key after loading it
// from disk.
func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
// getPubKey will return an error if the ledger is not
pub, err := pkl.getPubKey()
if err != nil {
return err
}
// verify this matches cached address
if !pub.Equals(pkl.CachedPubKey) {
return fmt.Errorf("cached key does not match retrieved key")
}
return nil
}
// AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op.
func (pkl *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
// Bytes implements the PrivKey interface. It stores the cached public key so
// we can verify the same key when we reconnect to a ledger.
func (pkl PrivKeyLedgerSecp256k1) Bytes() []byte {
return cdc.MustMarshalBinaryBare(pkl)
}
// Equals implements the PrivKey interface. It makes sure two private keys
// refer to the same public key.
func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok {
return pkl.CachedPubKey.Equals(ledger.CachedPubKey)
}
return false
}
// 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 private key is held in memory
// for a while before use.
func (pkl PrivKeyLedgerSecp256k1) Sign(msg []byte) ([]byte, error) {
sig, err := pkl.signLedgerSecp256k1(msg)
if err != nil {
return nil, err
}
return sig, nil
}
// getPubKey reads the pubkey the ledger itself
// since this involves IO, it may return an error, which is not exposed
// in the PubKey interface, so this function allows better error handling
func (pkl PrivKeyLedgerSecp256k1) getPubKey() (key tmcrypto.PubKey, err error) {
key, err = pkl.pubkeyLedgerSecp256k1()
if err != nil {
return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
}
return key, err
}
func (pkl PrivKeyLedgerSecp256k1) signLedgerSecp256k1(msg []byte) ([]byte, error) {
return pkl.ledger.SignSECP256K1(pkl.Path, msg)
}
func (pkl PrivKeyLedgerSecp256k1) pubkeyLedgerSecp256k1() (pub tmcrypto.PubKey, err error) {
key, err := pkl.ledger.GetPublicKeySECP256K1(pkl.Path)
if err != nil {
return nil, fmt.Errorf("error fetching public key: %v", err)
}
var pk tmsecp256k1.PubKeySecp256k1
// re-serialize in the 33-byte compressed format
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256())
if err != nil {
return nil, fmt.Errorf("error parsing public key: %v", err)
}
copy(pk[:], cmp.SerializeCompressed())
return pk, nil
}