github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/crypto/ledger_secp256k1.go (about)

     1  package crypto
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"github.com/btcsuite/btcd/btcec"
     8  	"github.com/pkg/errors"
     9  
    10  	tmcrypto "github.com/fibonacci-chain/fbc/libs/tendermint/crypto"
    11  	tmsecp256k1 "github.com/fibonacci-chain/fbc/libs/tendermint/crypto/secp256k1"
    12  	tmbtcec "github.com/tendermint/btcd/btcec"
    13  
    14  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/hd"
    15  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    16  )
    17  
    18  var (
    19  	// discoverLedger defines a function to be invoked at runtime for discovering
    20  	// a connected Ledger device.
    21  	discoverLedger discoverLedgerFn
    22  )
    23  
    24  type (
    25  	// discoverLedgerFn defines a Ledger discovery function that returns a
    26  	// connected device or an error upon failure. Its allows a method to avoid CGO
    27  	// dependencies when Ledger support is potentially not enabled.
    28  	discoverLedgerFn func() (LedgerSECP256K1, error)
    29  
    30  	// LedgerSECP256K1 reflects an interface a Ledger API must implement for SECP256K1
    31  	LedgerSECP256K1 interface {
    32  		Close() error
    33  		// Returns an uncompressed pubkey
    34  		GetPublicKeySECP256K1([]uint32) ([]byte, error)
    35  		// Returns a compressed pubkey and bech32 address (requires user confirmation)
    36  		GetAddressPubKeySECP256K1([]uint32, string) ([]byte, string, error)
    37  		// Signs a message (requires user confirmation)
    38  		SignSECP256K1([]uint32, []byte) ([]byte, error)
    39  	}
    40  
    41  	// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we
    42  	// cache the PubKey from the first call to use it later.
    43  	PrivKeyLedgerSecp256k1 struct {
    44  		// CachedPubKey should be private, but we want to encode it via
    45  		// go-amino so we can view the address later, even without having the
    46  		// ledger attached.
    47  		CachedPubKey tmcrypto.PubKey
    48  		Path         hd.BIP44Params
    49  	}
    50  )
    51  
    52  // NewPrivKeyLedgerSecp256k1Unsafe will generate a new key and store the public key for later use.
    53  //
    54  // This function is marked as unsafe as it will retrieve a pubkey without user verification.
    55  // It can only be used to verify a pubkey but never to create new accounts/keys. In that case,
    56  // please refer to NewPrivKeyLedgerSecp256k1
    57  func NewPrivKeyLedgerSecp256k1Unsafe(path hd.BIP44Params) (tmcrypto.PrivKey, error) {
    58  	device, err := getLedgerDevice()
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	defer warnIfErrors(device.Close)
    63  
    64  	pubKey, err := getPubKeyUnsafe(device, path)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	return PrivKeyLedgerSecp256k1{pubKey, path}, nil
    70  }
    71  
    72  // NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key for later use.
    73  // The request will require user confirmation and will show account and index in the device
    74  func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params, hrp string) (tmcrypto.PrivKey, string, error) {
    75  	device, err := getLedgerDevice()
    76  	if err != nil {
    77  		return nil, "", err
    78  	}
    79  	defer warnIfErrors(device.Close)
    80  
    81  	pubKey, addr, err := getPubKeyAddrSafe(device, path, hrp)
    82  	if err != nil {
    83  		return nil, "", err
    84  	}
    85  
    86  	return PrivKeyLedgerSecp256k1{pubKey, path}, addr, nil
    87  }
    88  
    89  // PubKey returns the cached public key.
    90  func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey {
    91  	return pkl.CachedPubKey
    92  }
    93  
    94  // Sign returns a secp256k1 signature for the corresponding message
    95  func (pkl PrivKeyLedgerSecp256k1) Sign(message []byte) ([]byte, error) {
    96  	device, err := getLedgerDevice()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	defer warnIfErrors(device.Close)
   101  
   102  	return sign(device, pkl, message)
   103  }
   104  
   105  // LedgerShowAddress triggers a ledger device to show the corresponding address.
   106  func LedgerShowAddress(path hd.BIP44Params, expectedPubKey tmcrypto.PubKey) error {
   107  	device, err := getLedgerDevice()
   108  	if err != nil {
   109  		return err
   110  	}
   111  	defer warnIfErrors(device.Close)
   112  
   113  	pubKey, err := getPubKeyUnsafe(device, path)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	if pubKey != expectedPubKey {
   119  		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")
   120  	}
   121  
   122  	config := sdk.GetConfig()
   123  	pubKey2, _, err := getPubKeyAddrSafe(device, path, config.GetBech32AccountAddrPrefix())
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	if pubKey2 != expectedPubKey {
   129  		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")
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // ValidateKey allows us to verify the sanity of a public key after loading it
   136  // from disk.
   137  func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
   138  	device, err := getLedgerDevice()
   139  	if err != nil {
   140  		return err
   141  	}
   142  	defer warnIfErrors(device.Close)
   143  
   144  	return validateKey(device, pkl)
   145  }
   146  
   147  // AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op.
   148  func (pkl *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
   149  
   150  // Bytes implements the PrivKey interface. It stores the cached public key so
   151  // we can verify the same key when we reconnect to a ledger.
   152  func (pkl PrivKeyLedgerSecp256k1) Bytes() []byte {
   153  	return cdc.MustMarshalBinaryBare(pkl)
   154  }
   155  
   156  // Equals implements the PrivKey interface. It makes sure two private keys
   157  // refer to the same public key.
   158  func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
   159  	if otherKey, ok := other.(PrivKeyLedgerSecp256k1); ok {
   160  		return pkl.CachedPubKey.Equals(otherKey.CachedPubKey)
   161  	}
   162  	return false
   163  }
   164  
   165  // warnIfErrors wraps a function and writes a warning to stderr. This is required
   166  // to avoid ignoring errors when defer is used. Using defer may result in linter warnings.
   167  func warnIfErrors(f func() error) {
   168  	if err := f(); err != nil {
   169  		_, _ = fmt.Fprint(os.Stderr, "received error when closing ledger connection", err)
   170  	}
   171  }
   172  
   173  func convertDERtoBER(signatureDER []byte) ([]byte, error) {
   174  	sigDER, err := btcec.ParseDERSignature(signatureDER, btcec.S256())
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	sigBER := tmbtcec.Signature{R: sigDER.R, S: sigDER.S}
   179  	return sigBER.Serialize(), nil
   180  }
   181  
   182  func getLedgerDevice() (LedgerSECP256K1, error) {
   183  	if discoverLedger == nil {
   184  		return nil, errors.New("no Ledger discovery function defined")
   185  	}
   186  
   187  	device, err := discoverLedger()
   188  	if err != nil {
   189  		return nil, errors.Wrap(err, "ledger nano S")
   190  	}
   191  
   192  	return device, nil
   193  }
   194  
   195  func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error {
   196  	pub, err := getPubKeyUnsafe(device, pkl.Path)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	// verify this matches cached address
   202  	if !pub.Equals(pkl.CachedPubKey) {
   203  		return fmt.Errorf("cached key does not match retrieved key")
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  // Sign calls the ledger and stores the PubKey for future use.
   210  //
   211  // Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning
   212  // an error, so this should only trigger if the private key is held in memory
   213  // for a while before use.
   214  func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) {
   215  	err := validateKey(device, pkl)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	sig, err := device.SignSECP256K1(pkl.Path.DerivationPath(), msg)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	return convertDERtoBER(sig)
   226  }
   227  
   228  // getPubKeyUnsafe reads the pubkey from a ledger device
   229  //
   230  // This function is marked as unsafe as it will retrieve a pubkey without user verification
   231  // It can only be used to verify a pubkey but never to create new accounts/keys. In that case,
   232  // please refer to getPubKeyAddrSafe
   233  //
   234  // since this involves IO, it may return an error, which is not exposed
   235  // in the PubKey interface, so this function allows better error handling
   236  func getPubKeyUnsafe(device LedgerSECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, error) {
   237  	publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath())
   238  	if err != nil {
   239  		return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
   240  	}
   241  
   242  	// re-serialize in the 33-byte compressed format
   243  	cmp, err := btcec.ParsePubKey(publicKey, btcec.S256())
   244  	if err != nil {
   245  		return nil, fmt.Errorf("error parsing public key: %v", err)
   246  	}
   247  
   248  	var compressedPublicKey tmsecp256k1.PubKeySecp256k1
   249  	copy(compressedPublicKey[:], cmp.SerializeCompressed())
   250  
   251  	return compressedPublicKey, nil
   252  }
   253  
   254  // getPubKeyAddr reads the pubkey and the address from a ledger device.
   255  // This function is marked as Safe as it will require user confirmation and
   256  // account and index will be shown in the device.
   257  //
   258  // Since this involves IO, it may return an error, which is not exposed
   259  // in the PubKey interface, so this function allows better error handling.
   260  func getPubKeyAddrSafe(device LedgerSECP256K1, path hd.BIP44Params, hrp string) (tmcrypto.PubKey, string, error) {
   261  	publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp)
   262  	if err != nil {
   263  		return nil, "", fmt.Errorf("address %s rejected", addr)
   264  	}
   265  
   266  	// re-serialize in the 33-byte compressed format
   267  	cmp, err := btcec.ParsePubKey(publicKey, btcec.S256())
   268  	if err != nil {
   269  		return nil, "", fmt.Errorf("error parsing public key: %v", err)
   270  	}
   271  
   272  	var compressedPublicKey tmsecp256k1.PubKeySecp256k1
   273  	copy(compressedPublicKey[:], cmp.SerializeCompressed())
   274  
   275  	return compressedPublicKey, addr, nil
   276  }