2020-07-22 09:51:12 -07:00
|
|
|
package ledger
|
2018-06-28 17:54:47 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2019-02-05 10:21:04 -08:00
|
|
|
"os"
|
2018-06-28 17:54:47 -07:00
|
|
|
|
2019-02-05 10:21:04 -08:00
|
|
|
"github.com/btcsuite/btcd/btcec"
|
2019-04-30 01:58:21 -07:00
|
|
|
"github.com/pkg/errors"
|
2019-04-04 07:36:39 -07:00
|
|
|
|
2019-02-05 10:21:04 -08:00
|
|
|
tmbtcec "github.com/tendermint/btcd/btcec"
|
2018-07-13 19:17:53 -07:00
|
|
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
2019-06-05 16:26:17 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
2020-08-28 09:02:38 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
2018-07-13 19:17:53 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-08-27 15:57:06 -07:00
|
|
|
// discoverLedger defines a function to be invoked at runtime for discovering
|
2018-08-27 15:42:03 -07:00
|
|
|
// a connected Ledger device.
|
2018-08-27 15:57:06 -07:00
|
|
|
discoverLedger discoverLedgerFn
|
2018-06-28 17:54:47 -07:00
|
|
|
)
|
|
|
|
|
2018-07-13 19:17:53 -07:00
|
|
|
type (
|
2018-08-27 15:57:06 -07:00
|
|
|
// discoverLedgerFn defines a Ledger discovery function that returns a
|
2018-08-28 05:24:19 -07:00
|
|
|
// connected device or an error upon failure. Its allows a method to avoid CGO
|
|
|
|
// dependencies when Ledger support is potentially not enabled.
|
2020-07-22 09:51:12 -07:00
|
|
|
discoverLedgerFn func() (SECP256K1, error)
|
2018-08-27 15:42:03 -07:00
|
|
|
|
2020-07-22 09:51:12 -07:00
|
|
|
// SECP256K1 reflects an interface a Ledger API must implement for SECP256K1
|
|
|
|
SECP256K1 interface {
|
2019-02-05 10:21:04 -08:00
|
|
|
Close() error
|
2019-04-30 01:58:21 -07:00
|
|
|
// Returns an uncompressed pubkey
|
2018-07-13 19:17:53 -07:00
|
|
|
GetPublicKeySECP256K1([]uint32) ([]byte, error)
|
2019-04-30 01:58:21 -07:00
|
|
|
// Returns a compressed pubkey and bech32 address (requires user confirmation)
|
|
|
|
GetAddressPubKeySECP256K1([]uint32, string) ([]byte, string, error)
|
|
|
|
// Signs a message (requires user confirmation)
|
2018-07-13 19:17:53 -07:00
|
|
|
SignSECP256K1([]uint32, []byte) ([]byte, error)
|
2018-06-28 17:54:47 -07:00
|
|
|
}
|
|
|
|
|
2018-07-13 19:17:53 -07:00
|
|
|
// 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
|
2019-02-05 08:22:56 -08:00
|
|
|
Path hd.BIP44Params
|
2018-06-28 17:54:47 -07:00
|
|
|
}
|
2018-07-13 19:17:53 -07:00
|
|
|
)
|
2018-06-28 17:54:47 -07:00
|
|
|
|
2020-07-22 09:51:12 -07:00
|
|
|
// NewPrivKeySecp256k1Unsafe will generate a new key and store the public key for later use.
|
2019-04-30 01:58:21 -07:00
|
|
|
//
|
|
|
|
// This function is marked as unsafe as it will retrieve a pubkey without user verification.
|
|
|
|
// It can only be used to verify a pubkey but never to create new accounts/keys. In that case,
|
2020-07-22 09:51:12 -07:00
|
|
|
// please refer to NewPrivKeySecp256k1
|
|
|
|
func NewPrivKeySecp256k1Unsafe(path hd.BIP44Params) (tmcrypto.PrivKey, error) {
|
|
|
|
device, err := getDevice()
|
2018-08-28 05:16:50 -07:00
|
|
|
if err != nil {
|
2019-02-05 10:21:04 -08:00
|
|
|
return nil, err
|
2018-08-28 05:16:50 -07:00
|
|
|
}
|
2019-02-05 10:21:04 -08:00
|
|
|
defer warnIfErrors(device.Close)
|
2018-08-28 05:16:50 -07:00
|
|
|
|
2019-04-30 01:58:21 -07:00
|
|
|
pubKey, err := getPubKeyUnsafe(device, path)
|
2018-06-28 17:54:47 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-13 19:17:53 -07:00
|
|
|
|
2019-02-05 10:21:04 -08:00
|
|
|
return PrivKeyLedgerSecp256k1{pubKey, path}, nil
|
2018-06-28 17:54:47 -07:00
|
|
|
}
|
|
|
|
|
2020-07-22 09:51:12 -07:00
|
|
|
// NewPrivKeySecp256k1 will generate a new key and store the public key for later use.
|
2019-04-30 01:58:21 -07:00
|
|
|
// The request will require user confirmation and will show account and index in the device
|
2020-07-22 09:51:12 -07:00
|
|
|
func NewPrivKeySecp256k1(path hd.BIP44Params, hrp string) (tmcrypto.PrivKey, string, error) {
|
|
|
|
device, err := getDevice()
|
2019-02-25 03:34:39 -08:00
|
|
|
if err != nil {
|
2019-04-30 01:58:21 -07:00
|
|
|
return nil, "", err
|
2019-02-25 03:34:39 -08:00
|
|
|
}
|
|
|
|
defer warnIfErrors(device.Close)
|
|
|
|
|
2019-04-30 01:58:21 -07:00
|
|
|
pubKey, addr, err := getPubKeyAddrSafe(device, path, hrp)
|
2019-02-25 03:34:39 -08:00
|
|
|
if err != nil {
|
2019-04-30 01:58:21 -07:00
|
|
|
return nil, "", err
|
2019-02-25 03:34:39 -08:00
|
|
|
}
|
|
|
|
|
2019-04-30 01:58:21 -07:00
|
|
|
return PrivKeyLedgerSecp256k1{pubKey, path}, addr, nil
|
2019-02-25 03:34:39 -08:00
|
|
|
}
|
|
|
|
|
2018-07-13 19:17:53 -07:00
|
|
|
// PubKey returns the cached public key.
|
|
|
|
func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey {
|
|
|
|
return pkl.CachedPubKey
|
|
|
|
}
|
|
|
|
|
2019-02-05 10:21:04 -08:00
|
|
|
// Sign returns a secp256k1 signature for the corresponding message
|
|
|
|
func (pkl PrivKeyLedgerSecp256k1) Sign(message []byte) ([]byte, error) {
|
2020-07-22 09:51:12 -07:00
|
|
|
device, err := getDevice()
|
2019-02-05 10:21:04 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer warnIfErrors(device.Close)
|
|
|
|
|
|
|
|
return sign(device, pkl, message)
|
|
|
|
}
|
|
|
|
|
2020-07-22 09:51:12 -07:00
|
|
|
// ShowAddress triggers a ledger device to show the corresponding address.
|
|
|
|
func ShowAddress(path hd.BIP44Params, expectedPubKey tmcrypto.PubKey,
|
2020-03-01 09:16:22 -08:00
|
|
|
accountAddressPrefix string) error {
|
2020-07-22 09:51:12 -07:00
|
|
|
device, err := getDevice()
|
2019-04-30 01:58:21 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer warnIfErrors(device.Close)
|
|
|
|
|
|
|
|
pubKey, err := getPubKeyUnsafe(device, path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-14 10:58:53 -07:00
|
|
|
if !pubKey.Equals(expectedPubKey) {
|
2019-04-30 01:58:21 -07:00
|
|
|
return fmt.Errorf("the key's pubkey does not match with the one retrieved from Ledger. Check that the HD path and device are the correct ones")
|
|
|
|
}
|
|
|
|
|
2020-03-01 09:16:22 -08:00
|
|
|
pubKey2, _, err := getPubKeyAddrSafe(device, path, accountAddressPrefix)
|
2019-04-30 01:58:21 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-14 10:58:53 -07:00
|
|
|
if !pubKey2.Equals(expectedPubKey) {
|
2019-04-30 01:58:21 -07:00
|
|
|
return fmt.Errorf("the key's pubkey does not match with the one retrieved from Ledger. Check that the HD path and device are the correct ones")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-13 19:17:53 -07:00
|
|
|
// ValidateKey allows us to verify the sanity of a public key after loading it
|
|
|
|
// from disk.
|
|
|
|
func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
|
2020-07-22 09:51:12 -07:00
|
|
|
device, err := getDevice()
|
2018-06-28 17:54:47 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-02-05 10:21:04 -08:00
|
|
|
defer warnIfErrors(device.Close)
|
2018-07-13 19:17:53 -07:00
|
|
|
|
2019-02-05 10:21:04 -08:00
|
|
|
return validateKey(device, pkl)
|
2018-06-28 17:54:47 -07:00
|
|
|
}
|
|
|
|
|
2018-07-13 19:17:53 -07:00
|
|
|
// AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op.
|
|
|
|
func (pkl *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
|
2018-06-28 17:54:47 -07:00
|
|
|
|
2018-07-13 19:17:53 -07:00
|
|
|
// 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)
|
2018-06-28 17:54:47 -07:00
|
|
|
}
|
|
|
|
|
2018-07-13 19:17:53 -07:00
|
|
|
// 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 {
|
2019-02-08 12:45:23 -08:00
|
|
|
if otherKey, ok := other.(PrivKeyLedgerSecp256k1); ok {
|
|
|
|
return pkl.CachedPubKey.Equals(otherKey.CachedPubKey)
|
2018-06-28 17:54:47 -07:00
|
|
|
}
|
2018-07-13 19:17:53 -07:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-14 10:58:53 -07:00
|
|
|
func (pkl PrivKeyLedgerSecp256k1) Type() string { return "PrivKeyLedgerSecp256k1" }
|
|
|
|
|
2019-02-05 10:21:04 -08:00
|
|
|
// warnIfErrors wraps a function and writes a warning to stderr. This is required
|
|
|
|
// to avoid ignoring errors when defer is used. Using defer may result in linter warnings.
|
|
|
|
func warnIfErrors(f func() error) {
|
|
|
|
if err := f(); err != nil {
|
|
|
|
_, _ = fmt.Fprint(os.Stderr, "received error when closing ledger connection", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertDERtoBER(signatureDER []byte) ([]byte, error) {
|
2019-08-19 09:06:27 -07:00
|
|
|
sigDER, err := btcec.ParseDERSignature(signatureDER, btcec.S256())
|
2019-02-05 10:21:04 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
sigBER := tmbtcec.Signature{R: sigDER.R, S: sigDER.S}
|
|
|
|
return sigBER.Serialize(), nil
|
|
|
|
}
|
|
|
|
|
2020-07-22 09:51:12 -07:00
|
|
|
func getDevice() (SECP256K1, error) {
|
2019-02-05 10:21:04 -08:00
|
|
|
if discoverLedger == nil {
|
|
|
|
return nil, errors.New("no Ledger discovery function defined")
|
|
|
|
}
|
|
|
|
|
|
|
|
device, err := discoverLedger()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "ledger nano S")
|
|
|
|
}
|
|
|
|
|
|
|
|
return device, nil
|
|
|
|
}
|
|
|
|
|
2020-07-22 09:51:12 -07:00
|
|
|
func validateKey(device SECP256K1, pkl PrivKeyLedgerSecp256k1) error {
|
2019-04-30 01:58:21 -07:00
|
|
|
pub, err := getPubKeyUnsafe(device, pkl.Path)
|
2019-02-05 10:21:04 -08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-07-13 19:17:53 -07:00
|
|
|
// 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.
|
2020-07-22 09:51:12 -07:00
|
|
|
func sign(device SECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) {
|
2019-02-05 10:21:04 -08:00
|
|
|
err := validateKey(device, pkl)
|
2018-06-28 17:54:47 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-02-05 10:21:04 -08:00
|
|
|
sig, err := device.SignSECP256K1(pkl.Path.DerivationPath(), msg)
|
2018-06-28 17:54:47 -07:00
|
|
|
if err != nil {
|
2019-02-05 10:21:04 -08:00
|
|
|
return nil, err
|
2018-06-28 17:54:47 -07:00
|
|
|
}
|
2018-07-13 19:17:53 -07:00
|
|
|
|
2019-02-05 10:21:04 -08:00
|
|
|
return convertDERtoBER(sig)
|
2018-07-13 19:17:53 -07:00
|
|
|
}
|
|
|
|
|
2019-04-30 01:58:21 -07:00
|
|
|
// getPubKeyUnsafe reads the pubkey from a ledger device
|
|
|
|
//
|
|
|
|
// This function is marked as unsafe as it will retrieve a pubkey without user verification
|
|
|
|
// It can only be used to verify a pubkey but never to create new accounts/keys. In that case,
|
|
|
|
// please refer to getPubKeyAddrSafe
|
|
|
|
//
|
2019-02-05 10:21:04 -08:00
|
|
|
// since this involves IO, it may return an error, which is not exposed
|
|
|
|
// in the PubKey interface, so this function allows better error handling
|
2020-07-22 09:51:12 -07:00
|
|
|
func getPubKeyUnsafe(device SECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, error) {
|
2019-02-05 10:21:04 -08:00
|
|
|
publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath())
|
2018-07-13 19:17:53 -07:00
|
|
|
if err != nil {
|
2019-02-05 10:21:04 -08:00
|
|
|
return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
|
2018-07-13 19:17:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// re-serialize in the 33-byte compressed format
|
2019-08-19 09:06:27 -07:00
|
|
|
cmp, err := btcec.ParsePubKey(publicKey, btcec.S256())
|
2018-07-14 14:48:41 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error parsing public key: %v", err)
|
|
|
|
}
|
2018-07-13 19:17:53 -07:00
|
|
|
|
2020-08-28 09:02:38 -07:00
|
|
|
compressedPublicKey := make(secp256k1.PubKey, secp256k1.PubKeySize)
|
2020-08-14 10:58:53 -07:00
|
|
|
copy(compressedPublicKey, cmp.SerializeCompressed())
|
2019-02-05 10:21:04 -08:00
|
|
|
|
|
|
|
return compressedPublicKey, nil
|
2018-06-28 17:54:47 -07:00
|
|
|
}
|
2019-04-30 01:58:21 -07:00
|
|
|
|
|
|
|
// getPubKeyAddr reads the pubkey and the address from a ledger device.
|
|
|
|
// This function is marked as Safe as it will require user confirmation and
|
|
|
|
// account and index will be shown in the device.
|
|
|
|
//
|
|
|
|
// Since this involves IO, it may return an error, which is not exposed
|
|
|
|
// in the PubKey interface, so this function allows better error handling.
|
2020-07-22 09:51:12 -07:00
|
|
|
func getPubKeyAddrSafe(device SECP256K1, path hd.BIP44Params, hrp string) (tmcrypto.PubKey, string, error) {
|
2019-04-30 01:58:21 -07:00
|
|
|
publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", fmt.Errorf("address %s rejected", addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// re-serialize in the 33-byte compressed format
|
2019-08-19 09:06:27 -07:00
|
|
|
cmp, err := btcec.ParsePubKey(publicKey, btcec.S256())
|
2019-04-30 01:58:21 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, "", fmt.Errorf("error parsing public key: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-08-28 09:02:38 -07:00
|
|
|
compressedPublicKey := make(secp256k1.PubKey, secp256k1.PubKeySize)
|
2020-08-14 10:58:53 -07:00
|
|
|
copy(compressedPublicKey, cmp.SerializeCompressed())
|
2019-04-30 01:58:21 -07:00
|
|
|
|
|
|
|
return compressedPublicKey, addr, nil
|
|
|
|
}
|