github.com/cosmos/cosmos-sdk@v0.50.1/crypto/ledger/ledger_secp256k1.go (about)

     1  package ledger
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  	"os"
     8  
     9  	secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
    10  	"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
    11  
    12  	"github.com/cosmos/cosmos-sdk/crypto/hd"
    13  	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
    14  	"github.com/cosmos/cosmos-sdk/crypto/types"
    15  )
    16  
    17  // options stores the Ledger Options that can be used to customize Ledger usage
    18  var options Options
    19  
    20  type (
    21  	// discoverLedgerFn defines a Ledger discovery function that returns a
    22  	// connected device or an error upon failure. Its allows a method to avoid CGO
    23  	// dependencies when Ledger support is potentially not enabled.
    24  	discoverLedgerFn func() (SECP256K1, error)
    25  
    26  	// createPubkeyFn supports returning different public key types that implement
    27  	// types.PubKey
    28  	createPubkeyFn func([]byte) types.PubKey
    29  
    30  	// SECP256K1 reflects an interface a Ledger API must implement for SECP256K1
    31  	SECP256K1 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  		// The last byte denotes the SIGN_MODE to be used by Ledger: 0 for
    39  		// LEGACY_AMINO_JSON, 1 for TEXTUAL. It corresponds to the P2 value
    40  		// in https://github.com/cosmos/ledger-cosmos/blob/main/docs/APDUSPEC.md
    41  		SignSECP256K1([]uint32, []byte, byte) ([]byte, error)
    42  	}
    43  
    44  	// Options hosts customization options to account for differences in Ledger
    45  	// signing and usage across chains.
    46  	Options struct {
    47  		discoverLedger    discoverLedgerFn
    48  		createPubkey      createPubkeyFn
    49  		appName           string
    50  		skipDERConversion bool
    51  	}
    52  
    53  	// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we
    54  	// cache the PubKey from the first call to use it later.
    55  	PrivKeyLedgerSecp256k1 struct {
    56  		// CachedPubKey should be private, but we want to encode it via
    57  		// go-amino so we can view the address later, even without having the
    58  		// ledger attached.
    59  		CachedPubKey types.PubKey
    60  		Path         hd.BIP44Params
    61  	}
    62  )
    63  
    64  // Initialize the default options values for the Cosmos Ledger
    65  func initOptionsDefault() {
    66  	options.createPubkey = func(key []byte) types.PubKey {
    67  		return &secp256k1.PubKey{Key: key}
    68  	}
    69  	options.appName = "Cosmos"
    70  	options.skipDERConversion = false
    71  }
    72  
    73  // Set the discoverLedger function to use a different Ledger derivation
    74  func SetDiscoverLedger(fn discoverLedgerFn) {
    75  	options.discoverLedger = fn
    76  }
    77  
    78  // Set the createPubkey function to use a different public key
    79  func SetCreatePubkey(fn createPubkeyFn) {
    80  	options.createPubkey = fn
    81  }
    82  
    83  // Set the Ledger app name to use a different app name
    84  func SetAppName(appName string) {
    85  	options.appName = appName
    86  }
    87  
    88  // Set the DER Conversion requirement to true (false by default)
    89  func SetSkipDERConversion() {
    90  	options.skipDERConversion = true
    91  }
    92  
    93  // NewPrivKeySecp256k1Unsafe will generate a new key and store the public key for later use.
    94  //
    95  // This function is marked as unsafe as it will retrieve a pubkey without user verification.
    96  // It can only be used to verify a pubkey but never to create new accounts/keys. In that case,
    97  // please refer to NewPrivKeySecp256k1
    98  func NewPrivKeySecp256k1Unsafe(path hd.BIP44Params) (types.LedgerPrivKeyAminoJSON, error) {
    99  	device, err := getDevice()
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	defer warnIfErrors(device.Close)
   104  
   105  	pubKey, err := getPubKeyUnsafe(device, path)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	return PrivKeyLedgerSecp256k1{pubKey, path}, nil
   111  }
   112  
   113  // NewPrivKeySecp256k1 will generate a new key and store the public key for later use.
   114  // The request will require user confirmation and will show account and index in the device
   115  func NewPrivKeySecp256k1(path hd.BIP44Params, hrp string) (types.LedgerPrivKey, string, error) {
   116  	device, err := getDevice()
   117  	if err != nil {
   118  		return nil, "", fmt.Errorf("failed to retrieve device: %w", err)
   119  	}
   120  	defer warnIfErrors(device.Close)
   121  
   122  	pubKey, addr, err := getPubKeyAddrSafe(device, path, hrp)
   123  	if err != nil {
   124  		return nil, "", fmt.Errorf("failed to recover pubkey: %w", err)
   125  	}
   126  
   127  	return PrivKeyLedgerSecp256k1{pubKey, path}, addr, nil
   128  }
   129  
   130  // PubKey returns the cached public key.
   131  func (pkl PrivKeyLedgerSecp256k1) PubKey() types.PubKey {
   132  	return pkl.CachedPubKey
   133  }
   134  
   135  // Sign returns a secp256k1 signature for the corresponding message using
   136  // SIGN_MODE_TEXTUAL.
   137  func (pkl PrivKeyLedgerSecp256k1) Sign(message []byte) ([]byte, error) {
   138  	device, err := getDevice()
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	defer warnIfErrors(device.Close)
   143  
   144  	return sign(device, pkl, message, 1)
   145  }
   146  
   147  // SignLedgerAminoJSON returns a secp256k1 signature for the corresponding message using
   148  // SIGN_MODE_LEGACY_AMINO_JSON.
   149  func (pkl PrivKeyLedgerSecp256k1) SignLedgerAminoJSON(message []byte) ([]byte, error) {
   150  	device, err := getDevice()
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	defer warnIfErrors(device.Close)
   155  
   156  	return sign(device, pkl, message, 0)
   157  }
   158  
   159  // ShowAddress triggers a ledger device to show the corresponding address.
   160  func ShowAddress(path hd.BIP44Params, expectedPubKey types.PubKey, accountAddressPrefix string) error {
   161  	device, err := getDevice()
   162  	if err != nil {
   163  		return err
   164  	}
   165  	defer warnIfErrors(device.Close)
   166  
   167  	pubKey, err := getPubKeyUnsafe(device, path)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	if !pubKey.Equals(expectedPubKey) {
   173  		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")
   174  	}
   175  
   176  	pubKey2, _, err := getPubKeyAddrSafe(device, path, accountAddressPrefix)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	if !pubKey2.Equals(expectedPubKey) {
   182  		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")
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  // ValidateKey allows us to verify the sanity of a public key after loading it
   189  // from disk.
   190  func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
   191  	device, err := getDevice()
   192  	if err != nil {
   193  		return err
   194  	}
   195  	defer warnIfErrors(device.Close)
   196  
   197  	return validateKey(device, pkl)
   198  }
   199  
   200  // AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op.
   201  func (pkl *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
   202  
   203  // Bytes implements the PrivKey interface. It stores the cached public key so
   204  // we can verify the same key when we reconnect to a ledger.
   205  func (pkl PrivKeyLedgerSecp256k1) Bytes() []byte {
   206  	return cdc.MustMarshal(pkl)
   207  }
   208  
   209  // Equals implements the PrivKey interface. It makes sure two private keys
   210  // refer to the same public key.
   211  func (pkl PrivKeyLedgerSecp256k1) Equals(other types.LedgerPrivKey) bool {
   212  	if otherKey, ok := other.(PrivKeyLedgerSecp256k1); ok {
   213  		return pkl.CachedPubKey.Equals(otherKey.CachedPubKey)
   214  	}
   215  	return false
   216  }
   217  
   218  func (pkl PrivKeyLedgerSecp256k1) Type() string { return "PrivKeyLedgerSecp256k1" }
   219  
   220  // warnIfErrors wraps a function and writes a warning to stderr. This is required
   221  // to avoid ignoring errors when defer is used. Using defer may result in linter warnings.
   222  func warnIfErrors(f func() error) {
   223  	if err := f(); err != nil {
   224  		_, _ = fmt.Fprint(os.Stderr, "received error when closing ledger connection", err)
   225  	}
   226  }
   227  
   228  func convertDERtoBER(signatureDER []byte) ([]byte, error) {
   229  	sigDER, err := ecdsa.ParseDERSignature(signatureDER)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	sigStr := sigDER.Serialize()
   235  	// The format of a DER encoded signature is as follows:
   236  	// 0x30 <total length> 0x02 <length of R> <R> 0x02 <length of S> <S>
   237  	r, s := new(big.Int), new(big.Int)
   238  	r.SetBytes(sigStr[4 : 4+sigStr[3]])
   239  	s.SetBytes(sigStr[4+sigStr[3]+2:])
   240  
   241  	sModNScalar := new(secp.ModNScalar)
   242  	sModNScalar.SetByteSlice(s.Bytes())
   243  	// based on https://github.com/tendermint/btcd/blob/ec996c5/btcec/signature.go#L33-L50
   244  	if sModNScalar.IsOverHalfOrder() {
   245  		s = new(big.Int).Sub(secp.S256().N, s)
   246  	}
   247  
   248  	sigBytes := make([]byte, 64)
   249  	// 0 pad the byte arrays from the left if they aren't big enough.
   250  	copy(sigBytes[32-len(r.Bytes()):32], r.Bytes())
   251  	copy(sigBytes[64-len(s.Bytes()):64], s.Bytes())
   252  
   253  	return sigBytes, nil
   254  }
   255  
   256  func getDevice() (SECP256K1, error) {
   257  	if options.discoverLedger == nil {
   258  		return nil, errors.New("no Ledger discovery function defined")
   259  	}
   260  
   261  	device, err := options.discoverLedger()
   262  	if err != nil {
   263  		return nil, fmt.Errorf("ledger nano S: %w", err)
   264  	}
   265  
   266  	return device, nil
   267  }
   268  
   269  func validateKey(device SECP256K1, pkl PrivKeyLedgerSecp256k1) error {
   270  	pub, err := getPubKeyUnsafe(device, pkl.Path)
   271  	if err != nil {
   272  		return err
   273  	}
   274  
   275  	// verify this matches cached address
   276  	if !pub.Equals(pkl.CachedPubKey) {
   277  		return fmt.Errorf("cached key does not match retrieved key")
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  // Sign calls the ledger and stores the PubKey for future use.
   284  //
   285  // Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning
   286  // an error, so this should only trigger if the private key is held in memory
   287  // for a while before use.
   288  //
   289  // Last byte P2 is 0 for LEGACY_AMINO_JSON, and 1 for TEXTUAL.
   290  func sign(device SECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte, p2 byte) ([]byte, error) {
   291  	err := validateKey(device, pkl)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	sig, err := device.SignSECP256K1(pkl.Path.DerivationPath(), msg, p2)
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  
   301  	if options.skipDERConversion {
   302  		return sig, nil
   303  	}
   304  
   305  	return convertDERtoBER(sig)
   306  }
   307  
   308  // getPubKeyUnsafe reads the pubkey from a ledger device
   309  //
   310  // This function is marked as unsafe as it will retrieve a pubkey without user verification
   311  // It can only be used to verify a pubkey but never to create new accounts/keys. In that case,
   312  // please refer to getPubKeyAddrSafe
   313  //
   314  // since this involves IO, it may return an error, which is not exposed
   315  // in the PubKey interface, so this function allows better error handling
   316  func getPubKeyUnsafe(device SECP256K1, path hd.BIP44Params) (types.PubKey, error) {
   317  	publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath())
   318  	if err != nil {
   319  		return nil, fmt.Errorf("please open the %v app on the Ledger device - error: %v", options.appName, err)
   320  	}
   321  
   322  	// re-serialize in the 33-byte compressed format
   323  	cmp, err := secp.ParsePubKey(publicKey)
   324  	if err != nil {
   325  		return nil, fmt.Errorf("error parsing public key: %v", err)
   326  	}
   327  
   328  	compressedPublicKey := make([]byte, secp256k1.PubKeySize)
   329  	copy(compressedPublicKey, cmp.SerializeCompressed())
   330  
   331  	return options.createPubkey(compressedPublicKey), nil
   332  }
   333  
   334  // getPubKeyAddr reads the pubkey and the address from a ledger device.
   335  // This function is marked as Safe as it will require user confirmation and
   336  // account and index will be shown in the device.
   337  //
   338  // Since this involves IO, it may return an error, which is not exposed
   339  // in the PubKey interface, so this function allows better error handling.
   340  func getPubKeyAddrSafe(device SECP256K1, path hd.BIP44Params, hrp string) (types.PubKey, string, error) {
   341  	publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp)
   342  	if err != nil {
   343  		return nil, "", fmt.Errorf("%w: address rejected for path %s", err, path.String())
   344  	}
   345  
   346  	// re-serialize in the 33-byte compressed format
   347  	cmp, err := secp.ParsePubKey(publicKey)
   348  	if err != nil {
   349  		return nil, "", fmt.Errorf("error parsing public key: %v", err)
   350  	}
   351  
   352  	compressedPublicKey := make([]byte, secp256k1.PubKeySize)
   353  	copy(compressedPublicKey, cmp.SerializeCompressed())
   354  
   355  	return options.createPubkey(compressedPublicKey), addr, nil
   356  }