github.com/Finschia/finschia-sdk@v0.48.1/crypto/ledger/ledger_secp256k1.go (about)

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