161 lines
4.7 KiB
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
|
|
}
|